Java 从 1995 年一路走到今天,早已不只是一门编程语言,而是企业级软件世界里的一整套基础设施:银行的核心账务、电商的下单链路、物流的调度系统、大数据的计算引擎,很多都跑在它上面。

这篇文章想做三件事:用大白话讲清 Java 在 2026 年长什么样,把从零到能独立做项目的学习顺序排清楚,再把那些只有踩过才知道的坑提前标出来。文中涉及的版本信息都对照过 Oracle 官方路线图和 2026 年最新的发布公告,代码示例也都在本机真实编译运行过,可以直接复制。

信息核验时点:2026 年 9 月(当前最新 LTS 为 2025-09-16 发布的 JDK 25,最新发布版 JDK 27 于 2026-09-15 发布)


本文要点
  • 一句话定义:Java 是 1995 年发布、运行在 JVM 上的编程语言,如今已是银行、电商、物流与大数据系统背后的一整套基础设施。
  • 版本节奏:每年 3 月和 9 月各发一个特性版本,每隔两年定一个 LTS;Oracle 明确下一个 LTS 是 2027 年 9 月的 JDK 29。
  • 怎么选版本:新项目直接上 JDK 25 这个 LTS,保守团队先在 JDK 21 稳一稳,尝鲜可以用 JDK 27,但别把预览特性带进生产。
  • JDK 25 的关键变化:紧凑源文件与实例主方法、紧凑对象头、作用域值、模块导入声明,入门门槛断崖式下降。
  • 四个关键词:虚拟线程(JDK 21 转正)、钉住问题、作用域值替代 ThreadLocal、模块化与 AOT 原生镜像。
  • 生态与用途:Spring Boot 是后端事实标准,MySQL、PostgreSQL、Redis、Kafka、Flink 构成主力链路;AI 方向上 Java 负责把模型接进企业系统。
  • 短板与现实:初级岗位竞争激烈;虚拟线程只对 I/O 密集型有效,瓶颈会前移到连接池等下游资源;安卓端 Kotlin 已成首选语言。

Java 在 2026 年的位置

三十年,一条慢变量曲线

1991 年,Sun 公司内部启动了一个叫 Oak 的项目,原本打算用在消费电子上;1995 年它改名 Java 正式对外发布,一次编写、到处运行的口号配合浏览器里的 Applet 迅速蹿红。2006 年 Java 开源,2010 年 Oracle 收购 Sun 接手这门语言,2017 年 JDK 9 带来模块化并确立了每半年一个版本的发布节奏,从 2018 年起又加上了长期支持版本(LTS)制度。

三个改不掉的优势

生态厚度:Maven 中央仓库里的 Java 构件数以百万计,几乎所有能想到的场景都有成熟轮子可用,不用从轮子开始造。

人才与存量:大量企业系统、招聘岗位和培训体系围绕 Java 建立,替换成本高得离谱,这反过来让它极稳。

JVM 是平台而不只是语言:Kotlin、Scala、Groovy、Clojure 都跑在 JVM 上,一个运行时撑起多种编程范式,也撑起了大数据与中间件的大半江山。

版本节奏:半年一版,两年一个 LTS

这条规则值得背下来:每年 3 月和 9 月各发一个特性版本,编号加一,只在下一个版本发布前收到更新,大约半年;每隔两年挑一个版本定为 LTS,享受以年计的长期支持。Oracle 官方已明确表态,未来的 LTS 保持两年一个节奏,下一个是 2027 年 9 月的 JDK 29。

版本发布时间类型当前定位
JDK 82014-03LTS历史包袱,Oracle 免费更新早已停止,只适合遗留系统
JDK 112018-09LTS存量过渡,厂商支持窗口陆续在 2026 至 2027 年收尾
JDK 172021-09LTS保守项目的安全底线
JDK 212023-09LTS虚拟线程正式落地,仍是大量生产系统的主力
JDK 252025-09-16LTS当前最新 LTS,新项目的推荐基线
JDK 262026-03特性版已被取代,不再收到更新
JDK 272026-09-15特性版当前最新发布版,免费更新至 2027-03
JDK 292027-09(计划)LTS下一个 LTS

2026 年版本地图:从 JDK 8 到 JDK 27

JDK 25 LTS:新的推荐基线

JDK 25 于 2025 年 9 月 16 日发布,共 18 项 JEP,是继 JDK 21 之后的第一个新 LTS,也是当下新手最合适的入坑版本。几个值得记住的点:

  • JEP 512 紧凑源文件与实例主方法:可以写不带类的源文件,主方法也不必是静态的,入门门槛断崖式下降。
  • JEP 519 紧凑对象头:64 位 JVM 上对象头从 96 位压到 64 位,堆占用更小。它是正式特性,但要显式加上 -XX:+UseCompactObjectHeaders 才生效,到 JDK 27 才变成默认。
  • JEP 506 作用域值:为虚拟线程时代准备的上下文传递机制,用来替代 ThreadLocal。
  • JEP 511 模块导入声明:一句 import module 就能引入整块 JDK API。
  • JEP 513 灵活构造器体:调用父类构造器之前可以写校验和字段初始化语句了。
  • JEP 521 分代 Shenandoah、JEP 503 移除 32 位 x86 移植、JEP 514 与 515 的 AOT 命令行简化与方法分析、JEP 510 密钥派生函数 API。

JDK 27:两项什么都不用做就受益的默认变更

JDK 27 于 2026 年 9 月 15 日正式发布,对应 JSR 402,共 9 项 JEP,其中 4 项预览、1 项孵化。最实在的两处改动是默认值变了:

  • JEP 523:G1 成为所有环境的默认垃圾收集器。以前小内存、少核环境会自动回退到 Serial GC,容易在数据量上来之后出现长时间停顿;现在无论容器规格多大都默认 G1,不同环境的 GC 行为更一致。
  • JEP 534:紧凑对象头成为默认。不写任何参数,升级即省内存,堆占用可减少约 20%。

另外三点也值得知道:JEP 527 给 TLS 1.3 引入后量子混合密钥交换,默认启用,使用 javax.net.ssl 的应用无需改代码;JEP 536 让 JFR 在数据离开进程之前对命令行参数、环境变量等做脱敏;预览特性里,JEP 532 走到第五次预览,基本类型也能参与模式匹配与 switch,JEP 533 结构化并发第七次预览,JEP 531 惰性常量第三次预览,JEP 538 PEM 编码第三次预览,JEP 537 向量 API 第十二次孵化。

该选哪个版本?

一句话建议:新项目直接上 JDK 25 这个 LTS,保守团队先在 JDK 21 上稳一稳,想尝鲜用 JDK 27 但别把预览特性带进生产。预览特性必须加 --enable-preview 才能编译,而且官方明确不承诺跨版本兼容,拿它写业务代码等于给自己埋雷。

动手第一步:把环境搭起来

选发行版,顺手把授权看清楚

Java 平台规范是开放的,但各家打包的 JDK 授权并不相同:

  • Oracle JDK:从 JDK 25 起在 NFTC 条款下可免费用于生产;而 JDK 8、11、17 的 Oracle 构建,免费授权只覆盖个人、开发等用途,商用需要订阅。
  • 社区发行版:Eclipse Temurin、Amazon Corretto、Microsoft Build of OpenJDK、Azul Zulu、Alibaba Dragonwell 等,长期免费。

学习阶段建议直接用 Eclipse Temurin 或 Amazon Corretto,省心。

安装与验证

Linux 和 macOS 推荐用 SDKMAN 管理多版本:

curl -s "https://get.sdkman.io" | bash
sdk list java            # 查看可选版本号
sdk install java 25.0.1-tem

Windows 用户到 Adoptium 官网下载 Temurin 版 JDK 的图形安装包,安装时勾选设置 JAVA_HOME 即可。Temurin、Corretto 等发行版与 Oracle JDK 功能一致,均为免费使用。

也可以手动把解压好的 JDK 放到固定目录并配置环境变量:

export JAVA_HOME=/opt/jdk-25
export PATH=/opt/jdk-25/bin:/usr/local/bin:/usr/bin:/bin

java -version
javac -version

顺便验一下自己装的 JDK 是不是被人裁过的精简包:

# 看发行商是不是可信来源
java -XshowSettings:properties -version 2>&1 | grep java.vendor

# 看模块目录是否完整,正经的 LTS 版本通常有几十个 .jmod 文件
ls /opt/jdk-25/jmods/ | wc -l

如果 jmods 目录里只有零星几个文件,说明这个包被裁过,后面用 jlink 裁剪运行时或者做模块化测试时,就可能报找不到模块。

第一个程序:一整套仪式感没了

JDK 25 起,一个能跑的 Java 程序可以短到这种程度:

// Hello.java
void main() {
    IO.println("Hello, Java 25!");
}

保存之后用 java Hello.java 直接运行,不需要先 javac 编译,不需要类名与文件名一致,也不需要写一长串 public static void main(String[] args)。当然,传统写法照样能用:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, Java!");
    }
}

构建工具:Maven 还是 Gradle?

小项目手敲编译命令还行,一旦依赖超过三五个就必须上构建工具。Maven 胜在约定统一、资料多;Gradle 胜在灵活、构建脚本可编程,也是 Android 和 Spring 官方新项目的默认选择。用 Maven 时记得把编译版本钉死在 pom.xml 里,避免本地能编、上线报错的尴尬:

<properties>
    <maven.compiler.release>25</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

语言骨架:从能读到能写

类型与变量

Java 是静态强类型语言,变量必须先声明类型,JDK 10 起局部变量可以用 var 让编译器推断。八种基本类型 byte、short、int、long、float、double、char、boolean 与它们的包装类要分清楚,这是后面理解自动装箱的基础。

public class Basics {
    public static void main(String[] args) {
        int count = 42;
        double price = 19.9;
        boolean ok = true;
        var name = "Java";              // 局部变量类型推断,JDK 10 起可用

        String text = """
                文本块从 JDK 15 起正式可用,
                不用再写一长串换行符拼接。
                """;

        System.out.printf("%s %d %.2f %b%n", name, count, price, ok);
        System.out.println(text);
        System.out.println("长度:" + name.length());
    }
}

面向对象:三件套没变,写法简单了

封装、继承、多态这套思想没有变,但代码比以前短很多:record 一行就能定义一个不可变数据类,sealed 能把实现类限定清楚,编译器还能顺带帮你检查分支是否穷尽。

public class Oop {
    sealed interface Shape permits Circle, Rect {
        double area();
    }

    record Circle(double r) implements Shape {
        @Override
        public double area() {
            return Math.PI * r * r;
        }
    }

    record Rect(double w, double h) implements Shape {
        @Override
        public double area() {
            return w * h;
        }
    }

    public static void main(String[] args) {
        Shape[] shapes = {new Circle(1.0), new Rect(2.0, 3.0)};
        for (Shape s : shapes) {
            System.out.println(s.getClass().getSimpleName() + " 面积 = " + s.area());
        }
    }
}

集合与泛型

日常打交道最多的是 List、Map、Set 三兄弟。记住两条:声明时面向接口,实例化时才用具体实现;泛型只存在于编译期,运行时会做类型擦除。

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class CollectionsDemo {
    public static void main(String[] args) {
        List<String> langs = new ArrayList<>();
        langs.add("Java");
        langs.add("Kotlin");
        langs.add("Scala");

        Map<String, Integer> ages = new LinkedHashMap<>();
        ages.put("Alice", 30);
        ages.put("Bob", 25);

        for (String lang : langs) {
            System.out.println(lang.toUpperCase());
        }
        ages.forEach((k, v) -> System.out.println(k + " 的年龄是 " + v));
        System.out.println("包含 Java 吗:" + langs.contains("Java"));
    }
}

异常处理

Java 的异常分两类:RuntimeException 及其子类叫非受检异常,编译器不管;其余叫受检异常,必须在方法签名里声明抛出或者就地捕获。新手最容易犯的错是捕获之后什么都不做,把错误悄悄吃掉,等到线上出问题再回头找就很难了。

public class ExceptionsDemo {
    static int parseAge(String raw) {
        try {
            int age = Integer.parseInt(raw);
            if (age < 0) {
                throw new IllegalArgumentException("年龄不能为负数:" + age);
            }
            return age;
        } catch (NumberFormatException e) {
            // 抛出更贴近业务的异常,同时保留原始异常作为原因
            throw new IllegalArgumentException("不是合法的数字:" + raw, e);
        }
    }

    public static void main(String[] args) {
        System.out.println("年龄是 " + parseAge("18"));
        try {
            parseAge("abc");
        } catch (IllegalArgumentException e) {
            System.out.println("捕获到异常:" + e.getMessage());
        }
    }
}

模式匹配:让一长串判断退休

从 JDK 16 的 instanceof 模式,到 JDK 21 的 switch 模式匹配与记录模式,Java 处理判断类型再取值的代码短了一大截。配合 sealed 接口,编译器会帮你确认分支有没有漏。

public class PatternDemo {
    sealed interface Event permits Login, Logout, Purchase {}

    record Login(String user) implements Event {}

    record Logout(String user) implements Event {}

    record Purchase(String user, double amount) implements Event {}

    static String describe(Event event) {
        // 因为有 sealed 限定,switch 不写 default 也能通过穷尽性检查
        return switch (event) {
            case Login(String user) -> user + " 登录了";
            case Logout(String user) -> user + " 退出了";
            case Purchase(String user, double amount) when amount > 1000 ->
                    user + " 大额消费 " + amount;
            case Purchase(String user, double amount) -> user + " 消费 " + amount;
        };
    }

    public static void main(String[] args) {
        Event[] events = {new Login("alice"), new Purchase("bob", 2000.0)};
        for (Event e : events) {
            System.out.println(describe(e));
        }
    }
}

流与函数式风格

Stream 把过滤、映射、排序、聚合写成一条链,配合 Collectors 做分组统计,是处理集合数据最顺手的工具。

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamDemo {
    record Employee(String name, String dept, double salary) {}

    public static void main(String[] args) {
        List<Employee> staff = List.of(
                new Employee("Alice", "研发", 30000),
                new Employee("Bob", "研发", 25000),
                new Employee("Cara", "市场", 22000)
        );

        Map<String, Double> avgByDept = staff.stream()
                .collect(Collectors.groupingBy(
                        Employee::dept,
                        Collectors.averagingDouble(Employee::salary)));

        staff.stream()
                .filter(e -> e.salary() >= 25000)
                .sorted((a, b) -> Double.compare(b.salary(), a.salary()))
                .map(e -> e.name() + " 属于 " + e.dept())
                .forEach(System.out::println);

        System.out.println("各部门平均薪资:" + avgByDept);
    }
}

现代 Java 的四个关键词

虚拟线程:把并发从稀缺资源变成随手可得

虚拟线程是 Project Loom 的成果,在 JDK 21 转正,是近几年 Java 最实质的变化。传统的平台线程与操作系统线程一一绑定,每条线程都要预留可观的栈空间;虚拟线程由 JVM 调度,成千上万条同时存在也不心疼,遇到阻塞会自动让出载体线程。它最适合 I/O 密集型场景:远程调用、数据库查询、文件读写。

import java.time.Duration;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class VirtualThreadsDemo {
    public static void main(String[] args) throws InterruptedException {
        // 方式一:每个任务一条虚拟线程,try-with-resources 结束时自动等待收尾
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 10_000).forEach(i -> executor.submit(() -> {
                Thread.sleep(Duration.ofMillis(50));   // 模拟一次网络等待
                return i;
            }));
        }
        System.out.println("一万个任务跑完了");

        // 方式二:直接启动一条虚拟线程
        Thread vt = Thread.ofVirtual().name("worker-1").start(
                () -> System.out.println("运行在 " + Thread.currentThread()));
        vt.join();
    }
}

钉住问题与新瓶颈

虚拟线程早期有个坑叫钉住:线程进到 synchronized 块或者本地方法里阻塞时,无法从载体线程上卸载,载体线程被白白占着,并发收益瞬间归零。JDK 24 的 JEP 491 移除了监控器导致的钉住,synchronized 不再是必须绕开的写法;但本地方法调用期间仍然会钉住,这一点到现在也没变。

更值得注意的是,线程不再稀缺之后,瓶颈会前移到下游资源。数据库连接池、限流配额、文件描述符,谁没设上限谁先被打爆。所以给下游加一道信号量,往往比反复调线程池更管用:

import java.util.concurrent.Semaphore;

public class RateLimitedFetch {
    // 用信号量给下游资源设一个硬上限,而不是靠线程池大小兜底
    private static final Semaphore PERMITS = new Semaphore(50);

    static String fetch(String url) throws InterruptedException {
        PERMITS.acquire();
        try {
            Thread.sleep(20);        // 模拟一次 HTTP 调用
            return "从 " + url + " 拿到的结果";
        } finally {
            PERMITS.release();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(fetch("https://example.com/api"));
    }
}

作用域值:替代线程局部变量的正确姿势

ThreadLocal 的老问题在线程池时代是忘记清理就内存泄漏,到了虚拟线程时代又多一条:线程用完即弃,缓存跟着反复重建,容易把垃圾回收拖垮。JDK 25 正式定稿的 ScopedValue 思路更干净,数据绑定在一段代码作用域上,而不是线程上,作用域结束自动失效。

public class ScopedValueDemo {
    static final ScopedValue<String> TRACE_ID = ScopedValue.newInstance();

    static void handle() {
        // 只有在这个作用域内才拿得到值
        System.out.println("处理请求,traceId = " + TRACE_ID.get());
    }

    public static void main(String[] args) {
        ScopedValue.where(TRACE_ID, "trace-42").run(ScopedValueDemo::handle);
        System.out.println("作用域外是否还绑定:" + TRACE_ID.isBound());
    }
}

模块化与原生镜像

JDK 9 引入的模块系统解决了类路径上的依赖地狱,用 module-info.java 显式声明依赖与导出:

module com.example.app {
    requires java.sql;
    requires com.example.spi;
    requires transitive com.example.core;
    exports com.example.app.api;
    opens com.example.app.model to org.hibernate.orm.core;
    uses com.example.spi.Plugin;
    provides com.example.spi.Plugin with com.example.app.internal.MyPlugin;
}

到了 JDK 25,模块导入声明(JEP 511)让这件事更轻:

import module java.base;

void main() {
    var names = new java.util.ArrayList<String>();   // 不必再逐个 import
    names.add("Java");
    System.out.println(names);
}

另一条线是启动速度。传统 Java 靠即时编译边跑边优化,启动快从来不是它的强项;AOT 缓存的思路是把上一次训练运行得到的热点信息存下来,下次启动直接复用。JDK 25 的 JEP 514 用 -XX:AOTCacheOutput 把训练与生成合并成一步,再配合 GraalVM 原生镜像,冷启动可以压到百毫秒量级。代价是编译时间变长,反射和动态代理需要额外配置,并非所有项目都值得。

生态全景:Java 到底能做什么?

Spring 生态:后端事实标准

Spring Framework 提供依赖注入容器与面向切面编程,Spring Boot 用约定优于配置的思路把它包装成开箱即用。当前版本线是 Spring Framework 7 加 Spring Boot 4.x:Spring Boot 4.0 于 2025 年 11 月 20 日随 Spring Framework 7.0 一同发布,基线升到 Jakarta EE 11 与 Jackson 3,最低 Java 版本为 17;Spring Boot 4.1.0 于 2026 年 6 月 10 日 GA,最新补丁 4.1.1 发布于 2026 年 8 月 20 日。

4.1 值得留意的变化有三处:gRPC 进入官方支持范围,服务端可选独立 Netty 或者 Servlet HTTP/2 传输,并新增集中式异常处理;HTTP 客户端有了服务端请求伪造的防护入口,InetAddressFilter 可以拦掉指向内网的出站请求,这对会按用户输入动态访问网址的智能体类应用很重要;在 Java 21 及以上环境虚拟线程默认开启,也正因如此,前面提到的连接池上限问题会变成新的必修课。

数据、中间件与大数据

MySQL、PostgreSQL 是主力数据库,MyBatis 与 JPA、Hibernate 是两条主流持久层路线;Redis 做缓存与分布式锁,Kafka、RocketMQ 做消息,Elasticsearch 做检索,Flink、Spark 做计算。这条链上的核心组件绝大多数本身就是 Java 或 JVM 语言写的,这也是 Java 工程师就业面宽的底层原因。

构建、测试与质量工具

环节常用选择
构建Maven、Gradle
单元测试JUnit 5、AssertJ、Mockito
集成测试Testcontainers,用真实数据库容器起环境
静态检查SpotBugs、Error Prone、Checkstyle
可观测性Micrometer、OpenTelemetry、JFR

Java 与人工智能

Java 在人工智能领域不是训练模型的语言,而是把模型接进企业系统的那门语言。Spring AI 2.0 已于 2026 年 6 月 12 日 GA,把工具调用、结构化输出、可观测性这些工程能力做成了框架级支持;LangChain4j 则在 Java 侧提供类似的编排能力。如果你已经有 Java 后端底子,往后端加智能应用集成这个方向走,会比从零转算法顺畅得多。

JVM 与性能:从能跑到跑得好

内存与垃圾回收

JVM 内存主要分堆、元空间、虚拟机栈、本地方法栈和程序计数器,调优九成的功夫花在堆和垃圾回收上。回收器的演进路线大致是:Serial 单线程适合小内存,Parallel 吞吐优先,G1 追求可预测停顿并在 JDK 9 成为服务端默认,ZGC 与 Shenandoah 则主打低延迟,停顿时间与堆大小基本无关。前面提过,JDK 27 起 G1 在所有环境下都是默认值,如果你在小容器里被 Serial 的长停顿坑过,升级就能白捡收益。

常用诊断命令

jps -l                                     # 列出本机 JVM 进程
jcmd 1234 VM.flags                         # 查看生效的 JVM 参数
jcmd 1234 GC.heap_info                     # 看堆使用情况
jcmd 1234 JFR.start duration=60s filename=app.jfr settings=profile
jcmd 1234 JFR.dump filename=app.jfr        # 导出飞行记录,交给 JMC 分析
jcmd 1234 VM.security_properties           # 查看当前安全属性(JDK 27 新增)

学习路线:五个阶段

每个阶段都给了学到什么程度算过关的判据,达不到就别急着往下走。

第一阶段,环境与基础语法(1 到 2 周)。装好 JDK、跑通命令行编译、掌握变量、运算符、分支循环、数组和方法。过关标准是能独立写出一个带输入输出的命令行小程序。

第二阶段,面向对象与核心类库(3 到 4 周)。类与对象、封装继承多态、接口与抽象类、String 与常用工具类、异常体系。过关标准是能用面向对象的方式拆解图书管理这类小需求,而不是把所有逻辑都塞进主方法。

第三阶段,集合、泛型、输入输出与 JVM 初步(3 到 4 周)。List、Map、Set 的实现差异与选型,泛型与类型擦除,流式输入输出,Lambda 与 Stream,类加载与内存区域。过关标准是说得清 ArrayList 与 LinkedList 各自适合什么场景,能读懂一段 Stream 链。

第四阶段,并发、数据库与框架(6 到 8 周)。线程与线程安全、锁与并发容器、线程池、虚拟线程、MySQL 与 SQL 优化、JDBC、Spring Boot 与持久层框架。过关标准是能独立搭一个带数据库增删改查的接口服务,并且知道并发场景下哪个环节会成为瓶颈。

第五阶段,工程化与分布式(长期)。测试、构建与发布、缓存与消息、微服务与配置中心、可观测性、容器化部署、性能调优。过关标准是能主导一个中等规模服务的上线,出故障时知道从哪几个指标入手排查。

十个新手常见坑

  1. 用 JDK 8 入门。教程虽然多,但语法旧,一上手就落伍,还会习惯性地绕开现代写法。直接用 JDK 25 学。
  2. 从第三方站点下载所谓绿色版 JDK。被裁掉模块目录和源码包的安装包,会让裁剪运行时和模块化测试莫名失败。
  3. 三处版本不一致。终端、编辑器、构建工具各用各的 JDK,症状是编译通过、运行报版本不支持。统一用 JAVA_HOME 加构建参数钉死。
  4. 把预览特性写进生产代码。预览特性需要额外开关,而且官方不承诺跨版本兼容,升级即翻车。
  5. 把虚拟线程当性能银弹。它只对 I/O 密集型有效,计算密集型场景不会有惊喜,反而可能因为调度开销变慢。
  6. 忽视下游资源上限。虚拟线程把线程这道闸门拆掉之后,数据库连接池这种默认只有一二十个连接的组件会立刻成为新瓶颈。
  7. 在虚拟线程里大量用线程局部变量做缓存。线程用完即弃,缓存的初始化次数可能成百上千倍膨胀,堆压力陡增。改用作用域值或者显式传参。
  8. equals 与 hashCode 只重写一个。把对象放进 HashSet 或者当作 HashMap 的键时,会出现存得进去、查不出来。
  9. 用双等号比较引用类型。字符串内容比较必须用 equals;包装类型还要小心 Integer 缓存只覆盖 -128 到 127。
  10. 数据结构选错。需要频繁按位置访问却用 LinkedList,需要去重排序却用 List,需要并发安全却拿 HashMap 硬扛。

生态与就业:诚实版现状

Java 是国内后端岗位存量最大的语言:银行、电信、政务、电商、SaaS 的招聘需求常年第一,Spring Boot 是简历标配。但供应量同样巨大,初级岗位竞争激烈,而懂 JVM 调优、高并发架构的资深工程师始终稀缺;安卓端 Kotlin 已成首选语言,岗位需求更多集中在后端与大数据方向。

简单说:认准企业后端与大数据这条路,Java 依然是最稳的船票;它不性感,但足够长青。

学习资源

结语

Java 的刻板印象是“啰嗦”,但这个印象已经过时:紧凑源文件让第一个程序只有三行,record 与模式匹配消掉了大半样板,虚拟线程重写了并发的复杂度公式。三十一岁的 Java 用近几年完成了幅度罕见的一次自我革新,而 JVM 的性能与生态护城河依旧深不见底。按“语法、面向对象、集合与 Stream、并发与 JVM”四步走,从入门到精通的路依然清晰可走。

延伸阅读

发表评论

验证码图片,点击可更换