← 返回面试专题导航

🚀 Java 新特性面试题总览

掌握 JDK 8+ 的核心新特性和现代编程范式

Java 8 是里程碑版本,引入了 Lambda、Stream、Optional 等现代特性。本篇系统梳理 Java 新特性的核心知识。

🎯 难度筛选

📚 本篇覆盖内容

本篇重点围绕 Java 新特性的核心知识,帮助你系统掌握:

  1. Lambda 表达式:函数式编程、方法引用、函数式接口。
  2. Stream API:流式操作、中间操作、终止操作。
  3. Optional 类:避免空指针、优雅处理 null。
  4. 其他新特性:接口默认方法、日期时间 API、模块化系统。

一、Lambda 表达式

1. 什么是 Lambda 表达式?有什么优势? 简单

Lambda 表达式:一种简洁的匿名函数表示方式,支持函数式编程。

// 传统写法:匿名内部类 Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Hello"); } }; // Lambda 写法 Runnable r2 = () -> System.out.println("Hello"); // 带参数的 Lambda Comparator comparator = (a, b) -> a - b; // 多行 Lambda Consumer consumer = s -> { System.out.println("Processing: " + s); System.out.println("Done"); };

优势:

  • 代码更简洁,减少样板代码
  • 支持函数式编程
  • 提高代码可读性
  • 便于并行处理
💡 语法:(参数) -> 表达式 或 (参数) -> { 语句块 }
2. 什么是函数式接口?常用的函数式接口有哪些? 中等

函数式接口:只有一个抽象方法的接口,可以用 Lambda 表达式实现。

// 自定义函数式接口 @FunctionalInterface interface MyFunction { int apply(int x, int y); } // 使用 Lambda 实现 MyFunction add = (x, y) -> x + y; System.out.println(add.apply(3, 5)); // 8

常用函数式接口(java.util.function):

// 1. Function:接收 T 返回 R Function strLength = s -> s.length(); System.out.println(strLength.apply("hello")); // 5 // 2. Consumer:接收 T 无返回值 Consumer printer = s -> System.out.println(s); printer.accept("Hello"); // 3. Supplier:无参数返回 T Supplier random = () -> Math.random(); System.out.println(random.get()); // 4. Predicate:接收 T 返回 boolean Predicate isEven = n -> n % 2 == 0; System.out.println(isEven.test(4)); // true // 5. BiFunction:接收 T 和 U 返回 R BiFunction add = (a, b) -> a + b; System.out.println(add.apply(3, 5)); // 8
💡 记忆口诀:Function 转换,Consumer 消费,Supplier 提供,Predicate 判断。
3. 什么是方法引用?有哪几种形式? 中等

方法引用:Lambda 的简化写法,直接引用已有方法。

// 1. 静态方法引用:类名::静态方法 Function parseInt1 = s -> Integer.parseInt(s); Function parseInt2 = Integer::parseInt; // 简化 // 2. 实例方法引用:对象::实例方法 String str = "hello"; Supplier upper1 = () -> str.toUpperCase(); Supplier upper2 = str::toUpperCase; // 简化 // 3. 类的实例方法引用:类名::实例方法 Function length1 = s -> s.length(); Function length2 = String::length; // 简化 // 4. 构造器引用:类名::new Supplier> list1 = () -> new ArrayList<>(); Supplier> list2 = ArrayList::new; // 简化 Function array1 = size -> new int[size]; Function array2 = int[]::new; // 简化
💡 使用场景:当 Lambda 只是调用一个已有方法时,可以用方法引用简化。
4. Stream API 的核心概念和使用场景? 困难

Stream:对集合进行函数式操作的流式 API,支持链式调用。

Stream 操作流程: 数据源 → 中间操作 → 终止操作 → 结果 中间操作(返回 Stream): - filter:过滤 - map:映射 - flatMap:扁平化映射 - sorted:排序 - distinct:去重 - limit:限制数量 - skip:跳过元素 终止操作(返回结果): - forEach:遍历 - collect:收集 - reduce:归约 - count:计数 - anyMatch/allMatch/noneMatch:匹配 - findFirst/findAny:查找
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 示例1:过滤 + 映射 + 收集 List result = numbers.stream() .filter(n -> n % 2 == 0) // 过滤偶数 .map(n -> n * n) // 平方 .collect(Collectors.toList()); // 收集结果 // 结果:[4, 16, 36, 64, 100] // 示例2:求和 int sum = numbers.stream() .filter(n -> n > 5) .mapToInt(Integer::intValue) .sum(); // 结果:40 // 示例3:分组 Map> grouped = numbers.stream() .collect(Collectors.partitioningBy(n -> n % 2 == 0)); // 结果:{false=[1,3,5,7,9], true=[2,4,6,8,10]} // 示例4:字符串拼接 String joined = numbers.stream() .map(String::valueOf) .collect(Collectors.joining(", ")); // 结果:"1, 2, 3, 4, 5, 6, 7, 8, 9, 10"
⚠️ 注意:Stream 只能使用一次,终止操作后不能再使用。
5. Stream 的 map 和 flatMap 有什么区别? 中等

map:一对一映射,每个元素映射为一个新元素。

flatMap:一对多映射,每个元素映射为一个流,然后扁平化。

// map 示例:一对一 List words = Arrays.asList("hello", "world"); List lengths = words.stream() .map(String::length) .collect(Collectors.toList()); // 结果:[5, 5] // flatMap 示例:一对多 List words = Arrays.asList("hello", "world"); List chars = words.stream() .flatMap(word -> Arrays.stream(word.split(""))) .collect(Collectors.toList()); // 结果:["h", "e", "l", "l", "o", "w", "o", "r", "l", "d"] // 更复杂的例子:嵌套列表扁平化 List> nested = Arrays.asList( Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9) ); // 使用 flatMap 扁平化 List flat = nested.stream() .flatMap(List::stream) .collect(Collectors.toList()); // 结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]
💡 记忆:map 是一对一,flatMap 是一对多并扁平化。
6. Optional 类的作用和使用场景? 中等

Optional:一个容器对象,可能包含或不包含非 null 值,用于优雅处理 null。

// 创建 Optional Optional opt1 = Optional.of("hello"); // 不能为 null Optional opt2 = Optional.ofNullable(null); // 可以为 null Optional opt3 = Optional.empty(); // 空 Optional // 判断是否有值 if (opt1.isPresent()) { System.out.println(opt1.get()); } // 推荐:使用 ifPresent opt1.ifPresent(System.out::println); // 提供默认值 String value1 = opt2.orElse("default"); // 有值返回值,无值返回默认 String value2 = opt2.orElseGet(() -> "default"); // 懒加载默认值 String value3 = opt2.orElseThrow(() -> new RuntimeException("No value")); // 转换值 Optional length = opt1.map(String::length); // 过滤 Optional filtered = opt1.filter(s -> s.length() > 3); // 链式调用 String result = Optional.ofNullable(getUserName()) .map(String::toUpperCase) .orElse("UNKNOWN");

使用场景:

  • 方法返回值可能为 null
  • 避免显式的 null 检查
  • 链式调用,代码更优雅
⚠️ 注意:不要在字段、方法参数中使用 Optional,只用于返回值。
7. 接口的默认方法和静态方法是什么? 简单

默认方法(default):接口中有具体实现的方法,实现类可以不重写。

静态方法(static):接口自己的工具方法,通过接口名调用。

interface MyInterface { // 抽象方法 void abstractMethod(); // 默认方法(JDK 8+) default void defaultMethod() { System.out.println("Default implementation"); } // 静态方法(JDK 8+) static void staticMethod() { System.out.println("Static method"); } // 私有方法(JDK 9+) private void privateMethod() { System.out.println("Private helper"); } } // 使用 class MyClass implements MyInterface { @Override public void abstractMethod() { System.out.println("Abstract implementation"); } // 可以不重写 defaultMethod } MyClass obj = new MyClass(); obj.abstractMethod(); obj.defaultMethod(); // 调用默认方法 MyInterface.staticMethod(); // 调用静态方法
💡 作用:允许接口演进,添加新方法而不破坏现有实现类。
8. 新的日期时间 API 有什么优势? 中等

新 API(java.time):JDK 8 引入,替代旧的 Date 和 Calendar。

// 1. LocalDate:日期(年月日) LocalDate date = LocalDate.now(); LocalDate birthday = LocalDate.of(1990, 1, 1); LocalDate tomorrow = date.plusDays(1); // 2. LocalTime:时间(时分秒) LocalTime time = LocalTime.now(); LocalTime noon = LocalTime.of(12, 0, 0); // 3. LocalDateTime:日期时间 LocalDateTime dateTime = LocalDateTime.now(); LocalDateTime specific = LocalDateTime.of(2024, 1, 1, 12, 0); // 4. ZonedDateTime:带时区的日期时间 ZonedDateTime zonedDateTime = ZonedDateTime.now(); ZonedDateTime tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo")); // 5. Duration:时间间隔 Duration duration = Duration.between(time, noon); long seconds = duration.getSeconds(); // 6. Period:日期间隔 Period period = Period.between(birthday, date); int years = period.getYears(); // 7. DateTimeFormatter:格式化 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String formatted = dateTime.format(formatter); LocalDateTime parsed = LocalDateTime.parse("2024-01-01 12:00:00", formatter);

优势:

  • 不可变:线程安全
  • 清晰:API 设计更合理
  • 功能强大:支持时区、格式化、计算
⚠️ 注意:不要再使用 Date、Calendar、SimpleDateFormat,它们不是线程安全的。
9. Stream 的并行流是什么?什么时候使用? 中等

并行流(Parallel Stream):利用多核 CPU 并行处理数据,提高性能。

List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 串行流 long sum1 = numbers.stream() .mapToLong(Integer::longValue) .sum(); // 并行流 long sum2 = numbers.parallelStream() .mapToLong(Integer::longValue) .sum(); // 或者 long sum3 = numbers.stream() .parallel() .mapToLong(Integer::longValue) .sum(); // 性能对比 long start = System.currentTimeMillis(); IntStream.range(0, 1000000) .parallel() .map(i -> i * 2) .sum(); long end = System.currentTimeMillis(); System.out.println("Time: " + (end - start) + "ms");

使用场景:

  • 适合:数据量大、计算密集型、无状态操作
  • 不适合:数据量小、IO 密集型、有状态操作
⚠️ 注意:并行流使用 ForkJoinPool,线程数默认为 CPU 核数。不要在并行流中使用非线程安全的操作。
10. var 关键字的作用和限制? 简单

var(JDK 10+):局部变量类型推断,编译器自动推断类型。

// 传统写法 String name = "Alice"; List list = new ArrayList<>(); Map map = new HashMap<>(); // 使用 var var name = "Alice"; // 推断为 String var list = new ArrayList(); // 推断为 ArrayList var map = new HashMap(); // 推断为 HashMap // 复杂类型 var stream = list.stream().filter(s -> s.length() > 3); var result = stream.collect(Collectors.toList());

限制:

  • 只能用于局部变量
  • 必须初始化
  • 不能用于字段、方法参数、返回值
  • 不能推断为 null
// 错误示例 var x; // 错误:必须初始化 var y = null; // 错误:不能推断为 null public var method() { } // 错误:不能用于返回值
💡 使用建议:当类型明显时使用 var,提高代码简洁性;当类型不明显时,显式声明类型。
11. Record 类是什么?有什么优势? 中等

Record(JDK 14+):不可变数据类,自动生成构造器、getter、equals、hashCode、toString。

// 传统写法:需要大量样板代码 public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public boolean equals(Object o) { /* ... */ } @Override public int hashCode() { /* ... */ } @Override public String toString() { /* ... */ } } // Record 写法:一行搞定 public record Person(String name, int age) { } // 使用 Person person = new Person("Alice", 30); System.out.println(person.name()); // Alice System.out.println(person.age()); // 30 System.out.println(person); // Person[name=Alice, age=30] // 自定义方法 public record Person(String name, int age) { // 紧凑构造器 public Person { if (age < 0) { throw new IllegalArgumentException("Age cannot be negative"); } } // 自定义方法 public String greeting() { return "Hello, " + name; } }

优势:

  • 代码简洁,减少样板代码
  • 不可变,线程安全
  • 自动实现 equals、hashCode、toString
  • 语义清晰,表示纯数据载体
12. 模块化系统(Jigsaw)是什么?有什么作用? 困难

模块化系统(JDK 9+):将 JDK 和应用程序划分为模块,提高封装性和可维护性。

// module-info.java module com.example.myapp { // 导出包,供其他模块使用 exports com.example.myapp.api; // 依赖其他模块 requires java.sql; requires java.logging; // 使用服务 uses com.example.myapp.spi.MyService; // 提供服务实现 provides com.example.myapp.spi.MyService with com.example.myapp.impl.MyServiceImpl; }

核心概念:

  • 模块:一组相关的包和资源
  • exports:导出包,供其他模块使用
  • requires:依赖其他模块
  • opens:允许反射访问

优势:

  • 强封装:只导出需要的包,隐藏内部实现
  • 可靠配置:编译时检查依赖
  • 减小体积:只打包需要的模块
  • 提高性能:JVM 可以优化模块加载
💡 实际应用:大型项目可以使用模块化提高可维护性,小型项目可以不使用。
13. Switch 表达式有什么新特性?(JDK 12+) 中等

Switch 表达式(JDK 12+):增强的 switch,支持表达式、箭头语法、模式匹配。

// 传统 switch 语句 String day = "MONDAY"; String result; switch (day) { case "MONDAY": case "FRIDAY": case "SUNDAY": result = "6"; break; case "TUESDAY": result = "7"; break; default: result = "8"; break; } // 新 switch 表达式(箭头语法) String result = switch (day) { case "MONDAY", "FRIDAY", "SUNDAY" -> "6"; case "TUESDAY" -> "7"; default -> "8"; }; // 多行代码块 String result = switch (day) { case "MONDAY", "FRIDAY", "SUNDAY" -> { System.out.println("Weekend or Monday"); yield "6"; } case "TUESDAY" -> { System.out.println("Tuesday"); yield "7"; } default -> "8"; };
💡 最佳实践:优先使用新的 switch 表达式,代码更简洁、更安全。

🏆 大厂面试真题专区

以下题目来自阿里巴巴、字节跳动、腾讯、美团等知名企业的真实面试。

14. 【阿里巴巴】Stream API 在什么情况下会导致性能问题?如何优化? 困难

性能问题场景:

  1. 小数据集使用并行流
  2. 频繁装箱拆箱
  3. 多次遍历
  4. 有状态操作(sorted、distinct)
// ❌ 性能问题 long sum = numbers.parallelStream() // 小数据集用并行流 .map(n -> n * 2) // 装箱 .reduce(0, Integer::sum); // ✅ 优化方案 long sum = numbers.stream() .mapToInt(Integer::intValue) // 避免装箱 .map(n -> n * 2) .sum();
💡 经验:数据量 < 1000 用串行流,> 10000 考虑并行流。
15. 【字节跳动】Lambda 中的变量为什么必须是 final? 困难

原因:

  1. Lambda 捕获的是变量的值,不是变量本身
  2. 局部变量在栈上,方法结束后销毁
  3. Lambda 可能在方法结束后执行
  4. 保证线程安全和语义一致性
// ❌ 错误 int count = 0; Runnable r = () -> System.out.println(count); count++; // 编译错误 // ✅ 正确:使用数组或 AtomicInteger int[] count = {0}; Runnable r = () -> { count[0]++; System.out.println(count[0]); };
16. 【腾讯】Optional 的 orElse 和 orElseGet 有什么区别? 困难

关键区别:

  • orElse:无论是否有值,都会执行参数表达式
  • orElseGet:只有为空时才执行 Supplier
Optional opt = Optional.of("hello"); // orElse:总是执行 String r1 = opt.orElse(expensiveOperation()); // 总是执行 // orElseGet:懒加载 String r2 = opt.orElseGet(() -> expensiveOperation()); // 不执行
💡 建议:默认值是常量用 orElse,需要计算用 orElseGet。

© 2024 真编程学习平台 | Java 新特性面试题总览