Java 从入门到精通:高级特性与底层原理实战全指南
前言
很多开发者写了几年 Java,却只停留在"会用 Spring 注解、会写 CRUD 接口"的阶段。一旦遇到框架源码、高并发瓶颈、内存泄漏、类加载冲突等问题,就无从下手。
本文以 Java 官方文档和最新 JEP(JDK Enhancement Proposal)为基准,深入讲解 Java 的高阶内容:泛型、反射、注解、并发编程、JVM 内存模型、垃圾回收、新特性(Java 21-26)。掌握这些,你才能从"会用框架"走向"理解底层"。
第一部分:泛型(Generics)
1.1 为什么需要泛型
没有泛型之前:
// JDK 5 之前:类型不安全
List list = new ArrayList();
list.add("Hello");
list.add(123); // 编译不报错
String s = (String) list.get(1); // 运行时报 ClassCastException
有了泛型之后:
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译直接报错,类型安全在编译期保证
1.2 泛型类与泛型方法
// 泛型类
public class Box<T> {
private T content;
public void put(T content) { this.content = content; }
public T get() { return content; }
}
// 泛型方法:类型参数在返回值之前声明
public static <T> T firstNonNull(T a, T b) {
return a != null ? a : b;
}
// 调用时类型推断
String result = firstNonNull(null, "default"); // 自动推断 T = String
1.3 通配符:? extends T 与 ? super T
这是 Java 泛型最 confusing 的部分,核心在于 PECS 原则:Producer Extends, Consumer Super。
// ? extends T:上界通配符(生产者,只读)
// 表示"某种 T 或 T 的子类"
List<? extends Number> numbers = new ArrayList<Integer>();
Number n = numbers.get(0); // ✅ 可以读(一定是 Number 或子类)
// numbers.add(1); // ❌ 编译错误:不知道具体是哪种 Number,不能写
// ? super T:下界通配符(消费者,只写)
// 表示"某种 T 或 T 的父类"
List<? super Integer> integers = new ArrayList<Number>();
// Number n = integers.get(0); // ❌ 编译错误:只能保证读出来是 Object
integers.add(1); // ✅ 可以写(Integer 一定是列表元素的子类)
// 无界通配符 ?
List<?> unknown = new ArrayList<String>();
Object obj = unknown.get(0); // 只能读为 Object
PECS 口诀:
如果参数化类型产生 T(你从里面读),用
? extends T如果参数化类型消费 T(你往里面写),用
? super T如果既读又写,不要用通配符,用精确的
T
// 标准库中的经典例子
public static <T extends Comparable<? super T>> void sort(List<T> list) {
// T 需要能被比较(产生 Comparable 值),所以用 extends
// Comparable 消费 T(compareTo 接收 T 参数),但 Comparable 本身用 ? super T
// 这样父类的 Comparator 也能用于子类排序
}
1.4 类型擦除(Type Erasure)
Java 的泛型是伪泛型,编译后类型参数会被擦除:
// 源码
Box<String> stringBox = new Box<>();
Box<Integer> intBox = new Box<>();
// 编译后等价于
Box stringBox = new Box();
Box intBox = new Box();
System.out.println(stringBox.getClass() == intBox.getClass()); // true
// 因为运行时都是 Box.class
类型擦除的副作用:
// ❌ 不能创建泛型数组
// List<String>[] array = new List<String>[10]; // 编译错误
// ❌ 不能做泛型类型的运行时检查
// if (obj instanceof List<String>) { } // 编译错误
// ✅ 可以用无界通配符检查
if (obj instanceof List<?>) { } // 合法
// ❌ 不能捕获泛型类型的异常
// catch (T e) { } // 编译错误
1.5 泛型约束
// 单一约束
public class Container<T extends Number & Comparable<T>> {
// T 必须同时是 Number 的子类且实现 Comparable<T>
}
// 多个约束用 where(Java 没有 where,用 & 连接)
// 注意:& 后面如果跟接口,可以有多个;如果跟类,只能有一个且必须在第一个
// 方法签名中的约束
public static <T extends Comparable<T>> T max(Collection<T> collection) {
return collection.stream().max(Comparator.naturalOrder()).orElse(null);
}
第二部分:反射(Reflection)
2.1 反射的核心概念
“反射是框架设计的灵魂。” 它允许程序在运行期获取任意类的信息(构造器、方法、字段、注解),并动态创建实例、调用方法、读写字段。
// 获取 Class 对象的三种方式
Class<?> clazz1 = Class.forName("com.example.User"); // 最常见
Class<?> clazz2 = User.class; // 编译期已知类型
Class<?> clazz3 = userInstance.getClass(); // 运行时获取
// 获取构造器
Constructor<?> constructor = clazz1.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // 跳过访问检查(访问 private 构造器)
User user = (User) constructor.newInstance("Alice", 25);
// 获取方法
Method method = clazz1.getDeclaredMethod("getName");
String name = (String) method.invoke(user);
// 获取字段
Field field = clazz1.getDeclaredField("age");
field.setAccessible(true);
int age = field.getInt(user);
field.setInt(user, 26); // 修改字段值
2.2 反射在框架中的应用
Spring 的依赖注入本质就是反射:
// Spring 内部简化伪代码
public Object createBean(Class<?> beanClass) throws Exception {
// 1. 找到合适的构造器
Constructor<?> constructor = beanClass.getDeclaredConstructor();
// 2. 创建实例
Object instance = constructor.newInstance();
// 3. 扫描字段上的 @Autowired 注解
for (Field field : beanClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
Object dependency = getBean(field.getType());
field.setAccessible(true);
field.set(instance, dependency); // 注入依赖
}
}
// 4. 调用 @PostConstruct 标注的方法
for (Method method : beanClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
method.setAccessible(true);
method.invoke(instance);
}
}
return instance;
}
动态代理(AOP 的基础):
public class LoggingProxy {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("Before: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After: " + method.getName());
return result;
}
);
}
}
// 使用
UserService realService = new UserServiceImpl();
UserService proxy = LoggingProxy.createProxy(realService);
proxy.createUser("Alice");
// 输出:
// Before: createUser
// After: createUser
2.3 反射的性能问题
反射调用比直接调用慢 10-50 倍,原因是:
方法查找需要安全检查
参数类型检查和装箱拆箱
访问控制检查(
setAccessible)
优化方案:
// 方式一:缓存 Method 对象
private static final Method cachedMethod;
static {
try {
cachedMethod = User.class.getMethod("getName");
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
// 后续调用直接使用缓存的 Method
String name = (String) cachedMethod.invoke(user);
// 方式二:使用 MethodHandle(JDK 7+,更快)
MethodHandle handle = MethodHandles.lookup()
.findVirtual(User.class, "getName", MethodType.methodType(String.class));
String name = (String) handle.invoke(user);
// 方式三:使用 VarHandle(JDK 9+,字段访问最快)
// 适合字段级别的高性能反射访问
2.4 JDK 26 的变化:Final Will Actually Mean Final
JEP 500 是 JDK 26 的重大变更:禁止通过反射修改 private final 字段。
// JDK 26 之前
Field field = User.class.getDeclaredField("id");
field.setAccessible(true);
field.set(user, "hacked-id"); // 可以修改 private final 字段
// JDK 26 起
field.set(user, "hacked-id"); // 抛出 IllegalAccessException
// private final 字段一旦初始化,就真的不可变了
这对序列化库(Jackson、Gson)和框架有影响,需要使用官方提供的替代方案(ObjectInputStream 的反序列化机制、MethodHandle 等)。
第三部分:注解(Annotations)
3.1 注解的本质
注解是 Java 的元数据机制,本身不改变代码行为,必须配合处理器(注解处理器、反射、字节码工具)才能发挥作用。
// 内置注解
@Override // 编译期检查方法是否正确重写
@Deprecated // 标记废弃,调用处会警告
@SuppressWarnings // 压制编译器警告
@SafeVarargs // 标记可变参数方法类型安全
@FunctionalInterface // 编译期检查是否为合法函数式接口
// 元注解(用来定义注解的注解)
@Target(ElementType.METHOD) // 该注解可以用在什么地方
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时(可反射读取)
@Documented // 包含在 Javadoc 中
@Inherited // 子类自动继承该注解
@Repeatable // 允许在同一位置重复使用
3.2 自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
String value() default ""; // 默认值
LogLevel level() default LogLevel.INFO;
boolean recordArgs() default true;
}
public enum LogLevel { DEBUG, INFO, WARN, ERROR }
3.3 运行时注解处理(反射方式)
public class LogExecutionAspect {
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
LogExecution log = method.getAnnotation(LogExecution.class);
if (log != null && log.recordArgs()) {
System.out.println("[LOG] " + method.getName() +
" args: " + Arrays.toString(args));
}
try {
Object result = method.invoke(target, args);
return result;
} catch (Exception e) {
if (log != null && log.level() == LogLevel.ERROR) {
System.err.println("[ERROR] " + method.getName() + ": " + e.getMessage());
}
throw e;
}
}
);
}
}
// 使用
@LogExecution(recordArgs = true, level = LogLevel.ERROR)
public void deleteUser(String userId) {
// 业务逻辑
}
3.4 编译期注解处理器(APT)
编译期注解处理器在 javac 编译阶段运行,不依赖反射,零运行时开销。Lombok、Dagger、MapStruct 都基于此。
// 编译期注解处理器(简化示例)
@SupportedAnnotationTypes("com.example.AutoToString")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class AutoToStringProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(AutoToString.class)) {
// 在编译期生成 toString() 方法的字节码/源码
generateToStringMethod(element);
}
return true; // 注解已处理
}
}
运行时注解 vs 编译期注解:
运行时注解:需要
@Retention(RUNTIME),通过反射读取,有性能开销编译期注解:在编译期生成新代码(APT),零运行时开销,但实现复杂
现代框架趋势:能用编译期就不用运行时(MapStruct 替代 ModelMapper,Lombok 替代 getter/setter)
3.5 重复注解
@Repeatable(Schedules.class)
@interface Schedule {
String day();
}
@interface Schedules {
Schedule[] value();
}
// 使用
@Schedule(day = "Monday")
@Schedule(day = "Wednesday")
@Schedule(day = "Friday")
public void meeting() { }
// 读取
Schedule[] schedules = method.getAnnotationsByType(Schedule.class);
第四部分:并发编程
4.1 线程基础
// 方式一:继承 Thread
new Thread() {
@Override public void run() { System.out.println("Running"); }
}.start();
// 方式二:实现 Runnable(推荐)
new Thread(() -> System.out.println("Running")).start();
// 方式三:Callable + Future(有返回值)
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "Done";
});
String result = future.get(); // 阻塞等待结果
4.2 java.util.concurrent 核心组件
线程池:
// 推荐的线程池创建方式(手动控制参数,避免 OOM)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // corePoolSize:核心线程数
8, // maximumPoolSize:最大线程数
60, TimeUnit.SECONDS, // 空闲线程存活时间
new ArrayBlockingQueue<>(100), // 任务队列
new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "worker-" + counter.incrementAndGet());
t.setDaemon(false);
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者线程执行
);
为什么不推荐 Executors 的快捷方法:
Executors.newFixedThreadPool()→ 无界队列,可能 OOM
Executors.newCachedThreadPool()→ 无界线程数,可能 OOM
Executors.newSingleThreadExecutor()→ 无界队列,可能 OOM阿里巴巴 Java 开发手册明确禁止使用
Executors创建线程池。
CompletableFuture:
// 链式异步编排
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 异步任务 1
return fetchDataFromApi();
}).thenApply(data -> {
// 处理结果
return data.toUpperCase();
}).thenAccept(result -> {
// 消费结果
System.out.println(result);
}).exceptionally(ex -> {
// 异常处理
System.err.println("Error: " + ex.getMessage());
return null;
});
// 多个 CompletableFuture 组合
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
// 全部完成后执行
CompletableFuture.allOf(f1, f2).thenRun(() -> {
String combined = f1.join() + " " + f2.join();
System.out.println(combined); // "Hello World"
});
// 任一完成后执行
CompletableFuture.anyOf(f1, f2).thenAccept(result -> {
System.out.println("First completed: " + result);
});
4.3 并发集合
4.4 锁机制
synchronized vs ReentrantLock:
// synchronized:内置锁,JVM 级别优化(偏向锁 → 轻量级锁 → 重量级锁)
public synchronized void increment() {
count++;
}
// ReentrantLock:API 级别锁,更灵活
private final ReentrantLock lock = new ReentrantLock();
public void doSomething() {
lock.lock();
try {
// 临界区
} finally {
lock.unlock(); // 必须在 finally 中释放
}
}
// ReentrantLock 高级功能
ReentrantLock lock = new ReentrantLock();
// 尝试获取锁(不阻塞)
if (lock.tryLock()) {
try { /* ... */ } finally { lock.unlock(); }
}
// 超时等待
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try { /* ... */ } finally { lock.unlock(); }
}
// 公平锁(按请求顺序分配)
ReentrantLock fairLock = new ReentrantLock(true);
ReadWriteLock:
ReadWriteLock rwLock = new ReentrantReadWriteLock();
public String read() {
rwLock.readLock().lock();
try {
return data; // 多个读线程可并发
} finally {
rwLock.readLock().unlock();
}
}
public void write(String newData) {
rwLock.writeLock().lock(); // 写锁互斥
try {
this.data = newData;
} finally {
rwLock.writeLock().unlock();
}
}
4.5 CAS 与原子类
CAS(Compare-And-Swap)是一种无锁并发机制,通过硬件指令实现:
// AtomicInteger:基于 CAS 的原子整数
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // 原子自增,线程安全
counter.compareAndSet(5, 10); // 如果当前值是5,则设为10
counter.getAndUpdate(x -> x * 2); // 原子更新
// 原子引用
AtomicReference<User> userRef = new AtomicReference<>();
userRef.compareAndSet(null, new User("Alice"));
// LongAdder:高并发下比 AtomicLong 更快(分段计数)
LongAdder longAdder = new LongAdder();
longAdder.increment();
long total = longAdder.sum(); // 求总和
4.6 虚拟线程(Virtual Threads)—— Java 21+
虚拟线程是 Java 21 引入的革命性特性,也是 JDK 25 进一步完善的核心功能。它将线程映射到操作系统线程的方式从"一对一"变为"多对一",一个 OS 线程可以运行成千上万个虚拟线程。
// 传统线程:每个线程绑定一个 OS 线程,内存开销大(约 1MB/线程)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 虚拟线程:轻量级,每个仅几百字节,可以轻松创建百万级
IntStream.range(0, 1_000_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1)); // 挂起时不占用 OS 线程
return i;
});
});
} // 自动等待所有虚拟线程完成
虚拟线程 vs 平台线程:
生产环境建议(2026 年):
JDK 25 对虚拟线程做了进一步优化,生产环境已经可以大规模使用
虚拟线程最适合 I/O 密集型应用(Web 服务、数据库调用、HTTP 请求)
CPU 密集型任务仍然建议使用平台线程池
不要使用线程池来管理虚拟线程,让调度器自动调度
4.7 结构化并发(Structured Concurrency)—— Java 25/26 Preview
结构化并发让多线程代码像结构化代码一样清晰:
// JEP 525(Java 26 第六次预览)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 启动子任务
Subtask<User> userTask = scope.fork(() -> findUser(userId));
Subtask<Order> orderTask = scope.fork(() -> fetchOrder(orderId));
// 等待所有子任务完成,任一失败则取消其余
scope.join().throwIfFailed();
// 处理结果
User user = userTask.get();
Order order = orderTask.get();
return new Response(user, order);
} // 作用域结束,所有子任务保证已完成或已取消
第五部分:JVM 内存模型与垃圾回收
5.1 JVM 内存结构
JVM 运行时数据区
├── 线程私有
│ ├── 程序计数器(PC Register):当前线程执行的字节码行号
│ ├── 虚拟机栈(JVM Stack):方法调用栈帧(局部变量表、操作数栈)
│ └── 本地方法栈(Native Method Stack):Native 方法调用
├── 线程共享
│ ├── 堆(Heap):对象实例、数组(GC 的主要区域)
│ │ ├── 新生代(Young Generation)
│ │ │ ├── Eden 区(新对象分配)
│ │ │ └── Survivor 区(S0/S1,Minor GC 后存活对象)
│ │ └── 老年代(Old Generation)
│ │ └── 经历多次 GC 后仍然存活的对象
│ └── 元空间(Metaspace):类元数据、常量池、方法信息
│ └── JDK 8 替代了永久代(PermGen),使用本地内存
└── 直接内存(Direct Memory):NIO 的 ByteBuffer.allocateDirect()
5.2 对象的生命周期
// 1. 类加载阶段:JVM 将 .class 文件加载到元空间
Class<?> clazz = Class.forName("com.example.User");
// 2. 对象创建:new 指令在堆上分配内存
User user = new User("Alice"); // Eden 区分配
// 3. 对象访问:通过引用访问对象
String name = user.getName();
// 4. 对象可达性分析:GC 从 GC Roots 开始追踪
// - GC Roots 包括:栈帧中的局部变量、静态变量、JNI 引用等
// - 无法从 GC Roots 到达的对象 → 可回收
// 5. 对象晋升:Eden → Survivor → Old Generation
// - 默认经过 15 次 Minor GC 后晋升到老年代(-XX:MaxTenuringThreshold)
// - 大对象直接进入老年代(-XX:PretenureSizeThreshold)
5.3 垃圾回收算法
5.4 主流垃圾收集器
G1 Garbage Collector(JDK 9+ 默认):
G1 将堆划分为多个大小相等的 Region,不再区分新生代/老年代的物理边界。
优势:可预测的停顿时间(-XX:MaxGCPauseMillis=200)
# 启用 G1 并配置参数
java -XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1HeapRegionSize=4m \
-Xms4g -Xmx4g \
-jar app.jar
ZGC(JDK 15+ 生产可用,JDK 21 分代 ZGC):
ZGC 使用染色指针和读屏障技术,停顿时间不超过 1ms。
JDK 21 引入分代 ZGC(Generational ZGC),结合分代回收优势,吞吐量大幅提升。
# 启用 ZGC(JDK 21+)
java -XX:+UseZGC -Xms4g -Xmx4g -jar app.jar
# 分代 ZGC(默认启用)
java -XX:+UseZGC -XX:+ZGenerational -Xms4g -Xmx4g -jar app.jar
5.5 内存泄漏排查
# 1. OOM 时自动 dump 堆
java -XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/heapdump.hprof \
-jar app.jar
# 2. 手动触发 dump
jmap -dump:format=b,file=heap.hprof <pid>
# 3. 分析 dump 文件
# 使用 MAT(Memory Analyzer Tool)、JProfiler、YourKit 等工具
# 4. 查看 GC 日志
java -Xlog:gc*:file=gc.log:time,uptime,level,tags \
-jar app.jar
# 5. 实时监控
jstat -gcutil <pid> 1000 # 每秒输出 GC 统计
jcmd <pid> GC.run # 手动触发 GC
常见内存泄漏场景:
// 场景一:静态集合未清理
public class Cache {
private static final Map<String, Object> cache = new HashMap<>();
public static void put(String key, Object value) {
cache.put(key, value); // 永远不删除,最终 OOM
}
}
// 场景二:未关闭资源
InputStream is = new FileInputStream("file.txt");
// is.close(); // 忘记关闭
// 正确做法:
try (InputStream is = new FileInputStream("file.txt")) {
// try-with-resources 自动关闭
}
// 场景三:ThreadLocal 未清理
private static final ThreadLocal<Context> context = new ThreadLocal<>();
context.set(new Context());
// context.remove(); // 线程池复用时,ThreadLocal 不会自动清理
5.6 JIT 编译
JVM 通过 JIT(Just-In-Time)编译器将热点代码编译为机器码:
// C1(Client Compiler):快速编译,优化较少,适合启动阶段
// C2(Server Compiler):深度优化,编译慢,适合运行阶段
// GraalVM AOT:提前编译为本地机器码,启动即高性能
// 查看 JIT 编译信息
java -XX:+PrintCompilation -jar app.jar
// 输出: 123 456 3 java.lang.String::hashCode (55 bytes)
// ^ ^ ^ ^
// 编译ID 编译方法级别 编译字节数
// 方法内联:JVM 自动将小方法内联到调用点
// -XX:MaxInlineSize=35 // 最大内联字节码大小(默认 35)
// -XX:FreqInlineSize=325 // 频繁调用方法的最大内联大小
5.7 AOT 缓存(JDK 26 新特性)
JDK 26 引入了 AOT Caching,支持所有 GC 类型:
# 生成 AOT 缓存
java -XX:AOTMode=store -jar app.jar
# 下次启动时自动加载 AOT 缓存,跳过 JIT 预热阶段
# 启动时间显著缩短,特别适合容器化部署
第六部分:类加载机制
6.1 类加载过程
1. 加载(Loading):将 .class 文件字节码读入 JVM,创建 Class 对象
2. 链接(Linking)
├── 验证(Verification):字节码合法性检查
├── 准备(Preparation):为 static 字段分配内存,赋默认值
└── 解析(Resolution):符号引用转为直接引用
3. 初始化(Initialization):执行 static 代码块和 static 字段初始值
6.2 双亲委派模型
Bootstrap ClassLoader(启动类加载器)
├── 加载 Java 核心类库(java.lang.*, java.util.* 等)
├── C++ 实现,Java 代码无法引用
│
├──> Extension ClassLoader(扩展类加载器)
│ ├── 加载 ext 目录中的类
│ ├── sun.misc.Launcher$ExtClassLoader
│ │
│ └──> Application ClassLoader(应用类加载器)
│ ├── 加载 classpath 中的类
│ ├── sun.misc.Launcher$AppClassLoader
│ │
│ └──> Custom ClassLoader(自定义类加载器)
双亲委派原则:当类加载器收到加载请求时,先委托给父类加载器,只有父类加载器无法加载时,才尝试自己加载。
破坏双亲委派的场景:
SPI 机制(Service Provider Interface):JDBC 驱动、JNDI 等通过
Thread.getContextClassLoader()加载Tomcat:每个 Web 应用独立 ClassLoader,优先加载应用自己的类
OSGi:模块化热部署,每个 Bundle 独立 ClassLoader
6.3 自定义类加载器
public class MyClassLoader extends ClassLoader {
private final Path classPath;
public MyClassLoader(Path classPath) {
super(MyClassLoader.class.getClassLoader()); // 父加载器
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 父加载器先加载(双亲委派)
try {
return getParent().loadClass(name);
} catch (ClassNotFoundException e) {
// 父加载器无法加载,自己加载
}
try {
Path path = classPath.resolve(name.replace('.', '/') + ".class");
byte[] bytes = Files.readAllBytes(path);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
}
第七部分:Java 新特性(Java 21-26)
7.1 Record 类(Java 16+)
不可变数据类,自动生成 equals、hashCode、toString、构造器和 getter:
// 传统方式需要几十行代码
public record User(String name, int age, String email) {
// 紧凑构造函数(用于参数校验)
public User {
if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
}
// 自定义方法
public String greeting() {
return "Hello, " + name;
}
}
// 使用
User user = new User("Alice", 25, "alice@example.com");
System.out.println(user.name()); // getter 是 name() 而不是 getName()
System.out.println(user.greeting()); // "Hello, Alice"
7.2 Pattern Matching(模式匹配)
instanceof 模式匹配(Java 16+):
// 旧写法
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// 新写法
if (obj instanceof String s) {
System.out.println(s.length()); // s 在 if 块中自动可用
}
switch 模式匹配(Java 21+):
// 旧写法
String formatted = switch (obj.getClass().getSimpleName()) {
case "Integer" -> "int: " + ((Integer) obj).intValue();
case "Long" -> "long: " + ((Long) obj).longValue();
case "String" -> "string: " + ((String) obj);
default -> "unknown";
};
// 新写法
String formatted = switch (obj) {
case Integer i -> "int: " + i.intValue();
case Long l -> "long: " + l.longValue();
case String s -> "string: " + s;
case null -> "null";
default -> "unknown: " + obj;
};
Record 模式匹配(Java 21+):
sealed interface Shape permits Circle, Rectangle, Triangle { }
record Circle(double radius) implements Shape { }
record Rectangle(double width, double height) implements Shape { }
record Triangle(double base, double height) implements Shape { }
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> 0.5 * t.base() * t.height();
}; // 编译器保证穷尽,无需 default
}
7.3 Sealed Classes(密封类,Java 17+)
限制哪些类可以继承当前类:
// 密封类:只允许指定的类继承
public sealed interface Result permits Success, Failure { }
public final class Success implements Result {
private final Object data;
public Success(Object data) { this.data = data; }
public Object getData() { return data; }
}
public final class Failure implements Result {
private final Throwable error;
public Failure(Throwable error) { this.error = error; }
public Throwable getError() { return error; }
}
// 不能有其他类实现 Result
// public class Unknown implements Result { } // 编译错误
密封类 + 模式匹配是 Kotlin 的
sealed class和 Rust 的enum的 Java 版本。
7.4 String Templates(字符串模板,Java 21+ Preview)
String name = "Alice";
int age = 25;
// 旧写法
String info = String.format("Name: %s, Age: %d", name, age);
// 新写法(STR 是内置模板处理器)
String info = STR."Name: \{name}, Age: \{age}";
// 可以自定义模板处理器
String json = JSON."""
{
"name": "\{name}",
"age": \{age}
}
""";
7.5 外部函数与内存 API(Foreign Function & Memory API,Java 22+)
替代 JNI 的安全高效方式,用于调用 C 代码和访问堆外内存:
// 调用 C 标准库的 strlen
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment cString = arena.allocateUtf8String("Hello, World!");
long len = (long) strlen.invoke(cString);
System.out.println("Length: " + len); // 12
}
7.6 Java 25(LTS)核心特性
2025 年 9 月发布的 LTS 版本包含 18 个 JEP:
7.7 Java 26 核心特性
2026 年 3 月发布的非 LTS 版本:
第八部分:设计模式与最佳实践
8.1 Builder 模式(Lombok 辅助)
// Lombok 方式
@Builder
public class User {
private String name;
private int age;
private String email;
}
// 使用
User user = User.builder()
.name("Alice")
.age(25)
.email("alice@example.com")
.build();
8.2 策略模式(函数式替代)
// 传统策略模式
interface DiscountStrategy {
double applyDiscount(double price);
}
class NoDiscount implements DiscountStrategy {
public double applyDiscount(double price) { return price; }
}
class PercentageDiscount implements DiscountStrategy {
private final double percentage;
public PercentageDiscount(double p) { this.percentage = p; }
public double applyDiscount(double price) { return price * (1 - percentage); }
}
// 函数式替代(推荐)
public class PriceCalculator {
public static double apply(double price, UnaryOperator<Double> strategy) {
return strategy.apply(price);
}
}
// 使用
double finalPrice = PriceCalculator.apply(100.0, p -> p * 0.9); // 9折
8.3 工厂模式 + Record
public sealed interface Message permits TextMessage, ImageMessage, VideoMessage {
String content();
}
public record TextMessage(String text) implements Message {
public String content() { return text; }
}
public record ImageMessage(String url, int width, int height) implements Message {
public String content() { return "[Image: " + url + "]"; }
}
public record VideoMessage(String url, int duration) implements Message {
public String content() { return "[Video: " + url + "]"; }
}
// 工厂方法
public class MessageFactory {
public static Message create(String type, String content) {
return switch (type) {
case "text" -> new TextMessage(content);
case "image" -> new ImageMessage(content, 800, 600);
case "video" -> new VideoMessage(content, 120);
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
}
}
8.4 Optional 的正确用法
// ❌ 错误用法
Optional<User> optionalUser = userRepository.findById(id);
if (optionalUser.isPresent()) {
User user = optionalUser.get();
System.out.println(user.getName());
}
// ✅ 正确用法
userRepository.findById(id)
.ifPresent(user -> System.out.println(user.getName()));
// 有默认值
String name = userRepository.findById(id)
.map(User::getName)
.orElse("Unknown");
// 抛异常
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
// 链式操作
Optional<String> email = userRepository.findById(id)
.filter(u -> u.isActive())
.map(User::getEmail)
.filter(e -> e.endsWith("@example.com"));
第九部分:常见问题 FAQ
Q1: Java 泛型为什么用类型擦除而不是具体化?
为了向后兼容。Java 5 引入泛型时,需要保证泛型代码与 JDK 1.4 及之前的二进制兼容。如果像 C++ 的模板那样在编译期生成具体类型的代码,就会破坏这一兼容性。C# 的泛型是具体化的(运行期保留类型信息),但 C# 没有历史包袱。
Q2: 什么时候用虚拟线程,什么时候用传统线程池?
Q3: JVM 默认使用哪个垃圾收集器?
JDK 8:Parallel GC
JDK 9+:G1 GC
JDK 21+ 大内存场景:考虑分代 ZGC
Q4: 内存泄漏如何定位?
开启
-XX:+HeapDumpOnOutOfMemoryError使用 MAT 分析 dump 文件,查找 Dominator Tree
看哪些对象占用最多内存,追踪其引用链
常见原因:静态集合、未关闭的连接、ThreadLocal 未清理、监听器未注销
Q5: JDK 26 的 “Final Will Actually Mean Final” 会影响哪些库?
所有使用反射修改 private final 字段的库都会受影响,包括:
Jackson / Gson 的字段注入反序列化
Mockito 的 Mock 对象创建
某些序列化/ORM 框架
这些库已经适配了新的 API(使用 MethodHandle、Unsafe 的替代方案或新的序列化协议)。
Q6: Record 类可以继承其他类吗?
不能。Record 隐式继承 java.lang.Record,Java 不支持多重继承。但 Record 可以实现接口。
Q7: 什么时候升级到 Java 21/25?
如果你还在用 Java 8:强烈建议升级到 Java 21(LTS)或 Java 25(最新 LTS)
Java 21 引入了虚拟线程、模式匹配、Record 模式匹配、String Templates 等大量特性
Spring Boot 3.x 要求最低 Java 17,Spring Boot 4.x 将要求 Java 21
Java 8 → Java 21 的迁移路径已经很成熟,通常不需要改代码
附录:JVM 调优参数速查
# 内存设置
-Xms2g # 初始堆大小
-Xmx4g # 最大堆大小
-XX:MetaspaceSize=256m # 元空间初始大小
-XX:MaxMetaspaceSize=512m # 元空间上限
# GC 选择
-XX:+UseG1GC # 使用 G1(JDK 9+ 默认)
-XX:+UseZGC # 使用 ZGC
-XX:MaxGCPauseMillis=200 # G1 最大停顿目标(毫秒)
# 调试
-XX:+HeapDumpOnOutOfMemoryError # OOM 时自动 dump
-XX:HeapDumpPath=/tmp/heap.hprof # dump 文件路径
-Xlog:gc*:file=gc.log # GC 日志(JDK 9+)
-XX:+PrintGCDetails # 详细 GC 日志(JDK 8)
# 性能
-XX:+UseStringDeduplication # G1 字符串去重
-XX:+AlwaysPreTouch # 启动时预分配内存(减少首次 GC 延迟)
-XX:MaxInlineSize=35 # 方法内联阈值
总结
Java 的高级特性可以分为三个层次:
语言层:泛型、反射、注解、Record、模式匹配、密封类 —— 让代码更安全、更简洁
并发层:线程池、CompletableFuture、虚拟线程、结构化并发 —— 让程序充分利用多核和 I/O
JVM 层:内存模型、垃圾回收、类加载、JIT 编译 —— 理解运行时行为,调优排查
从 Java 8 到 Java 26,语言本身在快速进化。虚拟线程、模式匹配、Record、密封类等特性正在改变我们写 Java 的方式。理解这些高级特性,不仅能让你写出更好的代码,也能让你在使用 Spring、MyBatis 等框架时不再"知其然而不知其所以然"。