Java 8 到 17 新特性详解——Lambda、Stream、Optional、Record、密封类

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/爬虫 实战干货,不让你白来。