Java 基础知识核心复习手册
Java 基础知识核心复习手册(面试·进阶·实战)
本文约 1.2 万字,涵盖数据类型、集合、多线程、IO、反射等 11 大模块。每个知识点均以“问-答”形式剖析,配合代码验证与图表对比,适合查漏补缺和面试冲刺。
目录
一、数据类型与运算符
1.1 8 种基本类型
| 类型 | 字节数 | 取值范围 | 默认值 | 包装类 |
|---|---|---|---|---|
| byte | 1 | -128 ~ 127 | 0 | Byte |
| short | 2 | -32768 ~ 32767 | 0 | Short |
| int | 4 | -2^31 ~ 2^31-1 | 0 | Integer |
| long | 8 | -2^63 ~ 2^63-1 | 0L | Long |
| float | 4 | ±3.4E-38 ~ ±3.4E+38 | 0.0f | Float |
| double | 8 | ±1.7E-308 ~ ±1.7E+308 | 0.0d | Double |
| char | 2 | 0 ~ 65535(Unicode) | ‘\u0000’ | Character |
| boolean | 未明确定义(通常1字节) | true / false | false | Boolean |
问: 为什么 float 和 double 不能用于精确计算(如货币)?
答: 浮点数采用 IEEE 754 表示,存在舍入误差。例如 0.1 + 0.2 在二进制中是无限循环小数。货币计算应使用 BigDecimal。
1.2 自动装箱与 Integer 缓存
问: 下面代码的输出是什么?为什么?
Integer a = 100, b = 100;
Integer c = 200, d = 200;
System.out.println(a == b); // true
System.out.println(c == d); // false
答:
Integer缓存池范围 -128 ~ 127(可通过-XX:AutoBoxCacheMax调整)。- 自动装箱时,值在此范围内会返回缓存对象,否则
new Integer()。 - 所以
a和b引用同一对象,c和d指向不同对象。
正确比较包装类值:使用
Objects.equals()或intValue()。
1.3 位运算经典应用
问: 如何用位运算快速判断一个数是否为 2 的幂?
答: n > 0 && (n & (n - 1)) == 0。
原理:2 的幂的二进制只有一个 1,减 1 后低位全 1,与原数按位与得 0。
二、流程控制与字符串
2.1 switch 支持的类型
int/byte/short/charString(Java 7+):实际比较的是hashCode+equalsenum(枚举)
问: switch 能否用于 long 或 double?
答: 不能。只支持可隐式转为 int 的类型以及 String/enum。
2.2 String 不可变性与常量池
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true,常量池同一对象
System.out.println(s1 == s3); // false,堆中新对象
System.out.println(s1.equals(s3)); // true
问: String 为什么设计成不可变?
答:
- 字符串常量池安全复用。
- 哈希码可缓存(
String的hashCode只计算一次)。 - 作为
HashMap的 key 时保证一致性。 - 线程安全。
2.3 StringBuilder 与 StringBuffer
| 类 | 线程安全 | 性能 | 适用场景 |
|---|---|---|---|
StringBuilder |
否 | 高 | 单线程字符串拼接 |
StringBuffer |
是(方法加 synchronized) |
低 | 多线程环境(极少使用) |
问: 下面代码创建了几个对象?
String result = "a" + "b" + "c";
答: 仅 1 个。编译器会优化为 "abc" 直接放入常量池。
若是 String result = a + b + c(变量),编译后使用 StringBuilder 拼接。
三、面向对象(OOP)
3.1 多态与虚方法表
问: 下面代码输出什么?
class Father {
void show() { System.out.println("Father"); }
}
class Son extends Father {
void show() { System.out.println("Son"); }
}
Father f = new Son();
f.show(); // Son
答: 多态:编译时类型为 Father,运行时类型为 Son,调用实际对象的方法。JVM 通过虚方法表动态绑定。
3.2 抽象类 vs 接口(Java 8+)
| 特性 | 抽象类 | 接口(Java 8) |
|---|---|---|
| 关键字 | abstract |
interface |
| 成员变量 | 可以是各种类型 | 默认为 public static final |
| 构造器 | 有 | 无 |
| 实例方法 | 可以有 | default / static 方法 |
| 多继承 | 单继承 | 可多实现 |
问: 什么时候用抽象类,什么时候用接口?
答:
- 抽象类:表示“is-a”关系,且需共享代码(状态或通用行为)。
- 接口:表示“can-do”能力,常用于回调、策略模式,且 Java 8 后可用默认方法提供通用实现。
3.3 内部类
class Outer {
private int a = 10;
class Inner { // 成员内部类
void print() { System.out.println(a); }
}
static class StaticNested { } // 静态嵌套类
void method() {
class Local { } // 局部内部类
Runnable r = new Runnable() { // 匿名内部类
@Override public void run() {}
};
}
}
问: 为什么非静态内部类不能有静态成员?
答: 非静态内部类依赖于外部类实例,而静态成员属于类级别,违背依赖关系。除非该成员是编译时常量(static final 基本类型或字符串)。
四、集合框架
4.1 ArrayList 扩容机制
问: ArrayList 初始容量多少?如何扩容?
答:
- 无参构造:初始容量为 10(懒加载,第一次
add时分配)。 - 扩容:
newCapacity = oldCapacity + (oldCapacity >> 1)→ 1.5 倍。 - 若仍不足,则直接扩至所需大小。
- 最大容量
Integer.MAX_VALUE - 8。
代码验证:
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) list.add(i);
// 在 debug 中观察 elementData 数组长度变化:10 → 15 → 22
4.2 HashMap(核心)
存储结构
- 数组 + 链表 + 红黑树(树化阈值 8,退化为链表阈值 6)
- 默认容量 16,负载因子 0.75 → 扩容阈值 12
put 流程
1. 计算 key.hashCode() 并扰动 (hash = h ^ (h>>>16))
2. (n-1) & hash 计算槽位
3. 若槽位空,直接放入
4. 否则判断链表头或树根是否相同 key → 覆盖
5. 否则插入链表尾部(JDK 8),若链表长度≥8 且数组≥64 → 树化
6. 若元素总数 > 阈值 → 扩容(resize)
问: HashMap 为何线程不安全?
答:
- JDK 7:扩容时头插法,多线程下可能形成环形链表,导致死循环 CPU 100%。
- JDK 8:改用尾插法,但仍存在数据覆盖(两个线程同时 put 同一槽位,一个覆盖另一个的值)以及 size 计数丢失。
解决方案:
ConcurrentHashMap。
扩容时的 rehash 优化
// 新 table 长度是旧 table 的 2 倍
if ((e.hash & oldCap) == 0) {
// 低位索引不变
} else {
// 高位索引 = 原索引 + oldCap
}
为什么高效? 因为 oldCap 是 2 的幂,其二进制只有一个 1。e.hash & oldCap 可快速判断高位是 0 还是 1,无需重新计算 hash。
4.3 集合常见陷阱
陷阱1:Arrays.asList() 返回的 List 不支持增删
List<String> list = Arrays.asList("a", "b");
list.add("c"); // UnsupportedOperationException
原因:返回的是 Arrays 内部类 ArrayList,未实现 add/remove。
陷阱2:在 for-each 中删除元素
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3));
for (Integer i : list) {
if (i == 2) list.remove(i); // ConcurrentModificationException
}
修复:使用 Iterator.remove()。
五、异常处理
5.1 异常体系
Throwable
├── Error(不可恢复,如 OutOfMemoryError、StackOverflowError)
└── Exception
├── RuntimeException(非受检,如 NullPointerException)
└── 非 RuntimeException(受检,如 IOException、SQLException)
问: finally 块一定会执行吗?
答: 不一定。以下情况不执行:
System.exit(0)- JVM 崩溃
- 线程被中断或杀死
5.2 try-with-resources
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
// 自动调用 close()
} catch (IOException e) {
e.printStackTrace();
}
要求:资源类必须实现 AutoCloseable 接口。
六、泛型
6.1 类型擦除
问: 泛型 List<String> 在运行时能通过反射添加 Integer 吗?
答: 可以。因为 Java 泛型是编译期语法,运行时 JVM 并不知道 String 类型参数,实际类型为 Object(若不指定边界)。
List<String> list = new ArrayList<>();
list.getClass().getMethod("add", Object.class).invoke(list, 123);
System.out.println(list.get(0)); // 123,运行时没有类型检查
6.2 PECS 原则(Producer Extends, Consumer Super)
? extends T:只能读,不能写(生产者)。? super T:只能写,不能精确读(消费者)。
public static void copy(List<? extends Number> src, List<? super Number> dest) {
for (Number n : src) dest.add(n);
}
src 提供 Number 子类对象(生产),dest 消费 Number 对象(存储)。
七、常用 API
7.1 日期时间(java.time)
| 类 | 说明 |
|---|---|
LocalDate |
年月日 |
LocalTime |
时分秒纳秒 |
LocalDateTime |
日期+时间 |
ZonedDateTime |
带时区 |
DateTimeFormatter |
线程安全的格式化器 |
示例:
LocalDate today = LocalDate.now();
LocalDate parsed = LocalDate.parse("2025-03-15");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String formatted = today.format(formatter);
7.2 BigDecimal 精度计算
问: new BigDecimal(0.1) 和 BigDecimal.valueOf(0.1) 有什么区别?
答:
new BigDecimal(0.1)会精确表示浮点数的二进制不精确值,结果为0.100000000000000005551...。BigDecimal.valueOf(0.1)内部调用Double.toString,更符合人预期。
推荐使用
BigDecimal.valueOf()或new BigDecimal("0.1")。
八、输入输出(I/O)
8.1 字节流 vs 字符流
- 字节流:
InputStream/OutputStream,处理二进制数据(图片、视频)。 - 字符流:
Reader/Writer,处理文本(自动处理字符编码)。
问: 为何不总是用字符流?
答: 字节流处理任何类型,但字符流需要编码转换,效率稍低,且不能用于非文本数据。
8.2 序列化
问: transient 关键字的作用?
答: 修饰的字段不会被序列化。例如密码、缓存数据等无需持久化的字段。
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private transient String password; // 序列化后为 null
}
九、多线程基础(API 层面)
9.1 线程创建方式
| 方式 | 特点 |
|---|---|
extends Thread |
单继承限制,不推荐 |
implements Runnable |
可多实现,无返回值 |
Callable + FutureTask |
有返回值,可抛异常 |
FutureTask<Integer> task = new FutureTask<>(() -> {
Thread.sleep(1000);
return 100;
});
new Thread(task).start();
Integer result = task.get(); // 阻塞直到返回
9.2 线程状态迁移
NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED
问: sleep() 和 wait() 的区别?
答:
sleep()属于Thread,不释放锁,时间到自动唤醒。wait()属于Object,必须在同步块内调用,释放锁,需要notify/notifyAll唤醒。
9.3 同步工具类速览
| 工具 | 作用 |
|---|---|
CountDownLatch |
等待多个线程完成 |
CyclicBarrier |
多个线程互相等待,达到屏障后继续 |
Semaphore |
控制并发访问数量 |
Exchanger |
两个线程交换数据 |
示例: 使用 CountDownLatch 等待所有子线程结束
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
// do work
latch.countDown();
}).start();
}
latch.await();
9.4 线程池(ThreadPoolExecutor)
核心参数:
corePoolSize:核心线程数maximumPoolSize:最大线程数keepAliveTime:空闲线程存活时间workQueue:任务队列(LinkedBlockingQueue、SynchronousQueue等)handler:拒绝策略(AbortPolicy、CallerRunsPolicy等)
问: Executors.newFixedThreadPool(10) 有什么风险?
答: 它使用无界队列 LinkedBlockingQueue,若任务生产速度大于消费速度,会导致 OOM。推荐手动创建 ThreadPoolExecutor。
十、反射与注解
10.1 反射常用操作
// 获取 Class 对象
Class<?> clazz = Class.forName("com.example.User");
// 实例化(无参构造)
Object obj = clazz.getDeclaredConstructor().newInstance();
// 调用私有方法
Method m = clazz.getDeclaredMethod("secret");
m.setAccessible(true);
m.invoke(obj);
问: 反射的缺点?
答:
- 性能损耗(需检查访问权限、动态解析)。
- 破坏封装性(可访问私有成员)。
- 安全限制(SecurityManager 可能阻止)。
10.2 注解保留策略
@Retention 取值 |
说明 |
|---|---|
SOURCE |
仅存在源码中,编译后丢弃(如 @Override) |
CLASS |
保留在 .class 文件中,但 JVM 不加载(默认) |
RUNTIME |
运行时可用,通过反射读取(常用) |
十一、JVM 相关基础(快速回顾)
问: JVM 内存区域哪些是线程共享的?
答: 堆和方法区(JDK 8 后元空间)共享;虚拟机栈、本地方法栈、程序计数器私有。
问: 双亲委派模型如何防止核心类库被篡改?
答: 类加载请求向上委托,java.lang.String 始终由 BootstrapClassLoader 加载,自定义类加载器无法覆盖。
问: volatile 解决了什么?
答: 保证可见性(写后立即刷新到主内存)和有序性(禁止指令重排序),但不保证原子性。
问: 你说不保证原子性,那 volatile long 的赋值是原子的吗?
答: 在 32 位 JVM 中,64 位的 long/double 读写可能分两次操作,使用 volatile 可保证原子性(JVM 会插入 lock 指令)。但对于 i++ 仍非原子。
问: CMS 和 G1 的核心区别?
答: CMS 是标记-清除(碎片化),全堆并发,停顿不可控;G1 基于 Region,增量回收,可设定最大停顿时间。
更多推荐


所有评论(0)