Java 8 新特性(保姆级教程)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
前言
Java 8 的发布标志着 Java 编程语言的一次重大革新。自 2014 年正式推出以来,它引入了一系列颠覆性的新特性,如 Lambda 表达式、Stream API、默认方法和新的日期时间 API等。这些特性不仅简化了代码的编写,还显著提升了开发效率和程序的可读性。无论是编程初学者还是中级开发者,掌握这些新特性都能为日常开发带来事半功倍的效果。本文将通过通俗易懂的讲解和实际案例,帮助读者系统性地理解 Java 8 的核心改进。
一、Lambda 表达式:代码简洁化的瑞士军刀
1.1 什么是 Lambda 表达式?
Lambda 表达式是 Java 8 引入的匿名函数语法,用于简化对函数式接口(仅包含一个抽象方法的接口)的调用。它的核心思想是将行为(一段代码)作为参数传递,而非传统的对象引用。
形象比喻:
Lambda 表达式就像一个“即插即用”的工具包,你只需描述“要做什么”,而无需关心“如何实现”。
语法结构:
(parameters) -> expression
或
(parameters) -> { statements; }
1.2 Lambda 的经典应用场景
案例 1:集合排序
在 Java 8 之前,排序需要通过 Comparator
接口的匿名内部类实现:
// 旧写法
list.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Lambda 简化版:
list.sort((a, b) -> a.length() - b.length());
案例 2:遍历集合
// 旧写法
for (String item : list) {
System.out.println(item);
}
使用 Lambda 和 forEach
:
list.forEach(item -> System.out.println(item));
1.3 函数式接口的威力
Java 8 引入了 @FunctionalInterface
注解,用于标记函数式接口。例如:
@FunctionalInterface
interface MyFunction {
int calculate(int a);
}
调用时可以直接传入 Lambda:
MyFunction add = (x) -> x + 5;
System.out.println(add.calculate(10)); // 输出 15
二、Stream API:数据处理的流水线
2.1 Stream 的核心概念
Stream API 是 Java 8 对集合操作的革命性改进。它通过链式调用提供了一种声明式(Declarative)的数据处理方式,将操作分为 中间操作 和 终端操作。
形象比喻:
Stream 就像一条流水线,你可以依次添加加工步骤(中间操作),最终通过终端操作获取结果。
中间操作
- 延迟执行(Lazy Evaluation)
- 返回新的流,不改变原集合
- 例如
filter()
、map()
、sorted()
终端操作
- 触发实际计算
- 返回具体结果或 void
- 例如
collect()
、forEach()
、reduce()
2.2 Stream 的典型用法
案例 1:过滤与映射
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
// 过滤长度 > 4 的名字,转为大写后收集到新列表
List<String> result = names.stream()
.filter(name -> name.length() > 4) // 中间操作
.map(String::toUpperCase) // 中间操作
.collect(Collectors.toList()); // 终端操作
案例 2:数值计算
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计算总和
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum(); // 输出 15
// 找出最大值
OptionalInt max = numbers.stream()
.mapToInt(Integer::intValue)
.max(); // 输出 5
2.3 并行流的性能优化
通过 parallelStream()
可以利用多核 CPU 并行处理数据:
List<Integer> largeList = IntStream.range(0, 1_000_000).boxed().collect(Collectors.toList());
// 串行流
long startTime = System.currentTimeMillis();
long sum = largeList.stream().mapToLong(i -> i).sum();
System.out.println("串行耗时:" + (System.currentTimeMillis() - startTime));
// 并行流
startTime = System.currentTimeMillis();
long parallelSum = largeList.parallelStream().mapToLong(i -> i).sum();
System.out.println("并行耗时:" + (System.currentTimeMillis() - startTime));
三、默认方法:接口的进化
3.1 问题背景
在 Java 8 之前,接口只能包含抽象方法。如果需要向已有接口添加新方法,所有实现类必须被迫实现,这会导致“破坏性变更”。
3.2 默认方法的解决方案
Java 8 允许在接口中定义 默认方法(Default Methods),提供方法的默认实现,从而兼容旧代码:
interface Shape {
void draw();
// 默认方法
default void resize() {
System.out.println("默认的 resize 实现");
}
}
class Circle implements Shape {
@Override
public void draw() {
// 实现 draw 方法
}
// 可选择性覆盖默认方法
@Override
public void resize() {
System.out.println("Circle 的特殊 resize 实现");
}
}
3.3 冲突解决规则
如果多个接口为同一方法提供了默认实现,子类必须显式覆盖该方法:
interface A {
default void show() { System.out.println("A"); }
}
interface B {
default void show() { System.out.println("B"); }
}
class C implements A, B {
// 必须覆盖 show 方法
@Override
public void show() {
A.super.show(); // 调用 A 的实现
// 或 B.super.show();
}
}
四、新的日期时间 API:告别 Date 的混乱
4.1 旧 Date 类的痛点
Java 7 及之前的 java.util.Date
和 Calendar
存在以下问题:
- 不可变性差:
Date
是可变类,容易引发线程安全问题。 - API 设计混乱:如月份从 0 开始(0=1月),易出错。
- 缺乏时区支持:需依赖
Calendar
的时区设置。
4.2 Java 8 的 java.time
包
Java 8 引入了全新的 java.time
包,包含以下核心类:
| 类名 | 用途 |
|-------------------|---------------------|
| LocalDate
| 不带时区的日期(如 2023-10-01) |
| LocalTime
| 不带时区的时间(如 14:30:00) |
| LocalDateTime
| 日期与时间的组合 |
| ZonedDateTime
| 带时区的日期时间 |
| Duration
| 表示时间差(基于秒/纳秒) |
案例:日期计算与格式化
// 创建当前日期
LocalDate today = LocalDate.now();
// 计算 10 天后的日期
LocalDate futureDate = today.plusDays(10);
// 格式化输出
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
System.out.println(futureDate.format(formatter)); // 输出 "2023-10-11"
// 解析字符串为日期
LocalDate parsedDate = LocalDate.parse("2023-10-01", formatter);
五、Optional 类:告别空指针异常
5.1 Null 的隐患
在 Java 中,NullPointerException
是最常见的运行时异常之一。Optional 类通过封装 null
值,强制开发者显式处理可能为空的场景。
5.2 Optional 的基础用法
// 创建 Optional 实例
Optional<String> optionalValue = Optional.of("Hello");
Optional<String> emptyOptional = Optional.empty();
// 安全获取值
String result = optionalValue.orElse("默认值"); // 输出 "Hello"
String safeValue = emptyOptional.orElse("默认值"); // 输出 "默认值"
// 条件判断
if (optionalValue.isPresent()) {
System.out.println(optionalValue.get()); // 仅当存在值时调用 get()
}
5.3 在流操作中的应用
List<Person> people = ...;
// 安全获取第一个匹配的姓名
Optional<String> firstName = people.stream()
.filter(p -> p.getAge() > 18)
.map(Person::getName)
.findFirst();
// 使用或操作链式处理
String name = firstName.orElseGet(() -> "匿名");
六、其他重要特性
6.1 方法引用:Lambda 的简化形式
方法引用允许直接引用已有方法,进一步简化代码:
// 对应 Lambda:(s) -> System.out.println(s)
list.forEach(System.out::println);
// 构造方法引用
List<Shape> shapes = Stream.generate(Shape::new)
.limit(5)
.collect(Collectors.toList());
6.2 新的集合工具类:Collectors
Collectors
类提供了丰富的流终端操作工具,如分组、分区、统计等:
// 按性别分组统计人数
Map<Gender, Long> countByGender = people.stream()
.collect(Collectors.groupingBy(Person::getGender, Collectors.counting()));
// 收集到 Set
Set<String> uniqueNames = people.stream()
.map(Person::getName)
.collect(Collectors.toSet());
结论
Java 8 的新特性彻底改变了 Java 的编程范式,从 命令式编程 向 声明式编程 转变。Lambda 和 Stream API 使得代码更简洁、易读;默认方法和 Optional 类则增强了 API 的灵活性和安全性。对于开发者而言,掌握这些特性不仅是技术能力的提升,更是适应现代 Java 生态系统的必要条件。
无论你是刚入门的开发者,还是希望优化现有代码的中级工程师,深入理解 Java 8 的核心改进都能让你在项目中更高效地解决问题。接下来,不妨尝试将这些新特性应用到实际项目中,感受 Java 8 带来的开发体验革新!