Java 8 是 Java 历史上最重要的版本,引入的函数式编程彻底改变了写代码的方式。而 Java 9-17 也带来了很多实用的新特性,这些在面试和日常开发中越来越常见。
一、Java 8 核心新特性
1. Lambda 表达式
java
// 旧写法:匿名内部类
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("点击");
}
});
// Lambda 写法
button.addActionListener(e -> System.out.println("点击"));
// 多行写法
button.addActionListener(e -> {
System.out.println("点击");
System.out.println("执行后续操作");
});
本质: Lambda 是函数式接口的匿名实现。函数式接口就是只有一个抽象方法的接口(@FunctionalInterface)。
2. Stream 流
java
List<String> names = Arrays.asList("张三", "李四", "王五", "赵六", "张三");
// 传统写法:过滤 + 去重 + 遍历
List<String> result = new ArrayList<>();
for (String name : names) {
if (name.startsWith("张")) {
if (!result.contains(name)) {
result.add(name);
}
}
}
for (String name : result) {
System.out.println(name);
}
// Stream 写法:一行搞定
names.stream()
.filter(name -> name.startsWith("张"))
.distinct()
.forEach(System.out::println);
常用的 Stream 操作:
java
List<User> users = getUserList();
// 1. 过滤(filter)
users.stream()
.filter(u -> u.getAge() > 18)
.collect(Collectors.toList());
// 2. 映射(map):提取某个字段
List<String> names = users.stream()
.map(User::getName)
.collect(Collectors.toList());
// 3. 排序(sorted)
users.stream()
.sorted(Comparator.comparing(User::getAge))
.collect(Collectors.toList());
// 4. 分组(groupingBy)
Map<String, List<User>> byCity = users.stream()
.collect(Collectors.groupingBy(User::getCity));
// 5. 聚合(reduce):求和
int totalAge = users.stream()
.map(User::getAge)
.reduce(0, Integer::sum);
// 6. 判断(anyMatch/allMatch/noneMatch)
boolean hasAdult = users.stream().anyMatch(u -> u.getAge() >= 18);
boolean allAdult = users.stream().allMatch(u -> u.getAge() >= 18);
// 7. 去重(distinct)
List<Integer> ages = users.stream()
.map(User::getAge)
.distinct()
.collect(Collectors.toList());
// 8. 分页(skip + limit)
List<User> page = users.stream()
.skip(10) // 跳过前10条
.limit(10) // 取10条
.collect(Collectors.toList());
Stream 注意事项:
- stream 只能消费一次,用完不能重复使用
- 中间操作(filter、map)是惰性的,遇到终端操作(collect、forEach)才执行
- 并行流
parallelStream()小心线程安全问题
3. Optional------告别空指针
java
// 以前:每次都要判空
User user = userService.findById(id);
if (user != null) {
String name = user.getName();
if (name != null) {
System.out.println(name.toUpperCase());
}
}
// Optional 写法
Optional.ofNullable(userService.findById(id))
.map(User::getName)
.map(String::toUpperCase)
.ifPresent(System.out::println);
常用方法:
java
// 创建
Optional<String> opt = Optional.of("value"); // 非空值,传 null 抛异常
Optional<String> opt = Optional.ofNullable(nullable); // 可空值
Optional<String> opt = Optional.empty(); // 空
// 消费
opt.ifPresent(val -> System.out.println(val)); // 有值则执行
opt.ifPresentOrElse(val -> { // 有值或无值各做不同事
// 有值
}, () -> {
// 无值
});
// 取值(安全)
String result = opt.orElse("默认值"); // 有值返回,无值返回默认
String result = opt.orElseGet(() -> computeDefault()); // 延迟计算默认值
String result = opt.orElseThrow(() -> new RuntimeException("值不存在"));
// 转换
Optional<String> upper = opt.map(String::toUpperCase);
Optional<Integer> len = opt.flatMap(val -> Optional.of(val.length()));
// 过滤
Optional<String> filtered = opt.filter(val -> val.startsWith("A"));
原则: Optional 主要用于返回值,不要用作参数或字段类型。
二、Java 9-11 新特性
1. 集合工厂方法(Java 9)
java
// 以前:写一堆 add
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
// Java 9 以后:一行搞定
List<String> list = List.of("a", "b", "c");
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("key1", 1, "key2", 2);
// 注意:返回的是不可变集合,不能增删改
2. var 局部变量类型推断(Java 10)
java
// 以前
Map<String, List<User>> userMap = new HashMap<>();
// Java 10+
var userMap = new HashMap<String, List<User>>();
var list = List.of("a", "b", "c");
var stream = list.stream().filter(s -> s.startsWith("a"));
// 注意:var 只能用在局部变量,不能用在成员变量、方法参数、返回值
3. 增强的 try-with-resources(Java 9)
java
// 以前:资源必须在 try 里创建
try (InputStream in = new FileInputStream("file.txt")) {
in.read();
}
// Java 9:可以用 final 或 effectively final 的变量
InputStream in = new FileInputStream("file.txt");
try (in) {
in.read();
}
4. 字符串增强(Java 11)
java
String str = " Hello World ";
str.isBlank(); // true(如果全是空白字符)
str.strip(); // "Hello World"(去除首尾空白,支持全角空格)
str.stripLeading(); // "Hello World "
str.stripTrailing(); // " Hello World"
str.repeat(3); // " Hello World Hello World Hello World "
"a\nb\nc".lines() // Stream<String> 按行分割
.forEach(System.out::println);
三、Java 14-17 新特性
1. Record------数据载体(Java 14 预览,Java 16 正式)
以前写一个 POJO 类要写一堆 getter/setter/toString/equals/hashCode:
java
// 以前
public class UserDTO {
private Long id;
private String name;
private Integer age;
public UserDTO(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
// getter/setter/toString/equals/hashCode 几十行代码...
}
// Record:一行搞定
public record UserDTO(Long id, String name, Integer age) {}
// 使用
var user = new UserDTO(1L, "张三", 25);
user.id(); // 注意不是 getId(),是 id()
user.name(); // name()
user.toString(); // UserDTO[id=1, name=张三, age=25]
Record 特点:
- 自动生成构造方法、getter(注意命名风格没有 get 前缀)、equals、hashCode、toString
- 实例是不可变的(字段都是 final 的)
- 适合做 DTO、VO、查询参数等纯数据载体
2. 密封类------Sealed Class(Java 15 预览,Java 17 正式)
控制哪些类可以继承它:
java
// 允许 Dog 和 Cat 继承,其他类不允许
public sealed abstract class Animal permits Dog, Cat {
public abstract void sound();
}
// 允许继承
public final class Dog extends Animal {
@Override
public void sound() {
System.out.println("汪汪");
}
}
// 允许继承
public final class Cat extends Animal {
@Override
public void sound() {
System.out.println("喵喵");
}
}
// ❌ 编译错误:Parrot 不在 permits 列表中
// public class Parrot extends Animal {}
使用场景: 枚举类不够用、但继承又想严格控制时。比如统一的支付结果类型、订单状态等。
3. 模式匹配------instanceof 增强(Java 16 正式)
java
// 以前
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
}
// Java 16+
if (obj instanceof String str) {
System.out.println(str.length());
}
4. 文本块(Java 13 预览,Java 15 正式)
java
// 以前:拼接 HTML 很痛苦
String html = "<html>\n" +
" <body>\n" +
" <p>Hello</p>\n" +
" </body>\n" +
"</html>";
// 文本块
String html = """
<html>
<body>
<p>Hello</p>
</body>
</html>
""";
5. Switch 表达式(Java 14 正式)
java
// 以前
String result;
switch (day) {
case MONDAY:
case FRIDAY:
result = "工作日";
break;
case SATURDAY:
case SUNDAY:
result = "休息日";
break;
default:
result = "未知";
}
// Switch 表达式(简洁多了)
String result = switch (day) {
case MONDAY, FRIDAY -> "工作日";
case SATURDAY, SUNDAY -> "休息日";
default -> "未知";
};
// 带代码块
String result = switch (day) {
case MONDAY -> {
System.out.println("周一");
yield "工作日"; // 用 yield 返回值
}
case SATURDAY, SUNDAY -> "休息日";
default -> "未知";
};
四、版本升级建议
从 Java 8 到 Java 17,你的代码基本不需要大改:
- Lambda、Stream、Optional 这些老写法完全兼容
- 新的语法(Record、Sealed)想用就用,不用也不影响
- 最大的收益是性能和安全性提升(GC、TLS、加密算法)
生产建议:
- 新项目直接用 Java 17(LTS 版本,支持到 2029 年)
- 老项目从 Java 8 升级到 11 再到 17,逐版本测试
- 注意:SpringBoot 2.x 最高支持 Java 17,SpringBoot 3.x 要求 Java 17+
💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。