Java 基础 · 权威教程

Java 这门语言,一句话定位:"写一次,到处跑;写在企业里,跑在服务器上"。它不花哨、不炫技,胜在稳定、坑少、人多、资料多。本页以 JDK 21 LTS 为学习基线,新特性一路讲到 JDK 25 LTS(2025 年 9 月发布)。学完这一页,你能独立写出一个控制台小项目,并听得懂同事说"这个 NPE 是哪来的"。

1
第 1 节 · 最新版本与兼容性:选哪个 JDK,别被版本号绕晕
JDK Releases · LTS · Vendors · Migration

Oracle 从 2017 年(JDK 10 起)改成每 6 个月发一个新版本,但只有 LTS(Long-Term Su

进入本节 →
2
第 2 节 · 为什么学 Java,以及把环境跑起来
Why Java · JDK / JRE / JVM · Hello World

先泼一盆冷水:Java 不是"最好玩"的语言,语法啰嗦、样板代码多,写个空 main 都要整行 public stati

进入本节 →
3
第 3 节 · 基础语法:变量、类型、流程控制
Variables · Primitive Types · Flow Contr

这一章是"认字"阶段。Java 语法比 Python 啰嗦,但啰嗦有啰嗦的好处——类型明确,bug 提前暴露。下面把最常

进入本节 →
4
第 4 节 · 面向对象:Java 的灵魂
OOP · Encapsulation / Inheritance / Poly

Java 是纯面向对象语言(基本类型除外)。所谓"面向对象",人话就是:把现实世界的东西,抽象成"类"(图纸),再按图纸

进入本节 →
5
第 5 节 · 集合框架:装一堆数据的工具箱
Collections · List / Set / Map · Stream

数组能装东西,但长度定死了、不方便增删。集合框架(Collections Framework)就是 Java 提供的"动

进入本节 →
6
第 6 节 · 泛型:给集合贴个"内容标签"
Generics · Wildcards · Type Erasure

上面代码里的 List,那个尖括号里的 String 就是泛型。人话:泛型 = 告诉编译器"这个盒子里装

进入本节 →
7
第 7 节 · 异常处理:程序出错了怎么办
Exceptions · try-catch-finally · try-wit

程序跑挂了,不能直接蓝屏给用户看。异常(Exception)机制就是 Java 的"保险丝":出错时抛出一个异常对象,你

进入本节 →
8
第 8 节 · IO 与 NIO:读写文件
IO Streams · Files · NIO.2 Path / Files

程序要跟硬盘打交道:读配置文件、写日志、导出报表。Java 的 IO 体系乍一看类名一大堆,其实分两派:字节流(Inpu

进入本节 →
9
第 9 节 · 并发编程:让程序同时干多件事
Threads · ExecutorService · Locks · Virt

单核时代程序一次只干一件事,像一个厨师炒菜。多核时代你要开多个线程(Thread),像开多个灶台同时炒。但线程不是免费的

进入本节 →
10
第 10 节 · JVM 入门:Java 程序住在哪里
JVM Memory · Garbage Collection · Class

面试必问、线上排障必备。你不用手写 JVM,但要知道对象住哪、垃圾谁收、类怎么加载这三件事。理解了它们,遇到"内存溢出

进入本节 →
11
第 11 节 · 新特性速览:别停在 Java 8
Pattern Matching · sealed · Text Blocks

很多老教程还停在 Java 8(2014 年)。其实 JDK 21/25 加了一大批让代码更短、更安全的特性。下面挑五个

进入本节 →
12
第 12 节 · 小项目实战:学生成绩管理系统(控制台版)
Mini Project · Collections + IO + Thread

学了一堆零件,拼个真东西才算数。下面这个控制台小程序,把前面的集合、record、IO、Scanner 输入、多线程全串

进入本节 →
13
第 13 节 · 设计模式:七个最常用的套路
Design Patterns · Singleton / Factory /

设计模式不是玄学,是前人踩坑后总结的"代码怎么组织才好改"。别去背那 23 种,先把下面七个真正天天用的吃透:单例、工厂

进入本节 →
14
第 14 节 · 常见坑与最佳实践
Pitfalls & Best Practices

这一章是"老司机的血书"——每一条都是前人踩过、线上出过事的坑。收藏下来,写完代码对照一遍,能避开 80% 的低级错误。

进入本节 →
15
第 15 节 · 构建工具 Gradle:把依赖、编译、测试、打包串成
Gradle · Kotlin DSL · Version Catalog ·

前面 13 章你写的都是"单个 .java 文件 + javac"。可一旦项目里出现 十几个 jar 依赖、三个模块、还

进入本节 →
16
第 16 节 · JIT 与字节码深入:Java 到底是怎么变快的
javap · C1 / C2 · Inlining · Escape Anal

第 9 章讲了 JVM 的内存和 GC,这一章往下再钻一层:你的 .java 文件到底变成了什么,又是怎么从"慢吞吞的解

进入本节 →
17
第 17 节 · JPMS 模块系统:从 classpath 到 mo
module-info · requires / exports / opens

JDK 9 引入的模块系统(JPMS,JSR 376)是 Java 二十多年来最大的一次架构变动。它想解决一个很朴素的问

进入本节 →
18
第 18 节 · 注解处理器:在编译期就把代码写出来
Annotation Processing · APT · kapt / KSP

你写 @Data 就白得一堆 getter;写一个接口,MapStruct 就帮你生成实现类;写 @Inject,Dag

进入本节 →
练

综合练习题:把全书串起来

Final Exercises · 5 Problems

下面 5 道题把全书知识点串一遍。先自己写代码,再展开答案对。别一上来就看答案——看了等于没做。

综合练习(5 题)

1.(Stream 实战)给定 List<Employee>,每个 Employee 有 name、dept、salary。写一段 Stream:过滤出工资 > 10000 的员工,按部门分组,算出每个部门的平均工资。

查看答案与参考代码

答案:

Map<String, Double> avgByDept = employees.stream()
    .filter(e -> e.salary() > 10000)
    .collect(Collectors.groupingBy(
        Employee::dept,
        Collectors.averagingDouble(Employee::salary)
    ));

解析:groupingBy 第二个参数是 downstream collector,averagingDouble 直接算平均值,不用手动 reduce。

2.(并发实战)要并发下载 10 个网页,每个下载完打印标题。用 CompletableFuture 写,要求全部完成后再打印"全部下载完"。

查看答案与参考代码

答案:

List<CompletableFuture<String>> futures = urls.stream()
    .map(url -> CompletableFuture.supplyAsync(() -> downloadTitle(url), pool))
    .toList();

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
    .thenRun(() -> System.out.println("全部下载完"))
    .join();

解析:allOf 等所有任务完成,thenRun 接一个无参回调。注意 toArray(new CompletableFuture[0]) 是类型安全写法。

3.(JVM 排障)线上服务每隔几分钟卡 2 秒,日志里看到 Pause Full (System.gc())。最可能是什么原因?怎么修?

查看答案

答案:有代码或调用的库显式调用了 System.gc(),触发 Full GC。修法:① 启动参数加 -XX:+DisableExplicitGC 屏蔽;② 找到调用处(用 jstack 或 Arthas),改掉。别让业务代码随便触发 Full GC。

4.(泛型 + PECS)写一个方法 copy(List<? super Dog> dest, List<? extends Dog> src),把 src 里的狗拷到 dest。解释为什么参数这么写。

查看答案与参考代码

答案:

static void copyDogs(List<? super Dog> dest, List<? extends Dog> src) {
    for (Dog d : src) {      // src 是生产者,extends:往外读
        dest.add(d);          // dest 是消费者,super:往里写
    }
}

解析:src 可能是 List<Puppy>,只能用 extends Dog 读;dest 可能是 List<Animal>,用 super Dog 才能往里塞 Dog。这就是 PECS。

5.(综合)写一个程序:用虚拟线程并发处理 1000 个文件,每个文件读出来统计字数,最后汇总总字数。要求异常不中断其他任务。

查看答案与参考代码

答案:

try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
    List<Future<Long>> futures = files.stream()
        .map(f -> exec.submit(() -> {
            try { return Files.readString(f).length(); }
            catch (IOException e) { return 0L; }   // 单个失败不影响整体
        }))
        .toList();

    long total = 0;
    for (Future<Long> fu : futures) total += fu.get();
    System.out.println("总字数:" + total);
}

解析:虚拟线程 + try-catch 包住单个任务,一个文件读失败返回 0 不影响其他。try 结束自动等所有虚拟线程结束。