Java 8中的新功能
1. 概述
在本教程中,我们将快速了解 Java 8 中一些最有趣的新特性。 我们将讨论接口默认和静态方法、方法引用和 Optional。 我们已经介绍了 Java 8 版本的一些特性——流 API 、lambda 表达式和函数式接口 ——因为它们是综合主题,值得单独查看。
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字段的值(如果存在)或者如果street为null则返回默认值:
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>。如果这些方法中的任何一个返回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以及出于什么目的是一个严肃且有争议的设计决策,对其所有优缺点的解释超出了本文的范围。但是有很多有趣的文章专门讨论这个问题。这个 和这个 可能对深入挖掘非常有帮助。