Skip to content
<

Java 8 绝对是 Java 历史上最重要的稳定版本,也是这么多⁢年来最受欢迎的 Java 版本,甚至有专门的⁡书籍来讲解 Java 8。这个版本最大的变化⁡就是引入了函数式编程的概念,给 Java 这门传统的面向对象语言增加了新的玩法。

⭐️ 正式特性

【必备】Lambda 表达式

什么是 Lambda 表达式?

Lambda 表达式可以说是 Java ⁢8 的杀手级特性。在这个特⁡性出现之前,我们要实现一个⁡简单的回调函数,只能通过匿名内部类的方式,代码又臭又长。

举些例子,比如给按钮添加点击事⁢件、或者创建一个新线⁡程执行操作,必须要自⁡己 new 接口并且编写接口的定义和实现代码。

java
// 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
// Java 8 Lambda 写法  
button.addActionListener(e -> System.out.println("按钮被点击了"));  
Thread thread = new Thread(() -> System.out.println("线程正在运行"));

Lambda 表达式的语法非⁢常灵活,可以根据参⁡数个数和方法代码的⁡复杂度选择不同的写法:

java
// 无参数的 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 表达式只是调用一个已存在的方法时,使用方法引用代码会更简洁。

举个例子:

java
List<String> names = Arrays.asList("鱼皮", "编程导航", "面试鸭");  
  
// 使用 Lambda 表达式  
names.forEach(name -> System.out.println(name));  
  
// 使用方法引用(更简洁)  
names.forEach(System.out::println);

实际开发中,方法引用经常用于获取某个 J⁢ava 对象的属性。比如使⁡用 MyBatis Plu⁡s 来构造数据库查询条件时,经常会看到下面这种代码:

java
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();  
lambdaQueryWrapper.eq(User::getName, "鱼皮");

方法引用有几种不同的形式,包⁢括静态方法引用、实⁡例方法引用、构造器⁡引用,适用于不同的场景。

java
// 静态方法引用  
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 表达⁢式的参数和返回值类型,而 La⁡mbda 表达式提供了这个接口的具⁡体实现。两者相辅相成,让 Java 函数式编程伟大!

常用的函数式接口

Java 8 为我们提供了很⁢多内置的函数式接口⁡,让函数式编程变得简⁡单直观。列举一些常用的函数式接口:

1)Predicate 用于条件判断:

java
// 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 接口用⁢于数据转换,支持函⁡数组合,让代码逻辑⁡更清晰:

java
// 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 和 S⁢upplier 接⁡口分别用于消费和提⁡供数据:

java
// 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)BinaryOperat⁢or 接口用于二元⁡操作,比如数学运算⁡:

java
// BinaryOperator<T> 用于二元操作  
BinaryOperator<Integer> max = Integer::max;  
BinaryOperator<String> concat = (a, b) -> a + b;

自定义函数式接口

虽然实际开发中,我们更多的是使⁢用 Java 内置的⁡函数式接口,但大家还⁡是要了解一下自定义函数式接口的写法,有个印象。

java
// 创建自定义函数式接口  
@FunctionalInterface  
public interface Calculator {  
    double calculate(double a, double b);  
}

使用自定义函数式接口,代码会更简洁:

java
// 使用自定义函数式接口  
Calculator addition = (a, b) -> a + b;  
Calculator subtraction = (a, b) -> a - b;

💡 自定义函数式接口时,需要注意:

1)函数式接口必须是接口类型,不能是类、抽象类或枚举。

2)必须且只能包含一个抽象方⁢法。否则 Lamb⁡da 表达式可能无法匹⁡配接口。

3)建议使用 @FunctionalInterface注解。

虽然这个注解不是强制的,但加上⁢后编译器会帮你检查是⁡否符合函数式接口的规⁡范(是否只有一个抽象方法),如果不符合会报错。

4)可以包含默认方法 default 和静态方法 static

函数式接口允许有多个默认方法⁢和静态方法,因为它⁡们不是抽象方法,不⁡影响单一抽象方法的要求。

java
// 创建自定义函数式接口  
@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 出⁢现之前,我们处理集⁡合数据只能通过传统⁡的循环,需要大量的样板代码。

比如过滤列表中的数据、将小写转为大写并排序:

java
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 AP⁢I,可以让同样的逻⁡辑变得更简洁直观:

java
// 使用 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⁢ 映射都是中间操作⁡,比如下面这段代码⁡,并不会对列表进行过滤和转换:

java
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 ⁡后,才会真正执行:

java
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):

java
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 内置的统计功能,对数据进行统计:

java
// 统计操作  
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)按照对象的某个字段进行分组计算:

java
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 实现。这也是 Str⁡eam 的典型应用场景 —— 对数据库中查出的⁡数据进行业务层面的运算

并行流

并行流是 Stream AP⁢I 的另一个强大特⁡性,它可以自动利用⁡多核 CPU 处理器加速数据处理任务的执行。

在此之前,我们要实现并行处理集合数据,需要⁢手动管理线程池和任务分割,代⁡码复杂且容易出错。

但有了 Stream API⁢,一行代码就能创建⁡并行流,比如过滤并⁡计算数据的总和:

java
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 方法:

java
public interface Drawable {  
    // 已有抽象方法  
    void draw();  
      
    // 默认方法  
    default void drawWithBorder() {  
        System.out.println("绘制边框");  
        draw();  
        System.out.println("边框绘制完成");  
    }  
}

使用默认方法后,实现类可以选⁢择重写默认方法,也⁡可以直接使用:

java
// 实现类可以选择重写默认方法  
public class Circle implements Drawable {  
    @Override  
    public void draw() {  
        System.out.println("绘制圆形");  
    }  
      
    // 可以重写默认方法  
    @Override  
    public void drawWithBorder() {  
        System.out.println("绘制圆形边框");  
        draw();  
    }  
}

Java 8 为 Colle⁢ction 接口添⁡加了 stream⁡、removeIf 等方法,都是默认方法:

需要注意的是,如果一个类实现⁢多个接口,并且这些⁡接口有相同的默认方⁡法时,需要显式解决冲突:

java
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 还支持接口的静态方法:

java
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 的作用

NullPointerExcept⁢ion(NPE)一直是⁡ Java 程序员的噩⁡梦,学 Java 的同学应该都被它折磨过。

之前,我们只能通过大量的 if⁢ 语句检查 null⁡ 来避免空指针异常,⁡不仅代码又臭又长,而且稍微不注意就漏掉了。

java
// 传统的空值检查  
public String getDefaultName(User user) {  
    if (user != null) {  
        String name = user.getName();  
        if (name != null && !name.isEmpty()) {  
            return name.toUpperCase();  
        }  
    }  
    return "unknown";  
}

Optional 类的引入就是⁢为了优雅地处理可能为⁡空的值,可以先把它理⁡解为 "包装器",把可能为空的对象封装起来。

创建 Optional 对象:

java
// 创建 Optional 对象  
Optional<String> optional1 = Optional.of("Hello");          // 不能为 null  
Optional<String> optional2 = Optional.ofNullable(getName()); // 可能为 null  
Optional<String> optional3 = Optional.empty();              // 空的 Optional

Optional 提供了多种处理空值的方法:

java
// 检查是否有值  
if (optional.isPresent()) {  
    System.out.println(optional.get());  
}  
  
// 更优雅的方式,如果对象存在则输出  
optional.ifPresent(System.out::println);

还可以设置默认值策略,比如空值时抛出异常:

java
// 提供默认值  
String result1 = optional.orElse("默认值");  
String result2 = optional.orElseGet(() -> generateDefaultValue());  
String result3 = optional.orElseThrow(() -> new IllegalStateException("值不能为空"));

除了前面这些基本方法外,Op⁢tional 甚至⁡提供了一套完整的 ⁡API 来处理空值场景!

跟 Stream API 类⁢似,你可以对 Op⁡tional 封装⁡的数据进行过滤、映射等操作:

java
optional  
    .filter(s -> s.length() > 5)  
    .map(String::toUpperCase)  
    .ifPresentOrElse(  
        System.out::println,                   // 有值时执行  
        () -> System.out.println("没有值")      // 无值时执行  
    );

应用场景

鱼皮经常使用 Optional 来简化空值判断:

java
int pageNum = Optional.ofNullable(params.getPageNum())  
    .orElseThrow(() -> new RuntimeException("pageNum不能为空"));

如果不用 Optional,就要写下面这段代码:VbcIqC1RWAetLyIBuBgD21xetQwHXq5YVHB9NTWM5BE=

java
int pageNum;  
if (params.getPageNum() != null) {  
    pageNum = params.getPageNum();  
} else {  
    throw new RuntimeException("pageNum不能为空");  
}

此外,Optional 的一⁢个典型应用场景是在⁡集合中进行安全查找⁡:

java
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 引入的新日期时间 AP⁢I 解决了旧版 Date⁡ 和 Calendar ⁡类的很多问题,比如线程安全、可变性、时区处理等等。

传统的日期处理方式:

java
// 旧版本的复杂日期处理  
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,代码会更简洁:

java
// 当前日期时间  
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);

典型的应用场景是从字符串解析日期,一行代码就能搞定:

java
// 从字符串解析  
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);

还有日期和时间的计算,也变得更直观、见名知意:

java
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,直接让它生成时间日期操作就好。

java
// 带时区的日期时间  
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
// Java 8 之前的做法  
@Schedules({  
    @Schedule(dayOfMonth="last"),  
    @Schedule(dayOfWeek="Fri", hour="23")  
})  
public void doPeriodicCleanup() { ... }

Java 8 引入了重复注解⁢特性,让同一个注解⁡可以在同一个地方多⁡次使用:

java
// Java 8 的重复注解  
@Schedule(dayOfMonth="last")  
@Schedule(dayOfWeek="Fri", hour="23")  
public void doPeriodicCleanup() { ... }

要实现重复注解,需要定义一个容器注解:

java
@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 指定容器注解:

java
@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 扩展了注解的使⁢用范围,现在注解可⁡以用在任何使用类型⁡的地方,包括:

java
// 创建对象  
@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();  
}

类型注解主要用于静态分析工具(如 C⁢hecker Frame⁡work)进行更精确的类⁡型检查,帮助发现潜在的空指针异常、并发问题等。

其他特性

【了解】Nashorn JavaScript 引擎

Java 8 引入了 Nashorn JavaScr⁢ipt 引擎,用来替代老旧的 Rh⁡ino 引擎。Nashorn 提供⁡了更好的性能和对 ECMAScript 5.1 的完整支持:

java
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 在 J⁢ava 11 中已⁡被标记为弃用,Ja⁡va 15 中完全移除。

【了解】并行数组操作

Java 8 为数组操作添加⁢了并行处理能力,可⁡以利用多核处理器提⁡升大数组的处理性能:

java
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 原生支持 Ba⁢se64 编码和解⁡码,不再需要第三方⁡库:

java
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 引入了紧凑配置⁢文件(Compac⁡t Profile⁡s),将 Java SE 平台分为三个子集:

  • compact1:最小配置,包含核心 API

  • compact2:包含 compact1 + XML、JDBC 等

  • compact3:包含 compact2 + 管理、命名等 API

这主要用于嵌入式设备或需要减⁢小 JRE 体积的⁡场景,现在已经被 ⁡Java 9 的模块系统所取代。

总结

Java 8 是 Java 发展史上的里程碑版本,引入的函数式编程特性彻底改变了 ⁢Java 开发的风格。Lambda 表达式、Strea⁡m API、Optional 等特性不仅让代码更简洁,⁡也提升了开发效率。新的日期时间 API 解决了长期以来的痛点,接口默认方法则保证了向后兼容性。

这些特性至今仍是现代 Jav⁢a 开发的基础,值⁡得每个 Java ⁡开发者深入掌握。