Contents

Java 8中的新功能

1. 概述

在本教程中,我们将快速了解 Java 8 中一些最有趣的新特性。 我们将讨论接口默认和静态方法、方法引用和 Optional。 我们已经介绍了 Java 8 版本的一些特性——流 APIlambda 表达式和函数式接口 ——因为它们是综合主题,值得单独查看。

2.接口默认和静态方法

在 Java 8 之前,接口只能有公共抽象方法。如果不强制所有实现类创建新方法的实现,就不可能向现有接口添加新功能,也不可能创建具有实现的接口方法。 从 Java 8 开始,接口可以有static方法和default方法,尽管它们是在接口中声明的,但它们具有定义的行为。

2.1. 静态方法

考虑接口的这个方法(我们称这个接口为Vehicle):

static String producer() {
    return "N&F Vehicles";
}

静态*producer()*方法只能通过接口并在接口内部使用。它不能被实现类覆盖。 要在接口外部调用它,应使用静态方法调用的标准方法:

String producer = Vehicle.producer();

2.2. 默认方法

默认方法是使用新的default关键字声明的。**这些可以通过实现类的实例访问并且可以被覆盖。 让我们为我们的Vehicle接口添加一个*默认方法,它也会调用这个接口的静态方法:*

default String getOverview() {
    return "ATV made by " + producer();
}

假设此接口由类VehicleImpl实现。 要执行默认方法,应创建此类的实例:

Vehicle vehicle = new VehicleImpl();
String overview = vehicle.getOverview();

3. 方法参考

方法引用可用作仅调用现有方法的 lambda 表达式的更短且更易读的替代方法。方法引用有四种变体。

3.1. 引用静态方法

对静态方法的引用包含语法ContainingClass::methodName 我们将尝试在 Stream API 的帮助下计算List<String>中的所有空字符串:

boolean isReal = list.stream().anyMatch(u -> User.isRealUser(u));

让我们仔细看看anyMatch()方法中的 lambda 表达式。它只是调用User类的静态方法isRealUser(User user)。 因此,它可以用对静态方法的引用代替:

boolean isReal = list.stream().anyMatch(User::isRealUser);

这种类型的代码看起来信息量更大。

3.2. 引用实例方法

对实例方法的引用包含语法containingInstance::methodName 以下代码调用User类型的方法*isLegalName(String string)*来验证输入参数:

User user = new User();
boolean isLegalName = list.stream().anyMatch(user::isLegalName);

3.3. 引用特定类型对象的实例方法

此引用方法采用语法ContainingType::methodName 让我们看一个例子:

long count = list.stream().filter(String::isEmpty).count();

3.4. 引用构造函数

对构造函数的引用采用语法ClassName::new 由于 Java 中的构造函数是一种特殊方法,方法引用也可以应用于它,借助new 作为方法名称:

Stream<User> stream = list.stream().map(User::new);

4. Optional<T>

在 Java 8 之前,开发人员必须仔细验证他们引用的值,因为可能会抛出 NullPointerException(NPE)。所有这些检查都需要非常烦人且容易出错的样板代码。 Java 8 Optional<T>类可以帮助处理可能获得NPE 的情况。它用作T类型对象的容器。如果这个值不是null ,它可以返回这个对象的值。当此容器内的值为null时,它允许执行一些预定义的操作而不是抛出NPE

4.1. 创建Optional<T>

可以在其静态方法的帮助下创建Optional类的实例。 让我们看看如何返回一个空的Optional

Optional<String> optional = Optional.empty();

接下来,我们返回一个包含非空值的Optional

String str = "value";
Optional<String> optional = Optional.of(str);

最后,这里是如何返回一个具有特定值的Optional或一个空的Optional如果参数为null

Optional<String> optional = Optional.ofNullable(getString());

4.2. *Optional<T>*用法

假设我们希望得到一个List<String> ,并且在null的情况下,我们想用ArrayList<*String>*的新实例替换它。

使用 Java 8 之前的代码,我们需要做这样的事情:

List<String> list = getList();
List<String> listOpt = list != null ? list : new ArrayList<>();

使用 Java 8,可以使用更短的代码实现相同的功能:

List<String> listOpt = getList().orElseGet(() -> new ArrayList<>());

当我们需要以旧方式访问某个对象的字段时,甚至会有更多的样板代码。 假设我们有一个User类型的对象,它有一个Address类型的字段和一个String类型的字段 street,并且我们需要返回street字段的值(如果存在)或者如果streetnull则返回默认值:

User user = getUser();
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        String street = address.getStreet();
        if (street != null) {
            return street;
        }
    }
}
return "not specified";

这可以用Optional简化:

Optional<User> user = Optional.ofNullable(getUser());
String result = user
  .map(User::getAddress)
  .map(Address::getStreet)
  .orElse("not specified");

在此示例中,我们使用map()方法将调用 getAdress()的结果转换为Optional<Address>并将getStreet()转换为Optional<String>。如果这些方法中的任何一个返回nullmap()方法将返回一个空的Optional。 现在假设我们的 getter 返回Optional<T>。 在这种情况下,我们应该使用flatMap()方法而不是map()

Optional<OptionalUser> optionalUser = Optional.ofNullable(getOptionalUser());
String result = optionalUser
  .flatMap(OptionalUser::getAddress)
  .flatMap(OptionalAddress::getStreet)
  .orElse("not specified");

Optional的另一个用例是将NPE更改为另一个异常。 因此,正如我们之前所做的那样,让我们尝试以 Java 8 之前的风格来做到这一点:

String value = null;
String result = "";
try {
    result = value.toUpperCase();
} catch (NullPointerException exception) {
    throw new CustomException();
}

如果我们使用Optional<String> ,答案将更具可读性和更简单:

String value = null;
Optional<String> valueOpt = Optional.ofNullable(value);
String result = valueOpt.orElseThrow(CustomException::new).toUpperCase();

请注意,如何在我们的应用程序中使用Optional以及出于什么目的是一个严肃且有争议的设计决策,对其所有优缺点的解释超出了本文的范围。但是有很多有趣的文章专门讨论这个问题。这个这个 可能对深入挖掘非常有帮助。