Appearance
Java 语言基础与环境(复习/面试笔记)
说明:本笔记聚焦「Java 语言基础与环境」,覆盖语言历史与特性、JDK/JRE/JVM 体系、程序执行机制、基础语法与数组,并结合版本升级、工具链和常见坑给出实战建议。适用于初学与面试/复盘阶段快速体系化梳理。
1. 概述
- 一句话定义:Java 是一种面向对象、强静态类型、通过字节码与 JVM 实现平台无关的通用编程语言。
- 解决问题:以安全、可维护、跨平台、高性能的方式构建大型企业级应用(后端服务、金融系统、大数据处理等)。
- 适用场景:企业后端(Spring Boot/Cloud)、大数据(Spark/Flink)、Android(历史主语言)、高稳定性领域(金融/电信等)。
2. 核心概念 / 原理
2.1 JDK / JRE / JVM 体系结构
- JDK(Development Kit):开发工具集,包含 JRE 与开发工具(javac、jdb、jar、jlink 等)。
- JRE(Runtime Environment):运行时环境,包含 JVM 与核心类库(rt.jar 等),仅可运行程序。
- JVM(Virtual Machine):执行字节码的虚拟机,含类加载、解释器、JIT 编译器、GC、线程调度等。
关系:JDK ⊃ JRE ⊃ JVM。理解三者的包含关系是掌握 Java 运行机制的基础。
2.2 Java 程序执行流程(编译期 + 运行期)
- 编译期:javac 将 .java 源码编译为平台无关的 .class 字节码。
- 运行期:JVM 类加载 → 解释执行 → 热点方法由 JIT 编译成本地机器码缓存,形成“解释+JIT”混合模式,兼顾冷启动与峰值性能。
- 跨平台原理:字节码平台无关,JVM 屏蔽硬件与 OS 差异;JVM 本身则是平台相关的。
- AOT:GraalVM Native Image 可将应用提前编译为平台原生可执行文件,提升启动速度(云原生/Serverless 友好),牺牲部分动态性与反射可达性(需配置反射元数据)。
流程示意(类加载到执行):
mermaid
flowchart TD
A[.java 源文件] --> B[javac 编译]
B --> C[.class 字节码]
C --> D[类加载/链接/初始化]
D --> E[解释器执行]
E --> F{热点探测?}
F -- 是 --> G[JIT 编译成本地码]
G --> H[本地代码执行]
F -- 否 --> E2.3 JVM 规范与主流实现
- 规范:定义字节码格式、指令集、内存模型、GC 接口等。
- 实现:
- HotSpot(Oracle/OpenJDK):最常用,JIT/GC 技术成熟。
- GraalVM:多语言互操作、AOT(Native Image)。
- OpenJ9(Eclipse/IBM):低内存占用、冷启动快,适配云场景。
2.4 Java 历史与 LTS
- 发布节奏:自 2017 起半年一更;生产优先选用 LTS(Java 8、11、17、21)。
- 关键版本(核对与补充):
- Java 8:Lambda、Stream、Optional、新日期时间 API(java.time)。
- Java 11:标准 HTTP Client(java.net.http),移除 Java EE 和 CORBA 模块;允许在 Lambda 参数上使用 var(JEP 323,属于“var 增强”,基础的 var 引入自 Java 10)。
- Java 17:密封类(sealed)、Record 转正、模式匹配预览(instanceof pattern matching 更成熟)。
- Java 21:虚拟线程(Project Loom)转正、结构化并发(java.util.concurrent.StructuredTaskScope,预览)。
3. 关键知识点详解(含代码/图示)
3.1 安装与多版本管理
- 环境变量:配置 JAVA_HOME 与 PATH(Windows:系统属性;Mac/Linux:~/.zshrc 或 ~/.bashrc)。
- 多版本管理工具:
- Mac/Linux:jenv 或 SDKMAN!(推荐)。
- 验证:java -version 与 javac -version 一致;IDEA 在 Project Structure 中设置 JDK。
示例(SDKMAN!):
bash
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk list java
sdk install java 21-tem
sdk use java 21-tem
java -version3.2 升级路径与模块化兼容
- 从 Java 8 → 11 → 17 的升级策略:
- JPMS 模块化导致反射/内部 API 访问受限,需合理使用 --add-opens/--add-exports。
- Java EE(javax.)模块已被移除(11),需迁移至 Jakarta EE(jakarta.)或 Spring 生态。
- 默认 GC 变更:从 Parallel → G1(自 JDK 9),新 GC(ZGC、Shenandoah)可按需选择。
- 借助工具:
- 废弃 API 检测:jdeprscan。
- 依赖治理:Maven Enforcer 插件约束 JDK/依赖版本。
示例(jdeprscan):
bash
$JAVA_HOME/bin/jdeprscan --release 17 target/classes示例(Maven Enforcer):
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[17,22)</version>
</requireJavaVersion>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>模块开放(示例):
bash
java --add-opens java.base/java.lang=ALL-UNNAMED -jar app.jar3.3 OpenJDK 与商业 JDK 选择
- 开源免费发行版:Eclipse Temurin(Adoptium, 推荐)、Amazon Corretto、Azul Zulu、Microsoft Build of OpenJDK。
- 关注点:安全补丁频率、LTS 支持期限、容器/云原生优化、证书策略。
- Oracle JDK 自 11 起商业用途需授权;多数企业选 OpenJDK 发行版。
3.4 字节码与 Class 文件结构(JDK 17 视角)
- 结构:魔数 0xCAFEBABE、版本号、常量池、访问标志、类/超类/接口索引、字段表、方法表、属性表。
- 常量池:存储符号引用(类名、方法描述符、字符串字面量等);反射、动态代理、invokedynamic 的基础。
- 分析工具:javap。
示例(javap 分析):
java
public class BytecodeDemo {
public static String foo(int x) {
try {
Integer a = x; // 自动装箱 -> Integer.valueOf
return "v=" + a; // 字符串拼接 -> StringBuilder
} finally {
System.out.println("finally"); // try-finally 展开
}
}
}bash
javac BytecodeDemo.java
javap -c -v BytecodeDemo
# 关注:
# - 常量池 (Constant pool)
# - 操作码:invokestatic/invokevirtual/monitorenter/monitorexit 等
# - 局部变量表/操作数栈/异常表方法调用指令速览:
- invokevirtual:实例方法(多态)
- invokeinterface:接口方法
- invokespecial:构造器/private/super(不走虚分派)
- invokestatic:静态方法
- invokedynamic:引导方法 + CallSite,Lambda/动态语言基础(Java 8 的 Lambdas)
3.5 Java 程序执行与 Loom
- 虚拟线程(JDK 21 正式):轻量线程,百万级并发,API 简洁。
- 结构化并发(JDK 21 预览):用 StructuredTaskScope 管理关联任务,简化并发错误处理与取消传播。
示例(虚拟线程 + 结构化并发):
java
// JDK 21, --enable-preview 可选(结构化并发是预览 API)
import java.util.concurrent.*;
public class LoomDemo {
public static void main(String[] args) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> f1 = scope.fork(() -> fetch("A"));
Future<String> f2 = scope.fork(() -> fetch("B"));
scope.join(); // 等待所有任务
scope.throwIfFailed(); // 若有异常则抛出并取消其他任务
System.out.println(f1.resultNow() + " & " + f2.resultNow());
}
}
static String fetch(String name) throws InterruptedException {
Thread.sleep(100); // I/O 模拟
return "ok-" + name;
}
}虚拟线程创建:
java
Thread.ofVirtual().start(() -> System.out.println(Thread.currentThread()));3.6 基本语法:数据类型与变量
- 基本类型(8 种)
- 整型:byte(1B)、short(2B)、int(4B)、long(8B)
- 浮点:float(4B, ~7 位有效)、double(8B, ~15 位)
- 字符:char(2B, Unicode)
- 布尔:boolean(JVM 层面按字节存储实现相关)
- 引用类型:类、接口、数组、枚举、注解、记录(record)等。
- 存储位置澄清:
- “基本类型都在栈上”是误解。准确说:局部变量(含基本类型)的值在栈帧局部变量表;对象实例与数组元素(无论基本/引用)在堆上,对象字段在堆上。
取值范围与精度要点:
- int 范围约 ±21 亿;long 用于大数计数与时间戳。
- 金融/货币计算:严禁使用 float/double,使用 BigDecimal(字符串构造避免二进制小数误差)。
java
BigDecimal x = new BigDecimal("0.1"); // 正确
BigDecimal y = new BigDecimal(0.1); // ⚠️ 精度问题类型转换:
- 自动提升:byte→short→int→long→float→double(算术中 byte/short/char 会先提升为 int)。
- 强制转换:高精度转低精度需强转,可能溢出/截断。
java
byte b = (byte) 130; // 结果为 -126(仅保留低 8 位)
int i = 'A'; // 65,char 自动提升为 int自动装箱/拆箱与缓存:
- 编译器插入 Integer.valueOf/intValue。
- Integer 缓存:[-128, 127],超出范围新建对象,== 比较可能出坑。
java
Integer a = 127, b = 127; // true(缓存)
Integer c = 128, d = 128; // false(不同对象)
System.out.println(a == b);
System.out.println(c == d);
// 规范:包装类型用 equals 比较引用与 null:
- 引用保存对象地址(引用别名);null 表示不指向任何对象。
- NPE 定位:JDK 14+ 提供更详细的 NPE 信息。
- 最佳实践:Optional、Objects.requireNonNull。
java
public String findName(User u) {
Objects.requireNonNull(u, "user must not be null");
return Optional.ofNullable(u.getName()).orElse("N/A");
}3.7 运算符与表达式(含位运算)
- 类别:算术、关系、逻辑、位运算、赋值、三目。
- 优先级:一元 > 算术 > 移位 > 关系 > 按位 > 逻辑 > 三目 > 赋值。
- 位运算高频实战:
- 权限标志位(组合/检验)
- HashMap 下标优化:n & (cap - 1)
- 快速乘/除 2、交换两数
java
// 权限标志位
int READ = 1 << 0, WRITE = 1 << 1, EXEC = 1 << 2;
int perm = READ | WRITE; // 授权
boolean canWrite = (perm & WRITE) != 0; // 检查
// 哈希取模(容量为 2 的幂)
int idx = hash & (capacity - 1);
// 异或交换(学习用,生产更可读的写法)
a ^= b; b ^= a; a ^= b;3.8 流程控制与模式匹配
- if-else、switch、for/while/do-while、break/continue/return。
- 增强 for(for-each):
- 遍历集合底层使用 Iterator;遍历数组使用下标。
- 遍历集合删除元素:必须使用 Iterator.remove(),否则可能抛 ConcurrentModificationException。
java
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
if (it.next() < 0) it.remove(); // 正确的删除方式
}- 标签跳转(不推荐,降低可读性):
java
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i * j > 2) break outer;
}
}- switch 表达式(JDK 14 正式)与模式匹配(JDK 16 起 instanceof pattern;JDK 21 switch 上的模式匹配预览):
java
// switch 表达式
int score = 85;
String grade = switch (score / 10) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> "C";
case 6 -> "D";
default -> "F";
};
// instanceof 模式匹配
Object o = "hello";
if (o instanceof String s && s.length() > 3) {
System.out.println(s.toUpperCase());
}3.9 数组
- 固定长度、同类型、索引从 0 开始,越界抛 ArrayIndexOutOfBoundsException。
- 一维/多维(“数组的数组”,交错结构,每行长度可不同,内存不保证连续)。
- 数组有 length 属性,数组名是引用;System.arraycopy() 原地高效拷贝。
java
int[] a = new int[5]; // 默认 0
int[] b = {1,2,3};
int[][] m = new int[2][]; // 交错数组
m[0] = new int[]{1,2};
m[1] = new int[]{3,4,5};
int[] dst = new int[3];
System.arraycopy(b, 0, dst, 0, 3);Arrays 工具类(JDK 17):
- 排序:Arrays.sort()
- 基本类型:双轴快排(O(n log n))
- 对象:TimSort(稳定,适合部分有序数据)
- 二分查找:Arrays.binarySearch()(需先排序)
- 复制/填充/比较:copyOf/copyOfRange/fill/equals/deepEquals
- 注意:
- Arrays.equals 比较一维数组元素是否相等;多维数组用 Arrays.deepEquals。
- Arrays.asList 返回固定大小 List(不支持 add/remove),若需可变需 new ArrayList<>(...)。
- 基本类型数组传入 Arrays.asList(int[]) 会被当作一个元素,需用 Integer[] 或流。
java
// asList 陷阱与正确用法
int[] arr = {1,2,3};
List<int[]> wrong = Arrays.asList(arr); // 长度为 1,元素是 int[] 本身
Integer[] boxed = {1,2,3};
List<Integer> ok = new ArrayList<>(Arrays.asList(boxed));
// 或
List<Integer> ok2 = Arrays.stream(arr).boxed().toList();4. 代码示例(可运行片段)
- 自动装箱/缓存陷阱与规范比较:
java
public class BoxingPitfall {
public static void main(String[] args) {
Integer a = 127, b = 127;
Integer c = 128, d = 128;
System.out.println(a == b); // true
System.out.println(c == d); // false
System.out.println(c.equals(d)); // true
}
}- 位运算在权限系统中的应用:
java
class Permission {
static final int READ = 1 << 0;
static final int WRITE = 1 << 1;
static final int EXEC = 1 << 2;
static int grant(int perm, int flag) { return perm | flag; }
static int revoke(int perm, int flag) { return perm & ~flag; }
static boolean has(int perm, int flag) { return (perm & flag) != 0; }
public static void main(String[] args) {
int p = 0;
p = grant(p, READ);
p = grant(p, WRITE);
System.out.println(has(p, EXEC)); // false
p = revoke(p, WRITE);
System.out.println(has(p, WRITE)); // false
}
}- switch 表达式与 instanceof 模式匹配:
java
public class SwitchPatternDemo {
static String typeOf(Object o) {
return switch (o) {
case null -> "null";
case Integer i -> i > 0 ? "positive int" : "non-positive int";
case String s -> "String(" + s.length() + ")";
default -> "other";
};
}
}- 虚拟线程并发请求(JDK 21):
java
import java.net.http.*;
import java.net.URI;
public class VirtualThreadHttp {
public static void main(String[] args) throws Exception {
var client = HttpClient.newHttpClient();
Runnable task = () -> {
try {
var req = HttpRequest.newBuilder(URI.create("https://example.com")).build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(Thread.currentThread() + " -> " + resp.statusCode());
} catch (Exception e) {
e.printStackTrace();
}
};
for (int i = 0; i < 1000; i++) Thread.ofVirtual().start(task);
Thread.sleep(2000);
}
}- Arrays 工具与二分查找:
java
import java.util.Arrays;
public class ArraysDemo {
public static void main(String[] args) {
int[] arr = {5,1,4,3,2};
Arrays.sort(arr);
int idx = Arrays.binarySearch(arr, 3);
System.out.println(Arrays.toString(arr)); // [1,2,3,4,5]
System.out.println(idx); // 2
}
}5. 对比 / 易混淆点
| 概念 | JDK | JRE | JVM |
|---|---|---|---|
| 定义 | 开发工具集 | 运行时环境 | 执行引擎 |
| 包含 | JRE + 开发工具 | JVM + 核心类库 | 字节码执行、JIT、GC |
| 适用 | 开发/编译/打包/调试 | 仅运行 | 运行实现 |
| 概念 | 基本类型 | 包装类型 |
|---|---|---|
| 存储 | 值语义(局部在栈帧,字段/数组元素在堆) | 引用语义(对象在堆) |
| 默认值 | 0/false/'\u0000' | null |
| 比较 | == 比较值 | equals 比较值,== 比较引用 |
| 其他 | 无装箱成本 | 自动装箱/拆箱有额外开销,存在缓存范围 |
| Arrays.asList | 返回类型 | 可变性 | 常见坑 |
|---|---|---|---|
| 传对象数组 | 固定大小 List | 不可 add/remove | 需 new ArrayList<>(...) 扩容 |
| 传基本类型数组 | List<int[]>(1 个元素) | 同上 | 使用 Integer[] 或流 boxed |
| 线程 | 平台线程(OS 线程) | 虚拟线程(JDK 21) |
|---|---|---|
| 数量级 | 千级 | 万到百万级 |
| 调度 | OS 调度 | JVM 调度,栈可挂起 |
| 适用 | CPU 密集/老代码 | I/O 密集/高并发服务 |
6. 最佳实践 / 常见坑
- 版本与发行版
- 生产优先 LTS(8/11/17/21);优选 Temurin、Corretto 等社区更新快的发行版。
- 新特性引入循序渐进,升级路径建议 8 → 11 → 17 → 21,配合测试与工具(jdeprscan、Enforcer)。
- 模块化兼容
- 避免使用内部 API(com.sun.*),必要时显式 --add-opens,并规划长期替换。
- 浮点精度
- 金融计算使用 BigDecimal(字符串构造),避免二进制小数误差;谨慎使用 equals(需考虑精度/舍入)。
- 装箱/拆箱
- 性能敏感代码避免频繁装箱;Map/Stream 中注意自动装箱成本;比较用 equals。
- 集合遍历删除
- 使用 Iterator.remove();或使用 removeIf/过滤产出新集合。
- Arrays 与集合互转
- Arrays.asList 返回固定大小 List;基本类型数组陷阱需 boxed。
- HashMap 容量
- 预估容量并设置合适初始容量(2 的幂,或使用构造函数/负载因子);注意 hash & (n-1) 的位运算优化。
- 异常信息与空值
- JDK 14+ 的 NPE 定位更清晰;对外接口用 Optional 或清晰的非空契约;入参 Objects.requireNonNull。
- 线程模型
- I/O 密集使用虚拟线程可显著提升吞吐;CPU 密集仍需关注并行度与绑定线程池大小;使用结构化并发提升可控性。
- AOT/Native Image
- 启动时间敏感场景考虑 AOT;注意反射/动态代理/资源配置,配合 tracing agent/手工元数据描述。
7. 面试高频问题(简答)
- 为什么 Java 能做到“跨平台”?
- 源码编译为平台无关的字节码;JVM 屏蔽 OS/硬件差异;不同平台提供各自的 JVM 实现。JVM 本身是平台相关的,字节码是平台无关的。
- JDK、JRE、JVM 的关系与区别?
- JDK ⊃ JRE ⊃ JVM。JDK 包含开发工具;JRE 提供运行环境;JVM 是执行引擎(类加载、解释/JIT、GC)。
- 自动装箱/拆箱有哪些坑?
- Integer 缓存范围 [-128,127] 导致 == 行为不一致;装箱/拆箱有性能与空指针风险(Integer 可能为 null 拆箱 NPE);比较统一用 equals。
- Java 8 → 17 升级的关键注意点?
- Java EE 模块移除(11),迁移至 Jakarta 或 Spring;模块化导致反射与内部 API 受限(--add-opens);默认 GC 变更(G1),需复核性能与参数;使用 jdeprscan 检测弃用 API。
- 虚拟线程适合什么场景?
- I/O 密集、高并发的服务端请求处理(百万级线程);与结构化并发结合可提升可观测性与错误处理易用性。CPU 密集不一定收益显著。
8. 总结(速记卡)
- 体系:JDK(开发)/JRE(运行)/JVM(执行)层层包含;编译生成字节码,运行期解释+JIT 混合执行。
- 版本:优选 LTS(8/11/17/21),关注模块化、移除的 Java EE、GC 默认变化与新并发(虚拟线程、结构化并发)。
- 语法:强静态类型;基本类型与包装类差异明确;装箱/拆箱与缓存范围要牢记;null/Optional/requireNonNull 规范使用。
- 运算:位运算在权限、哈希取模等高频出现;优先级和短路逻辑要清晰。
- 数组:固定长度、交错多维;Arrays 工具常用;数组/集合互转坑点需避免。
- 实战:版本管理(SDKMAN!/jenv)、升级工具(jdeprscan、Maven Enforcer)、并发模型(虚拟线程)、AOT(GraalVM)按需选型。
以上内容覆盖了「Java 语言基础与环境」的核心要点与实战建议,可作为复习与面试前的高效参考。