Java 基础核心复习笔记day01
1. 核心概念与生态
1.1 JDK, JRE, JVM 的包含关系
大厂不仅仅问定义,更看重你对“运行环境”的理解。
1 | +--------------------------------------------------------------------------------+ |
- JVM: 只有翻译能力,没有基石(类库)。
- JRE: 能运行程序,但不能开发(没有编译器
javac)。 - JDK: 全套装备。
🔥 大厂面试题: Q: 为什么 Java 被称为“一次编写,到处运行”? A: 关键在于 字节码 (.class) 和 JVM。Java 源代码编译成与平台无关的字节码,不同平台的 JVM(Windows版, Linux版)负责将相同的字节码翻译成特定平台的机器指令。JVM 屏蔽了底层操作系统的差异。
1.2 编译优化:JIT vs AOT
- JIT (Just-In-Time):
- 原理: 解释执行 -> 探测热点代码 (HotSpot) -> C1/C2 编译器编译为本地机器码 -> 缓存。
- 缺点: “预热”过程导致启动慢。
- AOT (Ahead-Of-Time):
- 原理: 编译期直接生成机器码(如 GraalVM)。
- 场景: Serverless、微服务快速启动。
2. 数据类型与底层存储(重点)
2.1 基本数据类型表 (必背)
Java 的基本类型长度是固定的,不随操作系统变化(这与 C/C++ 不同)。
| 类型 | 字节 (Bytes) | 位 (Bits) | 范围 | 默认值 |
|---|---|---|---|---|
| byte | 1 | 8 | -128 ~ 127 | 0 |
| short | 2 | 16 | -32768 ~ 32767 | 0 |
| int | 4 | 32 | -2^31 ~ 2^31-1 (约21亿) | 0 |
| long | 8 | 64 | -2^63 ~ 2^63-1 | 0L |
| float | 4 | 32 | IEEE 754 单精度 | 0.0f |
| double | 8 | 64 | IEEE 754 双精度 | 0.0d |
| char | 2 | 16 | Unicode 字符 (0 ~ 65535) | ‘\u0000’ |
| boolean | - | - | 只有 true/false | false |
🔥 大厂面试题: Q: Java 中的 boolean 类型占用多少内存? A: JVM 规范没有明确规定。
- 在编译后的字节码中,boolean 变量通常被当作
int处理(占 4 字节,使用iconst_0/iconst_1)。- 在
boolean[]数组中,通常每个元素占 1 个字节(byte)。 底气: 回答时提到“JVM 规范未定义”和“数组与单独变量的区别”是加分项。
2.2 浮点数的坑
1 | float a = 1.0f - 0.9f; |
- 原因: 浮点数采用二进制科学计数法,无法精确表示 0.1 等十进制小数,存在精度丢失。
- 最佳实践: 涉及金额计算,必须使用
java.math.BigDecimal,且必须使用 String 参数的构造器 (new BigDecimal("0.1"))。
2.3 内存布局图解:基本类型 vs 包装类型
当代码执行 int i = 10; Integer j = 10; 时,内存布局如下:
1 | 栈内存 (Stack Frame) 堆内存 (Heap) |
🔥 大厂面试题: Q: new Integer(10) 这个对象在内存中占多大?(64位 JVM) A: 这是一个典型的考察对象内存布局的问题。
- 对象头 (Mark Word + Class Pointer): 开启指针压缩时占 12 字节 (8 + 4),未开启占 16 字节。
- 实例数据 (int value): 4 字节。
- 对齐填充: Java 对象大小必须是 8 的倍数。
- 计算: 12 (头) + 4 (数据) = 16 字节。正好是 8 的倍数,无需填充。
- 结论: 约 16 字节(这是单纯对象的大小,不包含栈上的引用)。
2.4 字符串 String 的至暗时刻 (新增重要章节)
String 是面试中考察最细致的类,没有之一。
🔥 大厂面试题 1: Q: String s = new String(“abc”); 这行代码创建了几个对象? A:
- 情况 1: 如果字符串常量池中已经存在 “abc”,则只在堆中创建一个
String对象。- 情况 2: 如果字符串常量池中不存在 “abc”,则先在常量池中创建一个 “abc” 对象,再在堆中创建一个
String对象。共 2 个。
🔥 大厂面试题 2: Q: String s1 = “a” + “b”; 和 String s2 = x + y; (x, y 为变量) 有什么区别? A:
"a" + "b": 编译器优化。编译时直接变成"ab",放入常量池。x + y: 运行时拼接。底层通过StringBuilder(Java 8) 或StringConcatFactory(Java 9+) 实现,最终会在堆上生成一个新的 String 对象。
🔥 大厂面试题 3: Q: String 为什么要设计成不可变的 (Immutable)? A:
- 字符串常量池 (String Pool): 只有不可变才能实现池化,节省堆内存。
- 安全性: String 常作为网络连接、文件路径、反射参数,不可变防止被恶意修改。
- 线程安全: 不可变对象天生线程安全,多线程共享无需同步。
- HashCode 缓存: String 广泛用于 HashMap Key,不可变性保证了
hash值只需计算一次即可缓存,提升性能。
3. 深入理解:装箱、拆箱与缓存
3.1 缓存池原理 (IntegerCache)
这是通过静态内部类实现的单例模式变种。
1 | // 伪代码演示 IntegerCache 结构 |
🔥 大厂面试题: Q: Integer i1 = 128; Integer i2 = 128; 它们相等吗?为什么? A:
i1 == i2为 false。
- 原因: 128 超出了默认缓存范围
[-128, 127]。- 过程:
Integer.valueOf(128)会直接return new Integer(128),在堆中创建了两个不同的对象,地址不同。
3.2 自动装箱的性能陷阱
在循环中不小心使用包装类会导致大量的 GC(垃圾回收)。
1 | Long sum = 0L; // 致命错误:使用了 Long 包装类 |
4. 面向对象核心机制
4.1 Java 只有值传递 (Pass By Value)
这是初学者最大的误区。Java 永远是值传递。
- 传递基本类型: 传递的是数值的副本。
- 传递引用类型: 传递的是引用地址(指针)的副本,而不是对象本身。
经典面试题代码:
1 | public class ValuePassTest { |
4.2 静态 (Static) 加载顺序
类的加载顺序是:父类静态 -> 子类静态 -> 父类构造 -> 子类构造。
1 | class Father { |
4.3 关键字深度辨析:final, finally, finalize (新增)
这三个词长得像,但毫无关系。
| 关键字 | 类型 | 作用 |
|---|---|---|
| final | 修饰符 | 修饰类(不可继承)、方法(不可重写)、变量(不可修改/常量)。 |
| finally | 关键字 | try-catch 结构的一部分,保证代码一定被执行(常用于关闭资源)。 |
| finalize | 方法 | Object 类的方法,GC 回收对象前调用。已过时 (Deprecated),极不稳定,永远不要使用。 |
🔥 大厂面试题: Q: try { return 1; } finally { return 2; } 返回什么? A: 返回 2。
- 原理:
finally块中的代码会在try块的return语句执行之后、返回之前执行。如果finally里也有return,会直接覆盖try中的返回值。这是严重的逻辑坏味道。
4.4 深拷贝 vs 浅拷贝 (Deep vs Shallow Copy) (新增)
- 浅拷贝 (Shallow Copy): 也就是
Object.clone()的默认行为。只复制对象本身,对象内部的引用类型成员变量依然指向原来的对象。 - 深拷贝 (Deep Copy): 复制对象本身,同时递归复制对象内部引用的所有对象。
🔥 大厂面试题: Q: 如何实现一个对象的深拷贝? A:
- 序列化 (推荐): 将对象序列化为流 (ByteArrayOutputStream),再反序列化回来。前提是对象必须实现
Serializable。- JSON 转换:
JSON.parse(JSON.stringify(obj))(思路类似)。- 递归重写 clone(): 代码量大,易错,不推荐。
5. 常见误区与坑 (大厂避雷指南)
误区:所有整数的 == 比较都不可靠
- 纠正:
int(基本类型) 的==绝对可靠。只有Integer(包装类) 在比较时才需要注意。 - 铁律: 只要是对象比较,闭着眼睛用
equals(),别用==赌运气。
- 纠正:
陷阱:HashMap 中的 Key 如果不重写 hashCode 会怎样?
- 现象:
map.put(new Key("a"), 1);然后map.get(new Key("a"));返回null。 - 原因:
HashMap先算hashCode找桶位。如果没重写,两个逻辑相同的对象哈希值不同,get的时候直接去错误的桶里找,或者在正确的桶里比较==失败,导致找不到数据。
- 现象:
陷阱:short s1 = 1; s1 = s1 + 1; 会报错吗?
- 答案: 会编译报错。因为
1是 int,s1 + 1结果自动提升为int,不能直接赋值给short。 - 变种:
s1 += 1;不会报错。因为+=运算符隐式包含了强制类型转换s1 = (short)(s1 + 1)。
- 答案: 会编译报错。因为
异常陷阱:Exception vs Error (新增)
- Error: JVM 层面无法恢复的严重错误(如
StackOverflowError,OutOfMemoryError),不应该被 catch。 - Exception: 程序运行时的错误。
- Checked Exception (受检异常): 编译期强制处理 (如
IOException)。 - Unchecked Exception (运行时异常): 编译期不检查 (如
NullPointerException,ClassCastException)。
- Checked Exception (受检异常): 编译期强制处理 (如
🔥 面试追问:
NoClassDefFoundError和ClassNotFoundException区别?ClassNotFoundException(Exception): 显式加载类(如Class.forName())时找不到类。NoClassDefFoundError(Error): 编译时类存在,但运行时类文件找不到了(通常是 Jar 包版本冲突或打包遗漏)。
- Error: JVM 层面无法恢复的严重错误(如
6. 最佳实践
- 使用 long 定义时间戳: 系统时间
System.currentTimeMillis()返回的是毫秒数,必须用long,int放不下。 - 金额计算:
- ❌ 禁止使用
double/float。 - ✅ 强制使用
BigDecimal(构造传入 String)。 - ✅ 或者存数据库时单位存为“分”,用
long存储(如 100 代表 1.00 元)。
- ❌ 禁止使用
- POJO 类规范:
- 所有成员变量都用包装类型(如
Integer而非int)。 - 理由: 数据库查询结果可能是
NULL,如果用int接收NULL会报NullPointerException,而Integer可以接收null。
- 所有成员变量都用包装类型(如
- 资源关闭:
- 优先使用 try-with-resources (Java 7+),自动关闭实现了
AutoCloseable接口的资源(如 IO 流、JDBC 连接),避免finally中漏写关闭逻辑。
- 优先使用 try-with-resources (Java 7+),自动关闭实现了
7. 延伸阅读与练习
推荐阅读
- Java Guide: Java 基础知识常见面试题总结
- Oracle 官方文档: Autoboxing and Unboxing
思考与练习
练习题:
1
2
3Integer i1 = new Integer(10);
Integer i2 = 10;
System.out.println(i1 == i2); // 输出什么?false





