01-JDK 8 划时代特性全景:Lambda、Stream、Optional 一次讲透 如果说 Java 历史上哪一次发布真正脱胎换骨绝大多数老程序员的答案会毫不犹豫JDK 8。2014 年发布的它至今仍是无数生产系统的默认版本生命周期长到连后面的 JDK 21 都喊它前辈。为什么一个 2014 年的版本能活到今天还不过时因为它一口气把函数式编程、现代集合操作、更安全的空值处理、全新的时间 API 全部塞了进来等于给 Java 换了发动机。这篇文章把 JDK 8 那些用了就回不去的特性一次讲透帮你建立对现代 Java 的整体认知。后面所有新版本的新特性几乎都是在 8 的基础上做加法。一、Lambda 表达式函数式编程进门在 JDK 8 之前想让一个方法接收一个行为只能写匿名内部类又臭又长// JDK 7 及以前newThread(newRunnable(){Overridepublicvoidrun(){System.out.println(hello);}}).start();JDK 8 之后一行搞定newThread(()-System.out.println(hello)).start();// 带参数和返回值ComparatorStringcmp(a,b)-Integer.compare(a.length(),b.length());Lambda 的本质不是语法糖那么简单——它依赖背后的函数式接口和invokedynamic指令。所谓函数式接口就是只有一个抽象方法的接口JDK 8 用FunctionalInterface注解标记它如Runnable、Comparator、Function。(a, b) - ...在编译期会被匹配到对应函数式接口的抽象方法签名运行时通过invokedynamic动态绑定避免为每次调用都生成匿名类字节码性能更好。几个要点参数类型可省略编译器推断单参数可省括号。单语句可省{}和return。Lambda 内访问外部局部变量该变量会被隐式要求为final或等效 final不能重新赋值。二、Stream API集合操作的函数式革命java.util.stream是 JDK 8 最重磅的特性之一。它让对集合做过滤、转换、聚合从一堆for循环变成声明式流水线ListStringnamesusers.stream().filter(u-u.getAge()18)// 中间操作过滤.map(User::getName)// 中间操作映射.sorted()// 中间操作排序.collect(Collectors.toList());// 终止操作收集关键概念要分清类别例子特点中间操作filter/map/flatMap/distinct/sorted/limit惰性返回新 Stream不立即执行终止操作collect/forEach/count/reduce/anyMatch触发整个流水线真正执行“惰性求值是 Stream 的灵魂你写一堆filter/map在调用终止操作前它们都不干活一旦遇到collect才从源头一次性流过整条流水线。这也带来一个常见坑Stream 只能用一次调用终止操作后即消费”再调用会抛IllegalStateException。Stream 还支持并行list.parallelStream()或.parallel()底层用 ForkJoinPool 把数据分片并行处理。但要注意——并行不是免费的小数据量、有状态操作如sorted、distinct反而更慢且要警惕线程安全和顺序依赖。三、Optional给可能为空一个类型化的壳NullPointerException是 Java 程序员的老朋友。JDK 8 引入OptionalT不是消灭 null而是把可能为空这件事写进类型系统强迫调用方思考空值publicOptionalUserfindUser(longid){returnOptional.ofNullable(dao.get(id));// 可能为 null}// 调用方不得不处理没有的情况StringnamefindUser(1).map(User::getName).orElse(匿名);// 没有就给默认值findUser(1).ifPresent(u-System.out.println(u.getName()));// 有才执行最佳实践Optional用作返回值表示可能无结果不要用于字段、方法参数反而增加噪音。避免get()裸调用没有值照样抛异常优先orElse/orElseGet/ifPresent。orElseGet接受Supplier只有真的为空才执行比orElse(表达式)每次都计算更省。四、接口默认方法与静态方法JDK 8 打破了接口不能有实现的铁律引入default方法和static方法interfaceVehicle{defaultvoidstart(){// 默认实现实现类可重写也可不重写System.out.println(启动);}staticintwheels(){return4;}// 接口静态方法}这解决了给已有接口加方法会破坏所有实现类的老大难问题——Collection加stream()、forEach()就是靠default方法平滑落地旧实现类一行不用改。代价是引入了菱形继承歧义一个类实现两个接口都有同名default方法时必须显式重写指明用哪个InterfaceA.super.method()。五、新的 Date/Time APIjava.time旧的java.util.Date和Calendar饱受诟病月份从 0 开始、可变且线程不安全、CalendarAPI 反人类。JDK 8 带来全新的java.time借鉴 Joda-TimeLocalDatedLocalDate.of(2026,8,28);// 日期无时区LocalTimetLocalTime.of(9,30);// 时间无时区LocalDateTimedtLocalDateTime.now();// 日期时间ZonedDateTimezZonedDateTime.now(ZoneId.of(Asia/Shanghai));// 带时区PeriodpPeriod.between(d,LocalDate.now());// 日期间隔年/月/日DurationdurDuration.ofHours(3);// 时间段秒/纳秒核心改进不可变线程安全每次运算返回新对象。命名清晰LocalDate管日期、LocalTime管时间、Instant管时间戳。格式化用DateTimeFormatter线程安全旧的SimpleDateFormat是出了名的线程不安全坑。六、HashMap 的红黑树优化性能底层JDK 8 给HashMap做了一次关键改造当某个桶的链表长度超过 8且数组容量 ≥ 64 时链表转为红黑树查询从 O(n) 退化保护到 O(log n)。这同时也修复了 JDK 7 及以前HashMap在多线程下扩容可能形成的死循环JDK 8 改用高低位拆分避免了头插法环化。顺带8 还引入了Metaspace取代永久代PermGen类元数据改由本地内存管理默认不再有PermGen space溢出调优更友好。String底层从char[]改为byte[] 编码标记省内存不过真正的紧凑化在 JDK 9 才完成。新的StampedLock、LongAdder、CompletableFuture初版等并发增强。七、为什么 JDK 8 能活到今天一句话它把现代 Java的范式定下来了又足够稳定。Lambda/Stream/Optional 是写法的分水岭后面的版本9 模块化、10 的 var、16 的 record、21 的虚拟线程全是在这套范式上生长而不是推翻它。对业务系统来说8 的生态Spring Boot 2.x、各类中间件最成熟升级风险最低于是就停在 8成了最省心的选择。但今天再看停在 8 等于主动放弃后面十年免费送来的性能ZGC、G1 优化、安全性TLS、加密更新和开发效率record、虚拟线程。下一篇我们就从 JDK 9 的模块化说起看看 8 之后到底发生了什么。八、本篇记住的清单Lambda 背后是函数式接口 invokedynamic不是简单语法糖。Stream 区分中间/终止操作惰性求值、一次性消费。Optional用于返回值别裸调get()。default方法让接口可平滑演进代价是菱形歧义需显式解决。java.time全面不可变、线程安全取代Date/Calendar。HashMap链表转红黑树、扩容去死循环Metaspace 取代永久代。JDK 8 是现代 Java的基石但停留在 8 会错过后续十年的红利。