Java 8 绝对是 Java 历史上最重要的稳定版本,也是这么多年来最受欢迎的 Java 版本,甚至有专门的书籍来讲解 Java 8。这个版本最大的变化就是引入了函数式编程的概念,给 Java 这门传统的面向对象语言增加了新的玩法。
⭐️ 正式特性
【必备】Lambda 表达式
什么是 Lambda 表达式?
Lambda 表达式可以说是 Java 8 的杀手级特性。在这个特性出现之前,我们要实现一个简单的回调函数,只能通过匿名内部类的方式,代码又臭又长。
举些例子,比如给按钮添加点击事件、或者创建一个新线程执行操作,必须要自己 new 接口并且编写接口的定义和实现代码。
// Java 8 之前的写法,给按钮添加点击事件
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了");
}
});
// 使用线程的传统写法
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程正在运行");
}
});Lambda 表达式的出现,让代码变得简洁优雅,告别匿名内部类!
// Java 8 Lambda 写法
button.addActionListener(e -> System.out.println("按钮被点击了"));
Thread thread = new Thread(() -> System.out.println("线程正在运行"));Lambda 表达式的语法非常灵活,可以根据参数个数和方法代码的复杂度选择不同的写法:
// 无参数的 Lambda
Runnable r = () -> System.out.println("Hello Lambda!");
// 单个参数(可以省略括号)
Consumer<String> printer = s -> System.out.println(s);
// 多个参数
BinaryOperator<Integer> add = (a, b) -> a + b;
Comparator<String> comparator = (a, b) -> a.compareTo(b);
// 复杂的方法体(需要大括号和 return)
Function<String, String> processor = input -> {
String processed = input.trim().toLowerCase();
if (processed.isEmpty()) {
return "空字符串";
}
return "处理后的字符串:" + processed;
};方法引用
Lambda 表达式还有一个实用特性叫做 方法引用,可以看作是 Lambda 表达式的一种简写形式。当 Lambda 表达式只是调用一个已存在的方法时,使用方法引用代码会更简洁。
举个例子:
List<String> names = Arrays.asList("鱼皮", "编程导航", "面试鸭");
// 使用 Lambda 表达式
names.forEach(name -> System.out.println(name));
// 使用方法引用(更简洁)
names.forEach(System.out::println);实际开发中,方法引用经常用于获取某个 Java 对象的属性。比如使用 MyBatis Plus 来构造数据库查询条件时,经常会看到下面这种代码:
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getName, "鱼皮");方法引用有几种不同的形式,包括静态方法引用、实例方法引用、构造器引用,适用于不同的场景。
// 静态方法引用
List<String> strings = Arrays.asList("1", "2", "3");
List<Integer> numbers = strings.stream()
.map(Integer::parseInt) // 等于 s -> Integer.parseInt(s)
.collect(Collectors.toList());
// 实例方法引用
List<String> words = Arrays.asList("hello", "world", "java");
List<String> upperWords = words.stream()
.map(String::toUpperCase) // 等于 s -> s.toUpperCase()
.collect(Collectors.toList());
// 构造器引用
List<String> nameList = Arrays.asList("鱼皮", "编程导航", "面试鸭");
List<Person> persons = nameList.stream()
.map(Person::new) // 等于 name -> new Person(name)
.collect(Collectors.toList());【必备】函数式接口
什么是函数式接口?
函数式接口是 只有一个抽象方法的接口。要玩转 Lambda 表达式,就必须了解函数式接口,因为 Lambda 表达式的本质是函数式接口的匿名实现。
展开来说,函数式接口定义了 Lambda 表达式的参数和返回值类型,而 Lambda 表达式提供了这个接口的具体实现。两者相辅相成,让 Java 函数式编程伟大!
常用的函数式接口
Java 8 为我们提供了很多内置的函数式接口,让函数式编程变得简单直观。列举一些常用的函数式接口:
1)Predicate 用于条件判断:
// Predicate<T> 用于条件判断
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate(); // 取反
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(isEven)
.collect(Collectors.toList());2)Function 接口用于数据转换,支持函数组合,让代码逻辑更清晰:
// Function<T, R> 用于转换
Function<String, Integer> stringLength = String::length;
Function<Integer, String> intToString = Object::toString;
// 函数组合
Function<String, String> addPrefix = s -> "前缀-" + s;
Function<String, String> addSuffix = s -> s + "-后缀";
Function<String, String> combined = addPrefix.andThen(addSuffix);
String result = combined.apply("鱼皮"); // "前缀-鱼皮-后缀"3)Consumer 和 Supplier 接口分别用于消费和提供数据:
// Consumer<T> 用于消费数据(无返回值)
Consumer<String> printer = System.out::println;
Consumer<String> logger = s -> log.info("处理数据:{}", s);
// 组合消费
Consumer<String> combinedConsumer = printer.andThen(logger);
// Supplier<T> 用于提供数据
Supplier<String> randomId = () -> UUID.randomUUID().toString();
Supplier<LocalDateTime> now = LocalDateTime::now;4)BinaryOperator 接口用于二元操作,比如数学运算:
// BinaryOperator<T> 用于二元操作
BinaryOperator<Integer> max = Integer::max;
BinaryOperator<String> concat = (a, b) -> a + b;自定义函数式接口
虽然实际开发中,我们更多的是使用 Java 内置的函数式接口,但大家还是要了解一下自定义函数式接口的写法,有个印象。
// 创建自定义函数式接口
@FunctionalInterface
public interface Calculator {
double calculate(double a, double b);
}使用自定义函数式接口,代码会更简洁:
// 使用自定义函数式接口
Calculator addition = (a, b) -> a + b;
Calculator subtraction = (a, b) -> a - b;💡 自定义函数式接口时,需要注意:
1)函数式接口必须是接口类型,不能是类、抽象类或枚举。
2)必须且只能包含一个抽象方法。否则 Lambda 表达式可能无法匹配接口。
3)建议使用 @FunctionalInterface注解。
虽然这个注解不是强制的,但加上后编译器会帮你检查是否符合函数式接口的规范(是否只有一个抽象方法),如果不符合会报错。
4)可以包含默认方法 default 和静态方法 static
函数式接口允许有多个默认方法和静态方法,因为它们不是抽象方法,不影响单一抽象方法的要求。
// 创建自定义函数式接口
@FunctionalInterface
public interface Calculator {
double calculate(double a, double b);
// 可以有默认方法
default double add(double a, double b) {
return a + b;
}
// 可以有静态方法
static Calculator multiply() {
return (a, b) -> a * b;
}
}【必备】Stream API
什么是 Stream API?
Stream API 是 Java 8 另一个重量级特性,它让集合处理变得既优雅又高效。(学大数据的同学应该对它不陌生)
在 Stream API 出现之前,我们处理集合数据只能通过传统的循环,需要大量的样板代码。
比如过滤列表中的数据、将小写转为大写并排序:
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 传统的处理方式
List<String> result = new ArrayList<>();
for (String word : words) {
if (word.length() > 5) {
String upperCase = word.toUpperCase();
result.add(upperCase);
}
}
Collections.sort(result);如果使用 Stream API,可以让同样的逻辑变得更简洁直观:
// 使用 Stream 的方式
List<String> result = words.stream()
.filter(word -> word.length() > 5) // 过滤长度大于 5 的单词
.map(String::toUpperCase) // 转换为大写
.sorted() // 排序
.collect(Collectors.toList()); // 收集结果这就是 Stream 的作用。Stream 不是数据结构,而是 像工厂流水线 一样处理数据的工具。数据从一端进入,经历过滤、转换、排序等一系列加工步骤后,最终输出我们想要的结果。这种 链式调用 让代码读起来就像自然语言一样流畅。

Stream 操作类型
Stream 的操作分为中间操作和终端操作。中间操作是 "懒惰" 的,只有在遇到终端操作时才会真正执行。
filter 过滤和 map 映射都是中间操作,比如下面这段代码,并不会对列表进行过滤和转换:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
.filter(n -> n > 3) // 中间操作:过滤大于3的数字
.map(n -> n * n) // 中间操作:平方一些常用的中间操作:
filter()- 过滤元素map()- 转换元素sorted()- 排序distinct()- 去重limit()- 限制数量skip()- 跳过元素
给上面的代码加上一个终端操作 collect 后,才会真正执行:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 演示中间操作和终端操作
numbers.stream()
.filter(n -> n > 3) // 中间操作:过滤大于3的数字
.map(n -> n * n) // 中间操作:平方
.collect(Collectors.toList()); // 终端操作:收集结果一些常用的终端操作:
collect()- 收集到集合forEach()- 遍历每个元素count()- 统计数量findFirst()- 查找第一个anyMatch()- 是否有匹配的reduce()- 归约操作
实际应用
分享一些 Stream API 在开发中的典型用例。
1)对列表进行分组(List 转为 Map):
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.filter(n -> n > 3) // 中间操作:过滤大于3的数字
.map(n -> n * n) // 中间操作:平方
.collect(Collectors.partitioningBy(n -> n % 2 == 0)); // 终端操作:按奇偶分组2)使用 Stream 内置的统计功能,对数据进行统计:
// 统计操作
IntSummaryStatistics stats = numbers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("数量:" + stats.getCount());
System.out.println("总和:" + stats.getSum());
System.out.println("平均值:" + stats.getAverage());
System.out.println("最大值:" + stats.getMax());
System.out.println("最小值:" + stats.getMin());3)按照对象的某个字段进行分组计算:
List<Person> people = Arrays.asList(
new Person("张三", 25, "北京"),
new Person("鱼皮", 18, "上海"),
new Person("李四", 25, "北京"),
new Person("老二", 35, "上海")
);
// 按城市分组
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
// 按城市分组并统计年龄
Map<String, Double> avgAgeByCity = people.stream()
.collect(Collectors.groupingBy(
Person::getCity,
Collectors.averagingInt(Person::getAge)
));
// 按城市分组并收集姓名
Map<String, List<String>> namesByCity = people.stream()
.collect(Collectors.groupingBy(
Person::getCity,
Collectors.mapping(Person::getName, Collectors.toList())
));学过数据库的同学应该对这种操作并不陌生,其实 SQL 语句中的很多操作都可以通过 Stream 实现。这也是 Stream 的典型应用场景 —— 对数据库中查出的数据进行业务层面的运算
并行流
并行流是 Stream API 的另一个强大特性,它可以自动利用多核 CPU 处理器加速数据处理任务的执行。
在此之前,我们要实现并行处理集合数据,需要手动管理线程池和任务分割,代码复杂且容易出错。
但有了 Stream API,一行代码就能创建并行流,比如过滤并计算数据的总和:
List<Integer> largeList = IntStream.rangeClosed(1, 1000000)
.boxed()
.collect(Collectors.toList());
// 并行处理,只需要改一个方法调用
long parallelCount = largeList.parallelStream()
.filter(n -> isPrime(n))
.count();并行流底层使用了 Fork/Join 框架,简单来说就是把大任务拆分成小任务,分配给多个线程同时执行,最后把结果合并起来。这个过程对开发者完全透明,只需要调用 parallelStream() 即可。

但也正因如此,实际开发中,要谨慎使用并行流!
因为它使用的是 JVM 全局的 ForkJoinPool.commonPool(),默认线程数等于 CPU 核心数减 1。如果某个并行流任务阻塞了线程,会影响其他并行流的性能。
而且并行流不一定就更快,特别是对于简单操作或小数据集,切换线程的开销可能超过并行带来的收益。
因此,并行流更适合大数据量、CPU 密集型任务(如复杂计算、图像处理),不适合 I/O 密集型任务(如网络请求)。而且只要涉及到并发场景,就要考虑到线程安全问题。
【必备】接口中的默认方法和静态方法
Java 8 引入的接口默认方法解决了接口演化的问题。
在默认方法出现之前,如果你想给一个被广泛使用的接口添加新方法,就会影响所有已有的实现类。想象一下,如果要给 Collection 接口添加一个新方法,ArrayList、LinkedList 等所有的实现类都需要修改,成本很大。
默认方法让接口可以在 不破坏现有代码的情况下添加新功能。
举个例子,如果想要给接口增加一个 drawWithBorder 方法:
public interface Drawable {
// 已有抽象方法
void draw();
// 默认方法
default void drawWithBorder() {
System.out.println("绘制边框");
draw();
System.out.println("边框绘制完成");
}
}使用默认方法后,实现类可以选择重写默认方法,也可以直接使用:
// 实现类可以选择重写默认方法
public class Circle implements Drawable {
@Override
public void draw() {
System.out.println("绘制圆形");
}
// 可以重写默认方法
@Override
public void drawWithBorder() {
System.out.println("绘制圆形边框");
draw();
}
}Java 8 为 Collection 接口添加了 stream、removeIf 等方法,都是默认方法:

需要注意的是,如果一个类实现多个接口,并且这些接口有相同的默认方法时,需要显式解决冲突:
interface A {
default void hello() {
System.out.println("Hello from A");
}
}
interface B {
default void hello() {
System.out.println("Hello from B");
}
}
// 实现类必须重写冲突的方法
class C implements A, B {
@Override
public void hello() {
// 可以调用特定接口的默认方法
A.super.hello();
B.super.hello();
// 或者提供自己的实现
System.out.println("Hello from C");
}
}类似的,Java 8 还支持接口的静态方法:
public interface Utility {
static void printVersion() {
System.out.println("Java 8");
}
static String formatMessage(String message) {
return "[INFO] " + message;
}
}
// 调用接口静态方法
Utility.printVersion();
String formatted = Utility.formatMessage("Hello World");【必备】Optional 类
Optional 的作用
NullPointerException(NPE)一直是 Java 程序员的噩梦,学 Java 的同学应该都被它折磨过。

之前,我们只能通过大量的 if 语句检查 null 来避免空指针异常,不仅代码又臭又长,而且稍微不注意就漏掉了。
// 传统的空值检查
public String getDefaultName(User user) {
if (user != null) {
String name = user.getName();
if (name != null && !name.isEmpty()) {
return name.toUpperCase();
}
}
return "unknown";
}Optional 类的引入就是为了优雅地处理可能为空的值,可以先把它理解为 "包装器",把可能为空的对象封装起来。
创建 Optional 对象:
// 创建 Optional 对象
Optional<String> optional1 = Optional.of("Hello"); // 不能为 null
Optional<String> optional2 = Optional.ofNullable(getName()); // 可能为 null
Optional<String> optional3 = Optional.empty(); // 空的 OptionalOptional 提供了多种处理空值的方法:
// 检查是否有值
if (optional.isPresent()) {
System.out.println(optional.get());
}
// 更优雅的方式,如果对象存在则输出
optional.ifPresent(System.out::println);还可以设置默认值策略,比如空值时抛出异常:
// 提供默认值
String result1 = optional.orElse("默认值");
String result2 = optional.orElseGet(() -> generateDefaultValue());
String result3 = optional.orElseThrow(() -> new IllegalStateException("值不能为空"));除了前面这些基本方法外,Optional 甚至提供了一套完整的 API 来处理空值场景!
跟 Stream API 类似,你可以对 Optional 封装的数据进行过滤、映射等操作:
optional
.filter(s -> s.length() > 5)
.map(String::toUpperCase)
.ifPresentOrElse(
System.out::println, // 有值时执行
() -> System.out.println("没有值") // 无值时执行
);应用场景
鱼皮经常使用 Optional 来简化空值判断:
int pageNum = Optional.ofNullable(params.getPageNum())
.orElseThrow(() -> new RuntimeException("pageNum不能为空"));如果不用 Optional,就要写下面这段代码:VbcIqC1RWAetLyIBuBgD21xetQwHXq5YVHB9NTWM5BE=
int pageNum;
if (params.getPageNum() != null) {
pageNum = params.getPageNum();
} else {
throw new RuntimeException("pageNum不能为空");
}此外,Optional 的一个典型应用场景是在集合中进行安全查找:
List<String> names = Arrays.asList("张三", null, "李四", "", "王五");
// 使用 Optional 进行安全的查找
Optional<String> foundName = names.stream()
.filter(Objects::nonNull)
.filter(name -> name.startsWith("张"))
.findFirst();
foundName.ifPresentOrElse(
name -> System.out.println("找到了:" + name),
() -> System.out.println("没有找到匹配的名字")
);【必备】新的日期时间 API
Java 8 引入的新日期时间 API 解决了旧版 Date 和 Calendar 类的很多问题,比如线程安全、可变性、时区处理等等。
传统的日期处理方式:
// 旧版本的复杂日期处理
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.JANUARY, 15); // 注意月份从0开始
Date date = cal.getTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = sdf.format(date); // 线程不安全使用新的日期时间 API,代码会更简洁:
// 当前日期时间
LocalDate today = LocalDate.now(); // 2025-09-01
LocalTime now = LocalTime.now(); // 14:30:25.123
LocalDateTime dateTime = LocalDateTime.now(); // 2025-09-01T14:30:25.123
// 指定的日期时间
LocalDate specificDate = LocalDate.of(2025, 09, 01);
LocalTime specificTime = LocalTime.of(14, 30, 0);
LocalDateTime specificDateTime = LocalDateTime.of(2025, 09, 01, 14, 30, 0);典型的应用场景是从字符串解析日期,一行代码就能搞定:
// 从字符串解析
LocalDate parsedDate = LocalDate.parse("2025-09-01");
LocalDateTime parsedDateTime = LocalDateTime.parse("2025-09-01T14:30:25");
// 自定义格式解析
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime customParsed = LocalDateTime.parse("2025/09/01 14:30:25", formatter);还有日期和时间的计算,也变得更直观、见名知意:
LocalDate today = LocalDate.now();
// 基本的日期计算
LocalDate nextWeek = today.plusWeeks(1);
LocalDate lastMonth = today.minusMonths(1);
LocalDate nextYear = today.plusYears(1);
// 时间段计算
LocalDate startDate = LocalDate.of(2024, 1, 28);
LocalDate endDate = LocalDate.of(2025, 9, 1);
Period period = Period.between(startDate, endDate);
System.out.println("相差 " + period.getMonths() + " 个月 " + period.getDays() + " 天");
// 精确时间差计算
LocalDateTime start = LocalDateTime.now();
LocalDateTime end = LocalDateTime.of(2025, 09, 01, 14, 30, 0);
Duration duration = Duration.between(start, end);
System.out.println("执行时间:" + duration.toMillis() + " 毫秒");还支持时区处理和时间戳处理,不过这段代码就没必要记了,现在有了 AI,直接让它生成时间日期操作就好。
// 带时区的日期时间
ZonedDateTime beijingTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime newYorkTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
// 时区转换
ZonedDateTime beijingToNewYork = beijingTime.withZoneSameInstant(ZoneId.of("America/New_York"));
// 获取所有可用时区
ZoneId.getAvailableZoneIds().stream()
.filter(zoneId -> zoneId.contains("Shanghai"))
.forEach(System.out::println);
// 时间戳处理
Instant instant = Instant.now();
long epochSecond = instant.getEpochSecond();
ZonedDateTime fromInstant = instant.atZone(ZoneId.systemDefault());总之,有了这套 API,我们不需要使用第三方的时间日期处理库,也能解决大多数问题。
【实用】重复注解
Java 8 之前,同一个注解不能在同一个地方重复使用。如果需要多个相似的配置,只能使用数组形式的注解:
// Java 8 之前的做法
@Schedules({
@Schedule(dayOfMonth="last"),
@Schedule(dayOfWeek="Fri", hour="23")
})
public void doPeriodicCleanup() { ... }Java 8 引入了重复注解特性,让同一个注解可以在同一个地方多次使用:
// Java 8 的重复注解
@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }要实现重复注解,需要定义一个容器注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Schedule {
String dayOfMonth() default "first";
String dayOfWeek() default "Mon";
int hour() default 12;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Schedules {
Schedule[] value();
}然后在 @Schedule 注解上使用 @Repeatable 指定容器注解:
@Repeatable(Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Schedule {
String dayOfMonth() default "first";
String dayOfWeek() default "Mon";
int hour() default 12;
}【实用】类型注解
Java 8 扩展了注解的使用范围,现在注解可以用在任何使用类型的地方,包括:
// 创建对象
@NonNull String str = new @Interned String("Hello");
// 类型转换
String myString = (@NonNull String) str;
// 继承
class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... }
// 抛出异常
void monitorTemperature() throws @Critical TemperatureException { ... }
// 泛型参数
List<@NonNull String> strings = new ArrayList<>();
Map<@NonNull String, @NonNull Integer> map = new HashMap<>();
// 方法参数和返回值
public @NonNull String process(@NonNull String input) {
return input.toUpperCase();
}类型注解主要用于静态分析工具(如 Checker Framework)进行更精确的类型检查,帮助发现潜在的空指针异常、并发问题等。
其他特性
【了解】Nashorn JavaScript 引擎
Java 8 引入了 Nashorn JavaScript 引擎,用来替代老旧的 Rhino 引擎。Nashorn 提供了更好的性能和对 ECMAScript 5.1 的完整支持:
import javax.script.*;
public class NashornExample {
public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// 执行 JavaScript 代码
Object result = engine.eval("var greeting = 'Hello'; greeting + ' World!'");
System.out.println(result); // Hello World!
// 调用 JavaScript 函数
engine.eval("function add(a, b) { return a + b; }");
Invocable invocable = (Invocable) engine;
Object sum = invocable.invokeFunction("add", 5, 3);
System.out.println(sum); // 8
}
}不过 Nashorn 在 Java 11 中已被标记为弃用,Java 15 中完全移除。
【了解】并行数组操作
Java 8 为数组操作添加了并行处理能力,可以利用多核处理器提升大数组的处理性能:
import java.util.Arrays;
int[] array = new int[10000];
Arrays.fill(array, 1);
// 并行设置数组值
Arrays.parallelSetAll(array, index -> index * 2);
// 并行排序
Arrays.parallelSort(array);
// 并行前缀操作(累积计算)
Arrays.parallelPrefix(array, (left, right) -> left + right);【了解】Base64 编码解码支持
Java 8 原生支持 Base64 编码和解码,不再需要第三方库:
import java.util.Base64;
public class Base64Example {
public static void main(String[] args) {
String originalInput = "Hello World!";
// 编码
String encodedString = Base64.getEncoder().encodeToString(originalInput.getBytes());
System.out.println("编码后: " + encodedString);
// 解码
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
String decodedString = new String(decodedBytes);
System.out.println("解码后: " + decodedString);
// URL 安全的编码
String urlEncoded = Base64.getUrlEncoder().encodeToString(originalInput.getBytes());
System.out.println("URL编码: " + urlEncoded);
}
}【了解】紧凑配置文件
Java 8 引入了紧凑配置文件(Compact Profiles),将 Java SE 平台分为三个子集:
compact1:最小配置,包含核心 API
compact2:包含 compact1 + XML、JDBC 等
compact3:包含 compact2 + 管理、命名等 API
这主要用于嵌入式设备或需要减小 JRE 体积的场景,现在已经被 Java 9 的模块系统所取代。
总结
Java 8 是 Java 发展史上的里程碑版本,引入的函数式编程特性彻底改变了 Java 开发的风格。Lambda 表达式、Stream API、Optional 等特性不仅让代码更简洁,也提升了开发效率。新的日期时间 API 解决了长期以来的痛点,接口默认方法则保证了向后兼容性。
这些特性至今仍是现代 Java 开发的基础,值得每个 Java 开发者深入掌握。