Java 8面试题

2025 年 4 月 17 日 | 19 分钟阅读

1. Java 8 中引入了哪些主要特性?

Java 8 引入了几个新特性,包括:

  • Lambda 表达式:它用于实现函数式编程。
  • 函数式接口:只有一个抽象方法的接口 (SAM)。
  • Stream API:用于以函数式风格处理集合。
  • Optional 类:它有助于避免 NullPointerException
  • 接口中的默认方法和静态方法:它允许接口拥有方法实现。
  • 新的日期-时间 API:它提供了更好的日期和时间处理。
  • Collectors API:它有助于对集合执行归约操作。

2. Java 8 中的 Lambda 表达式是什么?

Lambda 表达式是编写 匿名函数 的一种简短方式。它有助于编写更简洁、更易读的代码。

示例

Lambda 表达式减少了样板代码,主要与函数式接口一起使用。


3. 什么是函数式接口?

函数式接口是只有一个 抽象方法 的接口。Java 8 引入了 @FunctionalInterface 注解来强制执行此规则。

示例

一些内置的函数式接口是:

  1. Runnable
  2. Callable
  3. 谓词
  4. 函数
  5. Consumer
  6. Supplier

4. Java 8 中的 Stream API 是什么?

Stream API 有助于高效地处理集合。它提供了函数式风格的操作,如 filter、map、reduce、collect 和 forEach

示例

Stream 的主要特性:

  1. 不修改原始集合。
  2. 支持并行处理以获得更好的性能。
  3. 使用惰性求值(操作仅在需要时执行)。

5. Optional 类的目的是什么?

Optional 类通过将值包装在容器中来帮助避免 NullPointerException。

示例

Optional 中的方法:

  1. of(): 为非空值创建一个 Optional。
  2. ofNullable(): 为可空值创建一个 Optional。
  3. isPresent(): 检查是否存在值。
  4. orElse(): 如果 Optional 为空,则返回一个默认值。
  5. orElseGet(): 使用 Supplier 函数返回一个默认值。

6. Java 8 接口中的默认方法是什么?

在 Java 8 之前,接口只能有抽象方法。使用 Java 8,我们现在可以在接口内部拥有默认方法。

示例

  • 允许向后兼容性,而不会破坏现有代码。
  • 有助于为多个类提供通用功能。

7. Java 8 中的 Collectors 是什么?

Collectors 有助于将流元素归约为集合或单个值。

示例

一些常见的 Collectors 是:

  1. toList(): 将元素收集到 List 中。
  2. toSet(): 将元素收集到 Set 中。
  3. joining(): 将元素连接成一个 String。
  4. groupingBy(): 根据条件对元素进行分组。

8. Java 8 如何改进内存管理?

  • 引入 Metaspace 而不是 PermGen(消除内存泄漏)。
  • 改进的垃圾回收 (G1 GC、Parallel GC、ZGC)。
  • 紧凑字符串以提高内存效率。

9. 我们可以在 Java 8 中覆盖默认方法吗?

  • 是的,默认方法可以在实现类中被覆盖。
  • 如果两个接口具有相同的默认方法,则实现类必须显式覆盖它。

10. Java 8 如何处理接口中的多个默认方法?

如果一个类实现了多个包含相同默认方法的接口,Java 如何解决冲突?

示例


11. Stream 的特点是什么?

Java Stream 具有四个主要特点:

  • 惰性执行: 操作仅在调用终端操作时才执行。
  • 管道化: 中间操作返回另一个流,允许方法链式调用。
  • 函数式: 与 map()、filter() 等函数式编程概念一起工作。
  • 可并行化: 支持使用 parallelStream() 进行并行执行。

12. Java 8 如何提高大型集合的性能?

  • 并行流 – 分割数据以加快执行速度。
  • 惰性求值: 仅处理必要的元素。
  • 更好的垃圾回收: 使用 Metaspace 而不是 PermGen。
  • 高效的数据结构: 示例:ConcurrentHashMap.computeIfAbsent()。

13. 如何处理 Lambda 内部的受检异常?

由于 Lambda 表达式 不能抛出受检异常,我们必须将它们包装在 try-catch 中或使用包装函数。

示例


14. 如何在 Java 8 中创建无限流?

使用 Stream.iterate() 或 Stream.generate()。

示例(生成斐波那契序列):

它生成斐波那契数列的前 10 项。


15. Java 8 中 map() 和 flatMap() 有什么区别?

特点map()flatMap()
输入它将一个值流作为输入。它将一个流的流(嵌套数据)作为输入。
输出它将每个元素转换为单个值。它将嵌套流扁平化为单个流。
映射类型:它使用一对一映射。它使用一对多映射。
使用场景当每个输入产生一个输出时使用。当每个输入产生多个输出时使用。
返回值它返回一个转换后的元素流。它返回一个扁平化流,其元素可能比原始流多或少。

16. Java 8 中的方法引用是什么?

Java 8 中的方法引用是 Lambda 表达式的简写形式,用于直接调用方法。它引用 对象 的方法。它可以与内置的函数式接口一起使用。下面列出了四种类型的方法引用:

  • 静态方法引用
  • 特定对象的实例方法引用
  • 引用属于特定类的未指定对象的实例方法。
  • 构造函数引用。

17. Collections.synchronizedList() 和 CopyOnWriteArrayList() 有什么区别?

Collections.synchronizedList(): 它包装任何 List 实现并通过同步方法使其线程安全。但是,由于同步,它可能会导致性能开销。

示例

CopyOnWriteArrayList(): 它是 ArrayList 的线程安全变体,允许读取操作并发执行而无需同步。但是,写入操作(添加、删除)很慢,因为每次修改都会复制列表。

示例

特性Collections.synchronizedList()CopyOnWriteArrayList()
线程安全它通过同步列表上的所有操作来提供线程安全性。它通过在写入操作期间创建列表的新副本提供线程安全性。
性能由于同步开销,频繁读取速度较慢。针对频繁读取和不频繁写入的场景进行优化。
写入操作:它直接修改原始列表。它修改列表,但每次写入操作都会创建一个新列表。
用例它适用于通用线程安全列表。它非常适合读写比高的列表,例如事件监听器列表。
迭代它在迭代期间需要手动同步以避免 ConcurrentModificationException。它允许安全迭代而无需手动同步,因为它在列表的副本上工作。

18. Java 8 中的 default 关键字是什么?

default 关键字允许我们在 接口 中定义带有方法体的方。它对于为接口中的方法提供默认实现很有用,从而更容易扩展接口而不会破坏实现该接口的现有类。

示例


19. Java 8 中 reduce() 和 collect() 有什么区别?

reduce(): reduce() 是一个终端操作,它将流的元素组合成一个结果。它接受一个二进制操作符来累积值。

示例

collect(): collect() 也是一个终端操作,但用于将元素累积到 List、Set 或 Map 等集合中。它为不同类型的集合操作提供了更大的灵活性。

示例

特性reduce()collect()
目的它将流的所有元素组合成一个结果。将元素收集到 List、Set 或 Map 等容器中。它更多的是收集项目而不是组合它们。
输出它产生一个单一的值。它产生一个集合或复杂的结果。
灵活性它仅限于简单的归约操作。它高度灵活,支持可变归约。
使用场景当我们想要组合或聚合元素时使用,例如添加数字或查找最大值。当我们需要将元素收集到集合中时使用,例如将名称收集到列表中。

20. Java 8 Stream 中的 peek() 方法是什么?

peek() 方法是一个中间操作,用于 调试 或检查元素在流中通过时的状态。它不修改流,但允许我们对元素进行一些操作。

示例


21. Java 8 中 forEach() 方法有什么用?

Java 8 中的 forEach() 方法用于使用 Consumer 函数式接口迭代集合。它通过替换传统循环来简化迭代。它通常与 List 和 Stream 一起用于对元素执行操作。

示例


22. Java 8 中 findFirst() 和 findAny() 方法的目的是什么?

Stream 中常用的两种方法是 findFirst() 和 findAny()。

findFirst(): 该方法总是返回流中的第一个元素。它确保保留顺序,这意味着所选元素是列表中的第一个元素。它在处理顺序很重要的顺序流时很有用。

findAny(): 该方法返回流中的任意一个元素。它不遵循顺序,这意味着所选元素可以是任何可用的元素。它针对并行流进行了优化,在某些情况下速度更快。

示例

编译并运行

输出

 
3
8   

23. Java 8 中 forEach() 和 forEachOrdered() 有什么区别?

方面forEach()forEachOrdered()
顺序它不保证处理顺序。它保证按遇到顺序处理。
并行流:它可能以无序方式处理元素。它按源的顺序处理元素。
性能由于缺乏顺序,它在并行流中速度更快。由于强制执行顺序,它在并行流中稍慢。
用例当顺序不重要时应使用。当保持顺序至关重要时应使用。

24. Java 8 中谓词和函数有什么区别?

特性谓词函数
目的谓词用于测试某事是否为真或为假。它返回一个布尔结果。它通常用于过滤或检查条件等情况。函数用于将一个对象转换或转换为另一种类型的对象,它返回的结果可能与输入不同。
输入它接受类型 T 的参数。它接受类型 T 的参数。
输出返回布尔值。返回类型 R 的值。
抽象方法test(T t)apply(T t)
用例用于过滤或条件检查。用于转换数据。

25. Java 8 中 Stream 和 Parallel Stream 有什么区别?

方面Stream并行流:
处理它的处理是顺序的(单线程)。它的处理是并行的(多线程)。
性能它适用于较小的数据集。它适用于较大的数据集。
顺序它在处理过程中保持顺序。它不保留顺序,除非处理。
开销最小的线程管理开销。由于线程管理而产生的开销。
线程使用:它使用主线程。它使用 ForkJoinPool 进行多线程处理。
创建它使用 .stream()。它使用 .parallelStream() 或 .paralle()。
用例当顺序至关重要时使用。当性能优于顺序重要性时使用。

26. Java Streams 中 limit() 和 skip() 有什么区别?

Java Stream 中,limit(n) 和 skip(n) 用于帮助控制处理多少元素。

limit(n):只获取前 N 个元素:

  • 它只保留流中的前 n 个元素。
  • 如果流的元素少于 n 个,它将返回所有元素。
  • 当您需要获取固定数量的结果时很有用,例如在分页中。

skip(n):忽略前 N 个元素:

  • 它删除前 n 个元素并返回其余元素。
  • 如果 n 等于或超过总元素数,它会返回一个空流。
  • 通常用于跳过不需要的结果,例如在实现分页时。
特性limit()skip()
功能将流限制为前 n 个元素。它跳过前 n 个元素并处理其余元素。
用例当我们只想获取前几个元素时使用。当我们想跳过初始元素并处理剩余元素时使用。
元素顺序:它保持前 n 个元素的顺序。它在跳过之后保持剩余元素的顺序。
行为它在处理指定数量的元素后截断流。它从流的开头丢弃指定数量的元素。
短路:一旦达到限制,它就会停止处理。它在跳过指定数量后处理所有元素。

27. 如何在 Java 8 中从列表中删除重复项?

在 Java 8 中,我们可以使用 Stream API 的 distinct() 方法从列表中删除重复元素。

示例

编译并运行

输出

 
[1, 2, 3, 4, 5] 

28. Java 8 中的 removeIf() 方法是什么?

Java 8 中的 removeIf() 方法允许我们根据条件从集合中删除元素。它是 Collection 接口中的一个默认方法,可以轻松删除元素而无需使用显式迭代器或循环。

示例

想象一下我们有一个数字列表,并且想要删除所有偶数。我们可以使用 removeIf() 和 Lambda 表达式,而不是使用 for 循环:

示例

编译并运行

输出

 
[1, 3, 5]

29. 我们可以在 Java 8 的 switch 语句中使用 Lambda 表达式吗?

Java 8 不支持在 switch 语句中使用 Lambda 表达式。switch 语句旨在处理特定值,如数字、字符、字符串和枚举。


30. 为什么 Java 8 不允许在 switch 语句中使用 Lambda 表达式?

switch 语句检查一个值并将其与不同的 case 标签匹配。由于 Lambda 不是值,因此它不能用作 case 标签。这就是 Java 8 不允许它的原因。

尽管我们不能在 switch 中使用 Lambda,但我们可以使用带有函数式接口的 Map 实现相同的效果。它允许我们根据键执行不同的代码片段,类似于 switch。

示例:使用 Map 代替 Switch

示例

编译并运行

输出

 
Hello, how are you?

31. 如何在 Java 8 中将流转换为数组?

在 Java 8 中,我们可以使用 toArray() 方法 将 Stream 转换为数组。当您想将元素作为 Stream 处理但将结果存储在 数组 中时,这很有用。

示例

我们有一个包含一些字符串值的 Stream,我们想将其转换为字符串数组。

示例

编译并运行

输出

 
[A, B, C]

32. Java 8 中的 Stream.concat() 是什么?

在 Java 8 中,Stream.concat() 用于将两个流合并为一个流。与其手动迭代和组合两个集合,我们可以使用此方法将两个流的元素作为一个连续流处理。

示例:合并两个流

示例

编译并运行

输出

 
ABCD

33. Java 8 中的 Collectors.groupingBy() 是什么?

Collectors.groupingBy() 方法在 Java 8 中根据分类函数对流的元素进行分组。当您想根据某些属性对数据进行分类时,这很有用,例如按长度对单词进行分组或按奇偶性对数字进行分组。

示例:按长度对单词进行分组

示例

编译并运行

输出

 
{3=[lion, dog, tiger], 8=[elephant]}

34. Java 8 中的 Collectors.joining() 是什么?

Collectors.joining() 方法用于将 Stream 的元素连接(组合)成一个 String。当您想将单词或值的列表转换为单个格式化字符串时,这特别有用。

示例:用空格连接单词

示例

编译并运行

输出

 
Java 8 Features

35. Java 8 中的 Collectors.partitioningBy() 是什么?

Collectors.partitioningBy() 方法用于根据给定条件将集合分成两组。它创建一个包含两个列表的 Map,其中:

  • 键为 true: 满足条件的元素。
  • 键为 false: 不满足条件的元素。

当您想将数据分成两类时,这很有用,例如偶数与奇数、及格与不及格的学生等。

示例:将数字分成偶数和奇数组

示例

编译并运行

输出

 
{false=[1, 3, 5], true=[2, 4]}

36. 使用 Java 8 打印当前日期和时间最简单的方法是什么?

LocalDate 的 now 方法可用于获取当前日期,如下所示:

同样,它也可以用于获取当前时间:

要同时获取当前日期和时间,我们可以使用 LocalDateTime:

如果我们想要特定格式的日期和时间,我们可以使用 DateTimeFormatter:


37. Java 8 新的日期和时间 API 修复了哪些问题?

在 Java 8 之前,处理日期和时间并不容易。由于许多问题,使用 Date 和 Calendar 类变得很困难。Java 8 添加了 java.time 包来解决这些问题。

可变性问题:

  • 旧的 Date 类允许在创建后进行更改,这导致了多线程系统中的意外问题。
  • Java 8 通过使 LocalDate 和 LocalTime 不可变来修复此问题,这意味着一旦它们被创建,就不能更改。

线程安全问题:

  • 在多个线程中使用 SimpleDateFormat 可能会导致问题,因为它不是线程安全的。
  • Java 8 引入了 DateTimeFormatter,它在许多线程中都可以安全使用。

不清晰的 API:

  • 以前 API 中不方便的方法,如 getYear() 返回:

38. Java 8 中的 PermGen 和 Metaspace 是什么?

在 Java 8 之前,Java 依赖 PermGen 存储有关类、方法和元数据等信息。PermGen 的问题在于其固定大小,在 JVM 启动时设置。如果应用程序加载了太多类,它很容易耗尽空间,导致烦人的崩溃和错误。这使得处理频繁加载和卸载类的应用程序(如 Web 应用程序)变得困难。

Java 8 引入了 Metaspace,解决了这些问题。Metaspace 不再局限于固定大小的存储系统,而是使用本机内存并根据加载的类数量自动调整其大小。这就像从狭窄的壁橱升级到可伸缩的存储单元,它会根据我们的需要增长或缩小。这种灵活性不仅使内存管理更容易,还减少了应用程序遇到内存问题的可能性。


39. 中间操作和终端操作有什么区别?

中间操作: 它修改或过滤流,但不会立即产生最终结果。它们是惰性的,这意味着它们直到调用终端操作才会执行。这里有一些常见的例子包括 map()、filter()、sorted() 和 distinct()。

示例

在这里,filter() 方法是一个中间操作,但在我们使用终端操作之前什么都不会发生。

终端操作: 这些触发流管道的执行并产生结果或执行操作。一旦使用终端操作,流就不能重复使用。它包括 collect()、forEach()、count() 和 reduce() 方法。

示例

在这里,count() 方法是一个终端操作,它触发 filter() 方法的执行并计算偶数。

特性中间操作终端操作
目的它将一个流转换为另一个流。它产生结果或副作用。
执行它直到调用终端操作才执行。它触发流的处理。
返回值它总是返回一个新流,允许进一步的流操作。它返回一个非流值(如列表、int)或无值。
链式调用:它可以链式调用以形成管道。它不能链式调用;它标志着管道的结束。
示例map()、filter()、distinct()、sorted() 是中间操作。forEach()、collect()、reduce()、count() 是终端操作。

40. 流管道用于什么?

Java 8 中的流管道允许我们将多个操作链接在一起以处理数据流。我们可以通过单个流执行一系列转换和计算,而不是手动逐个处理元素。

该概念基于两种类型的操作:

  • 中间操作
  • 终端操作

流管道示例:

在这里,filter() 方法处理数据但不会立即执行。count() 方法是一个终端操作,它触发整个管道并返回结果。

简单来说,流管道通过在单个流中链接多个操作而不是使用循环来编写更简洁、更高效的代码。


41. Java 8 有哪些优点?

  • 代码紧凑、可读且可重用。
  • 它避免了样板代码。
  • 它允许同时进行并行操作和执行。
  • 它可以在操作系统之间移植。
  • 它非常稳定。
  • 它有一个稳定的环境。

42. 举一个静态方法引用的例子,并说明语法。

在 Java 8 中,方法引用是 Lambda 表达式的简写形式,用于直接引用方法。

语法

示例


43. 接口中的静态方法是什么?

静态方法包含接口拥有的方法实现。这些方法使用接口的名称调用。它适用于定义实用程序方法。这些方法不能被覆盖。


44. String::Valueof 表达式的含义是什么?

String::Valueof() 表达式是对 String 类的 valueOf() 方法的静态方法引用。


45. Java 8 中的 Nashorn 是什么?

Java 8 提供了一个名为 Nashorn 的新 JavaScript 处理引擎。它提供了更好的 ECMA 标准化 JavaScript 规范兼容性,并且比其前身具有更好的运行时性能。

在 Java 8 之前,Java 平台使用 Mozilla Rhino 作为 JavaScript 处理引擎,用于相同的目的。

Nashorn


46. Java 8 中的 JJS 是什么?

它是用于在控制台执行 JavaScript 代码的新可执行文件或命令行工具。


47. 在什么情况下你会使用 Java 8 Stream API?

Java 8 Stream API 非常适合需要以函数式和高效方式处理数据集合的情况。有一些常见场景:

  • 用于执行惰性操作
  • 用于执行数据库操作
  • 用于内部迭代
  • 用于编写函数式编程风格
  • 使用管道操作

48. Type 接口的功能是什么?

在 Java 中,Type 是属于 java.lang.reflect 包的接口。它用于表示更具体的 Java 类型,例如类、接口、数组,甚至是参数化类型。自 Java 8 以来,它定义了一个方法 getTypeName()。该方法返回一个描述此类型的字符串,包括有关任何类型参数的信息。在使用反射时,我们应该使用 Type 接口及其子接口。


49. 说出 Stream 的主要组成部分。

  • 数据源。
  • 用于处理数据源的一组中间操作。
  • 产生结果的单个终端操作。

50. Java 8 中的 SAM 接口是什么?

在 Java 8 中,SAM 代表单一抽象方法接口。它也称为函数式接口。它只包含一个抽象方法,但包含多个静态方法和默认方法。它作为 Lambda 表达式和方法引用的基础。如果我们要将一个接口设为函数式接口,请用 @FunctionalInterface 注解标记它。它提供了一个函数式编程范式。

Java 中 SAM 接口的一些示例是 Runnable、Callable、Comparator 和 ActionListener。