Java 8 新特性有哪些?怎么用?

文章导读
Previous Quiz Next Java 8 是 Java 编程语言开发的一个重大特性版本。其初始版本于 2014 年 3 月 18 日发布。随着 Java 8 的发布,Java 提供了对函数式编程、新 JavaScript 引擎、日期时间操作的新 API、新的流 A
📋 目录
  1. Lambda 表达式
  2. 方法引用
  3. 默认方法
  4. Stream API
  5. Optional 类
  6. 新的日期时间 API
  7. Nashorn JavaScript 引擎
  8. Java 8 Questions and Answers
A A

Java 8 - 新特性



Previous
Quiz
Next

Java 8 是 Java 编程语言开发的一个重大特性版本。其初始版本于 2014 年 3 月 18 日发布。随着 Java 8 的发布,Java 提供了对函数式编程、新 JavaScript 引擎、日期时间操作的新 API、新的流 API 等的支持。

以下是 Java 8 支持的新特性列表:

Lambda 表达式

Lambda 表达式是 Java 中引入的最大特性之一。Lambda 表达式促进了 Java 中的函数式编程。Lambda 表达式基于函数式接口的原则工作。函数式接口是一个只有一个方法需要实现的接口。Lambda 表达式为函数式接口的方法提供了实现。

Lambda 表达式极大地简化了函数式编程,使代码更易读,且无需样板代码。Lambda 表达式可以推断参数的类型,并且可以在没有 return 关键字的情况下返回值。对于简单的单语句方法,甚至可以省略大括号。

示例 - 使用 Lambda 表达式

以下示例展示了 Lambda 表达式的使用。Lambda 表达式与函数式接口配合使用效果最佳,函数式接口是一个只有一个抽象方法的接口。我们定义了一个 Calculator 接口,包含单个方法 operate,该方法可以接受两个参数并返回一个值。在 main 方法中,我们首先使用匿名类实现了 Calculator 接口,然后使用 Lambda 表达式实现。operate() 方法在两种情况下都被调用并打印结果。

package com.;

public class Tester {

   public static void main(String[] args) {
      // 使用匿名类实现接口
      Calculator sum = new Calculator() {
         @Override
         public int operate(int a, int b) {
            return a + b;
         }
      };
      int result = sum.operate(2,3);
      System.out.println(result);	   

      // 使用 Lambda 表达式实现接口
      Calculator sum1 = (a,b) -> a + b;
      result = sum1.operate(2,3);
      System.out.println(result);
   }  

   interface Calculator {
      int operate(int a, int b);
   }
}

编译并运行上述程序,将产生以下结果 −

5
5

方法引用

方法引用是一种简洁的方式,用于调用方法、静态方法甚至构造函数,而无需冗长的语法。方法引用可以通过方法名称指向方法,即使不指定参数。参数由 lambda 表达式传递。方法引用使用 "::" 符号来描述。

静态方法可以使用以下语法引用:

<<class-name>>::methodName

实例方法可以使用以下语法引用:

<<object-name>>::methodName

我们可以使用以下语法调用构造函数:

<<class-name>>::new

示例 - 使用方法引用

在这个示例中,我们使用了静态方法 compare 和实例方法 compareTo 来对两个整数 arraylist 进行排序。我们使用了方法引用来表示静态方法和实例方法。

package com.;

import java.util.Arrays;
import java.util.List;

public class Tester {
   public static void main(String args[]) {
      List<Integer> numbers = Arrays.asList(1,2,4,9,8,7,3);
      System.out.println("Sorted using static method reference");
      // 使用静态方法 compare
      numbers = numbers.stream().sorted(Integer::compare).toList();
      System.out.println(numbers);

      numbers = Arrays.asList(1,2,4,9,8,7,3);
      System.out.println("Sorted using instance method reference" );
      // 使用实例方法 compareTo
      numbers = numbers.stream().sorted(Integer::compareTo).toList();

      System.out.println(numbers);		
   }
}

让我们编译并运行上述程序,这将产生以下结果 −

Sorted using static method reference
[1, 2, 3, 4, 7, 8, 9]
Sorted using instance method reference
[1, 2, 3, 4, 7, 8, 9]

默认方法

在 Java 8 之前,interface 只能包含抽象方法。随着 Java 8 引入 lambda 表达式,为了向后兼容,默认方法功能被添加,使得旧的 interface 可以在不修改其实现的情况下利用 lambda 表达式。

例如,List 或 Collection interface 没有 'forEach' 方法声明。因此,添加这样的方法将破坏 collection framework 的实现。Java 8 引入默认方法,使得 List/Collection interface 可以为 forEach 方法提供默认实现,而实现这些 interface 的类无需实现它。

语法

以下是在 Java 中 interface 默认方法的语法 −

public interface vehicle {
   default void message() {
      System.out.println("I am a vehicle!");
   }
}

示例 - 使用默认方法

在这个示例中,我们创建了一个带有默认方法的 interface。在实现类中,该 message 方法未被实现,而是直接用于打印消息。

package com.;

interface vehicle {
   // 默认方法必须有实现
   default void message() {
      System.out.println("I am a vehicle!");
   }
}

// 实现类无需实现 interface 的默认方法
public class Tester implements vehicle {
   public static void main(String args[]) {
      Tester tester = new Tester();
      // 实现类可以将默认方法作为自己的方法访问
      tester.message(); 
   }
}

让我们编译并运行上述程序,这将产生以下结果 −

I am a vehicle!

Stream API

Stream API 是 Java 8 中引入的一个新抽象层,用于以声明式方式处理数据。Stream 表示一个元素序列。Stream 以顺序方式提供特定类型的元素集合。Stream 按需获取/计算元素。它从不存储元素。

Stream 支持聚合操作,如 filter、map、limit、reduce、find、match 等,并且可以内部迭代提供的源元素,这与 Collections 不同,后者需要显式迭代。

语法

以下是使用 Stream 的通用语法

<<collection-instance>>.stream().<<non-terminal-operation()>>.<<non-terminal-operation()>>.<<terminal-operation()>>

示例 - 使用 Stream

在这个示例中,我们创建了一个字符串列表,其中有一些条目为空。现在使用 Stream API,我们过滤掉空字符串并统计它们的数量。

package com.;

import java.util.Arrays;
import java.util.List;

public class Tester {
   public static void main(String args[]) {
      List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");

      // 从列表使用 stream() 方法获取 stream
      // 然后应用 filter
      // 最后统计 filter 的结果
      long count = strings.stream().filter(string->string.isEmpty()).count();
      System.out.println("Empty Strings: " + count);
   }
}

让我们编译并运行上述程序,这将产生以下结果 −

Empty Strings: 2

Optional 类

Optional 类特性在 Java 8 中引入,用于以编程方式处理 Null Pointer Exception 场景,使程序更简洁、更少出错。Null Pointer Exception 发生在尝试从 null 对象引用获取值或调用其方法时。随着程序规模增大,处理所有可能发生 Null Pointer Exception 的情况非常繁琐。

Optional 类实例为对象提供了一个包装器,包含许多实用方法,例如获取备用值(如果底层值为 null)、检查对象引用是否为 null 等。

示例 - 使用 Optional 类

在这个示例中,我们使用 ofNullable() 方法创建了两个 Optional 类实例,该方法允许传递 null 作为底层对象,然后使用 orElse() 方法检索值,该方法在底层对象为 null 时返回默认值。

package com.;

import java.util.Optional;

public class Tester {
   public static void main(String args[]) {
      // 情况 1: Optional 的底层值为 null
      Optional<Integer> valueOptional = Optional.ofNullable(null);

      // 情况 2: Optional 的底层值为非 null
      Optional<Integer> valueOptional1 = Optional.ofNullable(Integer.valueOf(10));

      // orElse 将返回默认值 -1
      Integer value = valueOptional.orElse(Integer.valueOf(-1));

      System.out.println(value);

      // orElse 将返回底层值
      Integer value1 = valueOptional1.orElse(Integer.valueOf(-1));

      System.out.println(value1);
   }
}

让我们编译并运行上述程序,这将产生以下结果 −

-1
10

新的日期时间 API

Java 8 引入了一个新的日期时间 API,它是线程安全的,支持时区,并且提供了多种直接方法来处理日期操作。先前的日期时间 API 不是线程安全的,在并发问题中处理日期时可能会出现问题。新的日期时间 API 使用不可变结构,没有 setter 方法,从而使 API 更加安全。新 API 的设计考虑了时区和特定领域的要求。

Java 8 在 java.time 包下引入了一个新的日期时间 API。以下是 java.time 包中引入的一些重要类。

  • Local − 简化的日期时间 API,没有时区处理的复杂性。

  • Zoned − 专门用于处理各种时区的日期时间 API。

示例 - 使用日期时间 API

在这个示例中,我们使用 ofNullable() 方法创建了两个 Optional 类实例,该方法允许传递底层的对象为 null,然后使用 orElse() 方法检索值,如果底层对象为 null,则返回默认值。

package com.;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZonedDateTime;

public class Tester {
   public static void main(String args[]) {
      // 获取当前日期和时间
      LocalDateTime currentTime = LocalDateTime.now();
      System.out.println("Current DateTime: " + currentTime);

      LocalDate date1 = currentTime.toLocalDate();
      System.out.println("date1: " + date1);

      Month month = currentTime.getMonth();
      int day = currentTime.getDayOfMonth();
      int seconds = currentTime.getSecond();

      System.out.println("Month: " + month +", day: " + day +", seconds: " + seconds);

      ZonedDateTime date2 = ZonedDateTime.parse("2007-12-03T10:15:30+05:30[Asia/Karachi]");
      System.out.println("date2: " + date2);
   }
}

让我们编译并运行上述程序,这将产生以下结果 −

Current DateTime: 2024-03-07T10:29:15.650806
date1: 2024-03-07
Month: MARCH, day: 7, seconds: 15
date2: 2007-12-03T09:45:30+05:00[Asia/Karachi]

Nashorn JavaScript 引擎

Nashorn 是一个非常强大且高效的 Javascript 引擎,作为现有 javascript 引擎 Rhino 的替代品引入。Nashorn 引擎据称速度快 2 到 10 倍,因为它能直接将 JavaScript 代码编译为 bytecode。Nashorn 引擎允许在 Java 文件中运行 JavaScript 代码,甚至可以在 JavaScript 代码片段中执行 Java 代码。借助 Nashorn 引擎,还引入了一个命令行工具 jjs,用于在命令行工具中运行 javascript。

直接在命令提示符中执行 JavaScript

打开控制台,输入 jjs 并按回车键。jjs 工具将打开一个交互式会话。一旦 jjs 会话打开,我们就可以执行 javascript 代码。完成后,输入 quit() 并按回车键,即可退出 jjs 交互式会话并返回命令提示符。

示例

C:\JAVA>jjs
jjs> print("Hello, World!")
Hello, World!
jjs> quit()
>>
C:\JAVA>

示例 - 在 Java 代码中使用 Javascript 代码

自 Java 6 起,Java 就有了 ScriptEngineManager class,本示例中使用它来加载 javascript 引擎作为 ScriptEngine 实例。一旦在 Java 代码中加载了引擎,我们就可以调用 eval() 方法来在 Java 中评估 JavaScript 代码。我们甚至可以在 javascript 代码片段中使用 Java 变量。

package com.;

import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

public class Tester {

   public static void main(String args[]) {
      // 创建 script engine manager   
      ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
      // 加载 Nashorn javascript 引擎
      ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
		
      String message = "This is a message";
      String expression = "10 + 2";
      Integer result = null;
      
      try {
         // 调用 javascript 函数,传入 Java 变量	  
         nashorn.eval("print('" + message + "')");
         // 调用 javascript 函数并将结果返回到 Java 中
         result = (Integer) nashorn.eval(expression);
         
      } catch(ScriptException e) {
         System.out.println("Error executing script: "+ e.getMessage());
      }
      System.out.println(result.toString());
   }
}

让我们编译并运行上述程序,这将产生以下结果 −

This is a message
12

Nashorn 引擎在 Java 11 中被弃用,在 Java 15 中被移除,并由 GraalVM javascript 引擎取代。