Java 8中的新特性

2023/06/09

1. 概述

在本教程中,我们将快速浏览Java 8中一些最重要的新特性。

我们将讨论接口默认和静态方法、方法引用和Optional。

我们已经介绍了Java 8发行版的一些特性-Stream APIlambda表达式和函数接口。因为它们是全面的主题,值得单独查看。

2. 接口中的默认和静态方法

在Java 8之前,接口只能包含公共抽象方法。如果不强制所有实现类创建新方法的实现,就不可能向现有接口添加新功能,也不可能创建带有实现的接口方法。

从Java 8开始,接口可以具有默认静态方法,尽管这些方法是在接口中声明的,但它们已经具有定义的行为。

2.1 静态方法

考虑Vehicle接口中的以下方法定义:

public interface Vehicle {

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

静态producer()方法只能通过接口和在接口内部使用,它不能被实现类重写。

要在接口外部调用它,应该使用静态方法的标准调用方式,即类名.方法名:

@Test
void callDefaultInterfaceMethods_whenExpectedResults_thenCorrect() {
    String producer = Vehicle.producer();
    assertEquals(producer, "N&F Vehicles");
}

2.2 默认方法

默认方法是使用default关键字声明的。这些方法可以通过实现类的实例访问,并且可以被重写。

让我们为Vehicle接口添加一个默认方法,该方法也调用了静态方法producer():

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

假设VehicleImpl类实现了该接口。

为了执行getOverview()方法,应该创建这个类的一个实例:

@Test
void callStaticInterfaceMethodsMethods_whenExpectedResults_thenCorrect() {
    Vehicle vehicle = new VehicleImpl();
    String overview = vehicle.getOverview();

    assertEquals(overview, "ATV made by N&F Vehicles");
}

3. 方法引用

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

3.1 对静态方法的引用

对静态方法的引用语法为ContainingClass::methodName

我们将尝试在Stream API的帮助下计算List<String>中的所有空字符串:

public class User {
    private String name;
    private Address address;

    // constructors getters and setters ...

    public static boolean isRealUser(User user) {
        return true;
    }
}
@Test
void checkStaticMethodReferences_whenWork_thenCorrect() {
    List<User> users = new ArrayList<>();
    users.add(new User());
    users.add(new User());
    
    boolean isReal = users.stream().anyMatch(u -> User.isRealUser(u));
    
    assertTrue(isReal);
}

让我们仔细看看anyMatch()方法中的lambda表达式,它只是调用User类的静态方法isRealUser(User user)。

因此,它可以用对静态方法的引用代替:

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

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

3.2 对实例方法的引用

对实例方法的引用语法为containingInstance::methodName

以下代码调用User类的isLegalName(String string)方法,该方法校验输入参数:

public boolean isLegalName(String name) {
    return name.length() > 3 && name.length() < 16;
}
@Test
void checkInstanceMethodReferences_whenWork_thenCorrect() {
    User user = new User();
    boolean isLegalName = list.stream().anyMatch(user::isLegalName);
    
    assertTrue(isLegalName);
}

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

这种方法引用的语法为ContainingType::methodName

让我们看一个例子:

private List<String> list;

@BeforeEach
void init() {
    list = new ArrayList<>();
    list.add("One");
    list.add("OneAndOnly");
    list.add("Derek");
    list.add("Change");
    list.add("factory");
    list.add("justBefore");
    list.add("Italy");
    list.add("Italy");
    list.add("Thursday");
    list.add("");
    list.add("");
}

@Test
void checkParticularTypeReferences_whenWork_thenCorrect() {
    long count = list.stream().filter(String::isEmpty).count();
    
    assertEquals(count, 2);
}

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或如果参数为null,则返回空Optional:

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类型的字段,并且Address包含一个String类型的字段street,如果存在street的值不为空,我们需要返回street的值,如果street为空,我们需要返回一个默认值:

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()方法将调用getAddress()的结果转换为Optional<Address>,并将getStreet()的结果转换为Optional<String>。如果这些方法中的任何一个返回null,map()方法将返回一个空的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以及出于什么目的是一个严肃且有争议的设计决策,对其所有优缺点的解释超出了本文的范围。但是有很多有趣的文章专门讨论这个问题,这个这个可能对深入挖掘非常有帮助。

5. 总结

在本文中,我们简要讨论了Java 8中一些有趣的新特性。

当然,还有许多其他添加和改进分布在许多Java 8 JDK包和类中。

但是,本文中说明的信息是探索和了解其中一些新功能的良好起点。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章