Java 8/11/17 新特性详解 ⭐⭐⭐
🎯 面试题:Java 8 有哪些新特性?Stream 和 Lambda 的原理是什么?
Java 8 是历史上最重要的版本更新,引入了 Lambda 表达式、Stream API、Optional 等革命性特性,至今仍是面试的核心考点。Java 17 作为新的 LTS 版本,带来了 Sealed Classes、Records、Pattern Matching 等新特性。
一、Lambda 表达式
核心概念
// 匿名内部类 → Lambda 表达式
// 之前:冗长的匿名类
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// 之后:简洁的 Lambda
Runnable r2 = () -> System.out.println("Hello");
// 语法规则:
// (参数列表) -> { 方法体 }
Runnable r = () -> {}; // 无参数
Consumer<String> c = s -> {}; // 单参数(括号可省略)
BiFunction<A, B, R> f = (a, b) -> {}; // 多参数
Supplier<T> s = () -> new T(); // 返回值
函数式接口
// @FunctionalInterface 注解:只有一个抽象方法的接口
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
// 其他方法可以是 default 或 static
default Predicate<T> and(Predicate<? super T> other) { ... }
static <T> Predicate<T> isEqual(Object targetRef) { ... }
}
// Java 8 内置的四大核心函数式接口:
// 1. Consumer<T> void accept(T t)
// 2. Supplier<T> T get()
// 3. Function<T,R> R apply(T t)
// 4. Predicate<T> boolean test(T t)
// 还有变体:BiConsumer, BiFunction, BiPredicate, UnaryOperator, BinaryOperator
方法引用
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 三种方法引用:
// 1. 静态方法引用:ClassName::staticMethod
Function<String, Integer> parser = Integer::parseInt;
// 2. 实例方法引用:instance::method
String str = "Hello";
Supplier<Integer> len = str::length;
// 3. 构造方法引用:ClassName::new
Supplier<ArrayList<String>> listFactory = ArrayList::new;
Function<Integer, ArrayList<String>> listWithCapacity = ArrayList::new;
// 实际应用
names.stream()
.map(String::toUpperCase) // 实例方法引用
.forEach(System.out::println); // println 是 println(obj) 形式
二、Stream API
核心概念
Stream vs Collection:
Collection:存储数据的容器,遍历是外部迭代(for-each)
Stream: 计算数据的视图,遍历是内部迭代
┌──────────────────────────────────────────────────┐
│ Stream 流水线 │
│ │
│ 数据源 ──→ 中间操作 ──→ 中间操作 ──→ ... ──→ 终止操作 │
│ List filter() map() forEach()│
│ Set distinct() limit() collect()│
│ Array sorted() flatMap() reduce() │
│ Map skip() peek() findFirst()│
│ limit() │
└──────────────────────────────────────────────────┘
关键特性:
1. 不存储数据
2. 不修改数据源
3. 惰性求值(只有遇到终止操作才执行)
常用操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 中间操作(返回 Stream,可以链式调用)
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // 过滤偶数
.map(n -> n * n) // 平方
.distinct() // 去重
.limit(3) // 取前 3 个
.collect(Collectors.toList());
// 排序
List<String> sorted = names.stream()
.sorted(Comparator.comparing(String::length).reversed())
.collect(Collectors.toList());
// 分组
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
// 分区(按条件分为两组)
Map<Boolean, List<Person>> adults = people.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() >= 18));
// 多级分组
Map<String, Map<String, List<Person>>> byCityAndGender =
people.stream()
.collect(Collectors.groupingBy(
Person::getCity,
Collectors.groupingBy(Person::getGender)
));
终止操作
// collect - 收集
List<String> list = stream().collect(Collectors.toList());
Set<String> set = stream().collect(Collectors.toSet());
Map<K, V> map = stream().collect(Collectors.toMap(k, v));
// reduce - 聚合
Optional<Integer> sum = numbers.stream().reduce(Integer::sum);
int total = numbers.stream().reduce(0, Integer::sum);
// max / min
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
// findAny / findFirst
Optional<String> first = names.stream().findFirst(); // 有序 stream 返回第一个
// allMatch / anyMatch / noneMatch
boolean allPositive = numbers.stream().allMatch(n -> n > 0);
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
// forEach
stream().forEach(System.out::println);
// count
long count = stream().count();
// 并行流
List<Integer> result = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.collect(Collectors.toList());
// parallelStream() 默认使用 ForkJoinPool.commonPool()
常用收集器
// toList / toSet / toCollection
stream().collect(Collectors.toList());
// joining - 拼接字符串
String joined = names.stream()
.collect(Collectors.joining(", ", "[", "]")); // [Alice, Bob, Charlie]
// counting / summingInt / averagingInt
long count = stream().collect(Collectors.counting());
int sum = stream().collect(Collectors.summingInt(Integer::intValue));
double avg = stream().collect(Collectors.averagingInt(Integer::intValue));
// summarizingInt - 一次性获取 count/sum/avg/min/max
IntSummaryStatistics stats = numbers.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println(stats.getSum()); // 总和
System.out.println(stats.getAverage());// 平均值
System.out.println(stats.getMin()); // 最小值
System.out.println(stats.getMax()); // 最大值
三、Optional
为什么需要 Optional?
// 空指针的噩梦
String city = user.getAddress().getCity().getName();
// 如果任何一个环节返回 null,立即 NPE
// Optional 解决方案
// 链式调用,安全地处理空值
Optional<String> city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName);
// 类似于
Optional<String> city = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCity)
.map(City::getName); // City::getName 返回 String,不是 Optional
// 而 flatMap 用于返回 Optional 的方法
核心方法
// 创建
Optional<String> empty = Optional.empty();
Optional<String> nonNull = Optional.of("value"); // null 抛 NPE
Optional<String> nullable = Optional.ofNullable(null); // 安全
// 判断
optional.isPresent(); // 是否有值
optional.isEmpty(); // Java 11+,是否为空
// 获取值
optional.get(); // 空则抛 NoSuchElementException
optional.orElse("default"); // 空则返回默认值
optional.orElseGet(() -> "computed"); // 空则计算(惰性)
optional.orElseThrow(); // 空则抛异常(可自定义)
// 链式调用
optional.map(String::toUpperCase); // 有值则转换
optional.flatMap(opt -> opt.isEmpty() ? Optional.empty() : ...); // 返回 Optional
optional.filter(s -> s.length() > 3); // 过滤
// ifPresent
optional.ifPresent(v -> System.out.println(v)); // 有值才执行
optional.ifPresentOrElse(v -> {}, () -> {}); // Java 9+,有值/无值分别处理
// stream() - Java 9+
List<String> names = optional.stream()
.filter(...)
.collect(Collectors.toList());
实战应用
// 场景一:方法返回值用 Optional
public Optional<User> findById(Long id) {
return Optional.ofNullable(userRepository.findById(id));
}
// 场景二:避免 if-null 判断
String city = userRepository.findById(id)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName)
.orElse("未知");
// 场景三:默认值
String defaultValue = optional.orElse("N/A");
// 场景四:进阶用法 - Optional 作为方法参数(不推荐滥用)
public void process(Optional<String> name) {
name.ifPresent(n -> System.out.println(n));
}
四、接口默认方法与静态方法
public interface A {
// 默认方法:实现类可以不重写
default void defaultMethod() {
System.out.println("A 的默认实现");
}
// 静态方法:属于接口,不属于实现类
static void staticMethod() {
System.out.println("A 的静态方法");
}
}
public class B implements A {
// B 可以直接使用默认方法
// B 也可以重写默认方法
}
public class C implements A {
@Override
public void defaultMethod() {
System.out.println("C 的重写实现");
}
}
五、新的日期时间 API
// ❌ 旧 API 的问题:线程不安全、API 混乱、时区处理复杂
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 线程不安全!
// ✅ Java 8 新 API:线程安全、API 清晰、ISO 8601 标准
LocalDate date = LocalDate.now(); // 2024-01-15
LocalTime time = LocalTime.now(); // 14:30:00
LocalDateTime dt = LocalDateTime.now(); // 2024-01-15T14:30:00
ZonedDateTime zdt = ZonedDateTime.now(); // 带时区
// 格式化
String formatted = dt.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss"));
// 计算
LocalDate nextWeek = date.plusWeeks(1);
LocalDate lastMonth = date.minusMonths(1);
long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(start, end);
// 解析
LocalDate parsed = LocalDate.parse("2024-01-15");
LocalDate parsed2 = LocalDate.parse("2024/01/15", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
// 时区转换
ZonedDateTime tokyo = ZonedDateTime.of(dt, ZoneId.of("Asia/Tokyo"));
ZonedDateTime utc = tokyo.withZoneSameInstant(ZoneOffset.UTC);
// Duration vs Period
Duration duration = Duration.between(start, end); // 时间间隔(秒/纳秒)
Period period = Period.between(startDate, endDate); // 日期间隔(天/月/年)
六、CompletableFuture
// ❌ Future 的局限:无法组合、无法链式调用
Future<String> future = executor.submit(() -> {
return callRemoteService();
});
String result = future.get(); // 阻塞
// ✅ CompletableFuture:强大的异步编程
public CompletableFuture<String> fetchUser(Long id) {
return CompletableFuture.supplyAsync(() -> {
return userRepository.findById(id); // 异步执行
});
}
// 链式调用
CompletableFuture.supplyAsync(() -> fetchUser(1L))
.thenApply(User::getName) // 转换
.thenCompose(name -> fetchAddress(name)) // 返回 CompletableFuture 的函数
.thenCombine(otherFuture, (addr1, addr2) -> ...)// 合并两个
.exceptionally(ex -> "错误: " + ex.getMessage()) // 异常处理
.thenAccept(result -> System.out.println(result)); // 最终处理
// 并行执行多个任务
CompletableFuture.allOf(f1, f2, f3).join(); // 等待所有完成
CompletableFuture.anyOf(f1, f2, f3).join(); // 任意一个完成即可
// 超时控制
CompletableFuture.supplyAsync(() -> call())
.orTimeout(3, TimeUnit.SECONDS) // 3 秒超时
.exceptionally(ex -> "默认值"); // 超时后返回
七、Java 11 新特性
ZGC(低延迟垃圾回收器)
# 开启 ZGC,停顿时间控制在亚毫秒级
java -XX:+UseZGC -Xmx16g -jar app.jar
# ZGC 的特点:
# 1. 并发执行,大多数阶段和应用线程并发
# 2. 分区管理:小型(≤256KB)、中型(≤4MB)、大型(>4MB)
# 3. 停顿时间不超过 1ms(而 G1 可能达到 100ms+)
String 增强
" hello ".isBlank(); // true(空白字符)
" hello ".strip(); // "hello"(去除首尾空白)
" hello ".stripLeading(); // "hello "(只去头部)
" hello ".stripTrailing(); // " hello"(只去尾部)
"abc".repeat(3); // "abcabcabc"
"line1\nline2".lines().count(); // 2,按换行分割
HTTP Client(标准 API)
// Java 11 标准 HTTP Client(取代 HttpURLConnection)
HttpClient client = HttpClient.newHttpClient();
// 同步
HttpResponse<String> response = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.GET()
.build()
.send();
System.out.println(response.body());
// 异步
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
var 局部类型推断
// Java 10 引入,局部变量可以用 var
var list = new ArrayList<String>(); // 推断为 ArrayList<String>
var stream = list.stream(); // 推断为 Stream<String>
var map = new HashMap<String, Integer>();
// 注意:var 不是关键字,是保留类型名
// 不能用于:字段、方法参数、返回值
八、Java 17 新特性(LTS)
Records(不可变数据类)
// ❌ 传统的 POJO:大量样板代码
public class Point {
private final int x;
private final int y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int getX() { return x; }
public int getY() { return y; }
// 还要 equals, hashCode, toString...
}
// ✅ Records:自动生成 constructor, getters, equals, hashCode, toString
public record Point(int x, int y) {}
// 自动生成的方法:
// Point(int x, int y) - 构造方法
// int x() / int y() - getter(不是 getX())
// equals, hashCode, toString
Point p = new Point(1, 2);
System.out.println(p.x()); // 1
System.out.println(p); // Point[x=1, y=2]
// 可以添加约束
public record Point(int x, int y) {
public Point { // 紧凑构造方法
if (x < 0 || y < 0) throw new IllegalArgumentException();
}
}
// 可以添加额外方法
public record Point(int x, int y) {
public double distanceFromOrigin() {
return Math.sqrt(x * x + y * y);
}
}
Sealed Classes(密封类)
// 限制类的继承层级:哪些类可以继承,哪些不可以
public sealed class Shape permits Circle, Rectangle, Square {}
// Circle 和 Rectangle 可以是 final、sealed 或 non-sealed
public final class Circle extends Shape { }
public sealed class Rectangle extends Shape permits ColoredRectangle { }
public non-sealed class Square extends Shape { } // 允许被任意类继承
// 使用场景:模式匹配(Java 21 Pattern Matching 更强大)
sealed interface Expr permits NumExpr, AddExpr, VarExpr { }
record NumExpr(int n) implements Expr { }
record AddExpr(Expr left, Expr right) implements Expr { }
record VarExpr(String name) implements Expr { }
String eval(Expr e) {
return switch (e) {
case NumExpr(int n) -> String.valueOf(n);
case AddExpr(var l, var r) -> eval(l) + "+" + eval(r);
case VarExpr(String name) -> name;
};
}
Pattern Matching for switch(预览)
// Java 21 正式版
String formatted(Object obj) {
return switch (obj) {
case Integer i -> String.format("int %d", i);
case String s && s.length() > 5 -> "Long string: " + s;
case String s -> "String: " + s;
case null, default -> "Other";
};
}
九、高频面试题
Q1: Java 8 的 Stream 和 for-each 循环有什么区别?
三个核心区别:① Stream 是内部迭代(Stream 库控制迭代),for-each 是外部迭代(自己控制循环);② Stream 支持链式操作(filter/map/reduce),代码更简洁;③ Stream 支持并行流(parallelStream),自动利用多核并行处理。for-each 适合简单的遍历和修改集合本身,Stream 适合对数据的转换、过滤、聚合等计算密集型操作。
Q2: Lambda 表达式的原理是什么?
Lambda 表达式在运行时会生成一个对应函数式接口的匿名内部类,或者在 JVM 层面生成
invokedynamic指令。编译时,Lambda 表达式被翻译成invokedynamic调用点,第一次执行时由LambdaMetafactory.metafactory生成一个实现了函数式接口的内部类(可能用方法引用或直接执行 Lambda 体)。方法引用比 Lambda 更高效,因为可能直接指向已有的方法而不是生成新的类。
Q3: Optional 的正确用法和误区?
正确用法:① 作为方法返回值替代 null;② 链式调用替代多层 if-null 判断;③
orElseGet惰性计算默认值。误区:① 不要用get()+isPresent()(和 if-null 一样啰嗦);② 不要把 Optional 作为字段或方法参数(滥用);③ 不要用Optional.of(null)(会抛 NPE),用Optional.ofNullable()。
Q4: Java 17 和 Java 8 最大的区别是什么?
核心区别:① 语法层面:Records(不可变数据类)、Sealed Classes(限制继承)、Pattern Matching(简化 switch)、text blocks;② 性能层面:ZGC(亚毫秒停顿的 GC)、G1 成为默认 GC、字符串底层优化(UTF-16 → byte[]);③ API 层面:标准 HTTP Client、新的日期时间 API 完善、Files 新增方法;④ 安全层面:移除 Security Manager、移除 Applet、密封类增强类型安全。
Q5: CompletableFuture 和 Future 的区别?
Future 的局限:只能通过
get()阻塞获取结果,无法组合多个 Future,无法链式调用。CompletableFuture 的优势:① 支持链式调用(thenApply/thenCompose/thenCombine);② 支持并行组合(allOf/anyOf);③ 支持异常处理(exceptionally);④ 支持超时控制(orTimeout);⑤ 支持手动完成(complete/experimentally complete exceptionally)。可以完全替代 Future。