JAVA基础面试题
JAVA基础篇
八股文
什么是反射
- 含义
反射是 Java 提供的一种机制,可以在运行时动态地获取类的信息、创建对象、调用方法、访问字段。
- 换句话说:
- 平时你是写死调用类、方法
- 反射让你在“运行时”动态操作类和对象
- 🧠 举个最简单的例子
👇 正常方式:👇 反射方式:1
2Person p = new Person();
p.sayHello();🔍 说明:1
2
3
4Class<?> clazz = Class.forName("Person");
Object obj = clazz.getDeclaredConstructor().newInstance(); // 创建对象
Method method = clazz.getMethod("sayHello"); // 找到方法
method.invoke(obj); // 调用方法
- Class.forName(“类名”):加载类
- getDeclaredConstructor().newInstance():创建对象
- getMethod(“方法名”):获取方法
- invoke():执行方法
- 为什么说反射是在运行时
通过学习JVM,我们已经知道,我们编写一个类,会执行为class文件,类加载器会将class文件加载到JVM中,其中方法区存的是类的属性和方法信息。
👇当我们new了一个对象时,会在堆中开辟一块空间存放这个对象,这个过程是在编译过程中的,还没有开始运行,就已经知道了需要这个对象,所以开辟一块空间当你执行:1
2Person p = new Person();
p.sayHello();clazz.getMethod(“methodName”) 查的是:1
2Class<?> clazz = Class.forName("com.example.MyClass");
查的是方法区中该类的“方法表”,而不是去堆中查找。
每个类在被加载的时候,JVM 会为它建立一个「类元信息结构」:
- 方法名
- 返回类型
- 参数类型
- 修饰符(public/private/static)
- 方法在字节码里的位置
- 字节码数组(code)
反射就是读取这些信息,并用它来动态调用
final,finally,finalize的区别是什么
| 关键词 | 类型 | 用途 | 是否是关键字 |
|---|---|---|---|
final | 关键字 | 声明常量/不可变对象/类/方法 | ✅ 是 |
finally | 关键字 | 用于 try-catch-finally 中,一定会执行的代码块 | ✅ 是 |
finalize() | Object 方法 | 垃圾回收前由 GC 调用的钩子函数,已过时 | ❌ 是方法 |
面试题
- 面试官:JAVA的基础八大类型
- 我:
类型 类别 示例 byte 整数 byte a = 10; short 整数 short b = 20; int 整数 int c = 100; long 整数 long d = 1000L; float 浮点 float e = 3.14f; double 浮点 double f = 3.1415; char 字符 char g = ‘A’; boolean 布尔 boolean h = true;
- 面试官: 怎么把string类型的时间转换成时间类型
我:
- 使用 DateTimeFormatter
1
2
3
4
5
6
7
8
9
10
11
12import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
String timeStr = "2025-07-24 15:30:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.parse(timeStr, formatter);
System.out.println("转换后的 LocalDateTime:" + localDateTime);
}
}
- 使用 DateTimeFormatter
面试官: 判断一个字段是一个字符串的第几位,用哪个方法
我:
- String.indexOf()
底层是通过字符数组遍历+匹配来实现的。对于单字符查找,它会从字符串左边开始,逐个比较字符值是否相等;对于子字符串查找(indexOf(String)),JDK 使用的是一种改进版的暴力匹配算法
面试官问我的时候,我想不起来了,我说用charAt循环遍历一下,太好笑了,估计给面试官都整无语了,想得起来这个,想不起来 String.indexOf()
- String.indexOf()
面试官:Spring Boot 启动核心注解
我:
1
2
3
4
5
6
7
8
9
10
11
12
13
14@SpringBootApplication
@EnableCreateCacheAnnotation
@EnableMethodCache(basePackages = "com.shortplay.server")
@EnableScheduling
@Slf4j
public class StartApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(StartApp.class, args);
Environment env = context.getEnvironment();
String active = env.getProperty("spring.profiles.active");
log.info("服务启动成功, active = {}", active);
}
}✅ @SpringBootApplication(最核心)等价于以下三个注解的组合:
- @Configuration // 表示该类是配置类(等同于配置文件)
- @EnableAutoConfiguration // 开启自动配置功能(根据 classpath 中的 jar 自动配置)
- @ComponentScan // 开启包扫描(默认扫描当前类所在包及其子包)
注解 来源 作用 @EnableSchedulingSpring 启用定时任务 @EnableMethodCacheJetCache 启用方法级缓存,识别 @Cached等注解@EnableCreateCacheAnnotationJetCache 启用 @CreateCache,用于声明缓存对象集合篇
面试官:介绍一下hashmap
- 我:
HashMap 是 Java 中最常用的集合类之一,它基于哈希表(Hash Table)实现,用于以键值对(Key-Value)的形式存储数据,属于 java.util 包中的一部分。
HashMap的特点:- 线程不安全
- JDK8之前:数组 + 链表;JDK8之后:数组 + 链表/红黑树(链表长度超过 8 且数组长度大于 64 时转为红黑树)
- ✅ 允许一个 null 键和多个 null 值
- 初始容量 默认是 16
- 负载因子 默认是 0.75(即 75% 满时扩容)
HashMap存储数据的原理:
1
map.put("apple", 1);- ✅ 第一步:计算哈希值(扰动函数)
1
int hash = hash(key); - ✅ 第二步:定位数组下标(index)
1
int index = (table.length - 1) & hash;1
int index = (n - 1) & hash; ✅ 第三步:判断该位置是否已有元素
- 如果该位置是空的:说明没有哈希冲突,直接插入新节点。
✅ 第四步:如果该位置不为空(发生哈希冲突)
- 此时需要将该位置的数据取出,判断key是否相等
- 相等的话则直接覆盖 value即可 (例如:map.put(“apple”, 1),key指的是apple)
- 不相等则遍历链表或者红黑树在末尾插入数据
1
2
3
4
5
6
7Node<K,V> p = table[index];
if (p.hash == hash && (p.key == key || (key != null && key.equals(p.key)))) {
// key 相等,直接覆盖旧值
p.value = value;
} else {
// 遍历链表或红黑树
}
- 此时需要将该位置的数据取出,判断key是否相等
- ✅ 第五步:是否需要扩容
- 每次插入后判断是否需要扩容
put(key, value)
↓
计算 hash → 计算 index = (n-1) & hash
↓
判断 table[index]
↓
为空 → 直接插入新节点
↓
不为空 → 判断是否 key 相等
↓
key 相等 → 覆盖旧值
↓
key 不相等 → 链表尾部插入或转红黑树
↓
插入后判断是否需要扩容
- 面试官: hashmap为什么会选择红黑树而不是其他的树
我:
- 红黑树是一种自平衡二叉搜索树,可以保证基本操作(查找、插入、删除)时间复杂度为 O(log n)。
- 兼顾查找速度和维护成本
- AVL 树查找速度稍优于红黑树,但插入/删除时旋转较多,开销大。(AVL树的性质:左子树高度 - 右子树高度,且值只能是 -1、0、1。)
- 红黑树牺牲一点查找效率,换来更少的重平衡操作,整体性能更均衡。
面试官: 什么是哈希表
- 我:
- 哈希表的结构:
哈希表(Hash Table)的底层结构主要由 数组 组成,结合 链表 或 红黑树 来处理哈希冲突。
- 数组(Bucket 数组)
- 核心存储结构是一个数组,每个数组位置称为“桶”(bucket)。
- 哈希函数计算出的哈希值会映射到数组的某个索引位置。
- 数组的每个元素是一个链表或红黑树的头节点,用来处理哈希冲突。
- 链表(或红黑树)
- 因为不同的键可能映射到相同的桶,产生冲突(哈希冲突)。
- 传统解决冲突的方法是用链表把冲突的元素串起来,存放在同一个桶中。
- JDK8 及以后,当链表长度过长时,会将链表转换成红黑树,提高查找效率。
- 结构示意图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16table(数组):
+----------+----------+----------+----------+------------------+
| bucket0 | bucket1 | bucket2 | bucket3 | bucket4 |
| null | ↓ | null | ↓ | ↓ |
| | Node1 | | Node3 | TreeNodeA(B) | ← 红黑树根节点(黑)
| | ↓ | | ↓ | / \ |
| | Node2 | | Node4 | R(B) T(R) |
| | | | | / \ \ |
| | | | | ... ... X(B) |
+----------+----------+----------+----------+------------------+
说明:
- bucket0:为空(null)
- bucket1:有两个冲突元素,用链表 Node1 → Node2 存储
- bucket3:链表 Node3 → Node4
- bucket4:冲突太多,链表已转为红黑树(TreeNodeA 是根)
- 哈希表的结构:
- 面试官:如果一个对象 没有重写 equals() 方法,将它作为 HashMap 的 key,会产生什么问题?
我:
HashMap 判断两个 key 是否相同,要经过两个步骤:
- 比较 hash 值是否相等:hash(key1) == hash(key2)
- 调用 equals() 方法判断是否真的“相等”:key1.equals(key2)
如果没有重写hashCode(),则两个对象的hash值可能会不同
如果没有重写equals的话,equals等价于 ==,只有引用相同时才会返回true
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Person {
String name;
Person(String name) {
this.name = name;
}
}
HashMap<Person, String> map = new HashMap<>();
//new了两个对象,所以在JVM的堆内存中 会有两个对象属性 p1和p2
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
//将p1加到hashmap中
map.put(p1, "Developer");
//(前提:已经重写hashCode(),没有重写equles)因为没有重写equals,所以map.get的方法中的比较key时会是false,会发现没有一样的key,所以会返回null
//(前提:已经重写equles,没有重写hashCode())因为没有重写hashCode(),所以map.get的方法中通过计算hash来确定数据在哪一个位置,但是p2的hash值和p1的hash值不同,所以p1的位置在map中的第一个位置,但是却去第二个位置查找了,所以会一致查询不到
System.out.println(map.get(p2)); // 输出NULL? - 重写equles和hashcode示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import java.util.Objects;
public class Person {
private String name;
private int age;
// 构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 重写 equals() 方法
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 1. 引用相同
if (obj == null || getClass() != obj.getClass()) // 2. 类型不同
return false;
Person person = (Person) obj; // 3. 类型一样,强制转换
return age == person.age && // 4. 按字段比较
Objects.equals(name, person.name);
}
// 重写 hashCode() 方法
@Override
public int hashCode() {
return Objects.hash(name, age); // 推荐写法
}
// 可选:用于打印
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + '}';
}
}
面试官:如何实现把hashmap的数据按key排序,可以利用其他数据结构也可以在hashmap上
- 我:
- ✅ 方法一:使用 TreeMap (TreeMap会自动排序)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import java.util.*;
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("banana", 3);
map.put("apple", 5);
map.put("pear", 2);
// 将 HashMap 转成 TreeMap(自动按 key 升序排序)
Map<String, Integer> sortedMap = new TreeMap<>(map);
for (Map.Entry<String, Integer> entry : sortedMap.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
} - ✅ 方法二:使用 HashMap.entrySet() + List 排序
HashMap.entrySet():是 Java Map 接口中的一个非常重要的方法,它的作用是返回 该 Map 中所有键值对(Entry)的集合。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import java.util.*;
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("banana", 3);
map.put("apple", 5);
map.put("pear", 2);
// 转换为 List<Map.Entry>
List<Map.Entry<String, Integer>> entryList = new ArrayList<>(map.entrySet());
// 按 key 升序排序
entryList.sort(Map.Entry.comparingByKey());
// 排序后插入 HashMap 保持顺序
}
}
- ✅ 方法一:使用 TreeMap (TreeMap会自动排序)
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Little Monste'Blog!
评论





