此前,在 java8 问世时,我写过一篇文章,详细介绍了 java8 的新特性。
如今,java8 已经问世 8 年多了,java 已经更新到了 java19 版本,尽管目前国内 java8 仍然占据着最大的使用比例。但这不妨碍我们来看看,8 年来,java 在使用上的体验出现了哪些优化。
本文,我们就来一一看看自 java8 以来,有哪些开发者友好的新特性诞生吧。
switch 语句有时显得比较复杂:
public String oldMultiSwitch(int day) {
switch (day) {
case 1:
case 2:
case 3:
case 4:
case 5:
return "workday";
case 6:
case 7:
return "weekend";
default:
return "invalid";
}
}
java12 对此进行了优化:
public String newMultiSwitch(int day) {
return switch (day) {
case 1, 2, 3, 4, 5 -> "workday";
case 6, 7 -> "weekend";
default -> "invalid";
};
}
通过箭头让 switch 语句变成了一个表达式,十分简洁明了。
自 java17 开始,可以将类型的判断应用到 switch 表达式中了:
public String newSwitchWithPatternMatching(Pet pet) {
return switch (pet) {
case Cat c -> "cat";
case Dog d -> "dog";
default -> "other pet";
};
}
pet 变量被声明为抽象类 Pet 的对象,这个方法则实现了根据 pet 具体实现类的不同返回不同的字符串。
密封类是 Kotlin 中的一个特性,在 java17 中也开始了对这一特性的支持。
Java17 引入一对新的关键词:sealed 与 non-sealed 以及与之配套使用的 permits 关键字。
有时,在我们设计一个类时,我们只希望这个类派生出一部分我们自己定义的类,而不想让其他人去派生这个类,此时,我们就可以使用 sealed class 这个新特性:
public abstract sealed class Pet permits Cat, Dog {}
这意味着,对于 Pet 这个类,我们只允许派生出 Cat 和 Dog 两个类,我们不允许其他类直接派生自 Pet 类。
所以,在我们定义 sealed 类的子类时,我们必须要加上 final 关键字,防止从这些子类派生:
public final class Cat extends Pet {}
如果我们只是不想让任何类直接派生自 Pet 类,但却允许让 Cat 和 Dog 拥有各自的子类,那么,我们就可以使用 non-sealed 关键字来解开限制:
public non-sealed class Dog extends Pet {}
在 python 等语言中,拥有文本块的特性:
def getNewPrettyPrintJson():
return """
{
"firstName": "Piotr",
"lastName": "Mińkowski"
}
"""
但是这段代码在 java 中写起来就较为复杂:
public String getOldPrettyPrintJson() {
return "{\n" +
" \"firstName\": \"Piotr\",\n" +
" \"lastName\": \"Mińkowski\"\n" +
"}";
}
从 java13 开始,java 也支持了文本块的特性:
public String getNewPrettyPrintJson() {
return """
{
"firstName": "Piotr",
"lastName": "Mińkowski"
}
""";
}
Optional 类作为 java8 的新特性,对于预防代码中令人头疼的可能的空指针异常有着非常好的作用。
在 java9 和 java10 中,相继为 Optional 类增加了新的实用的方法:
有了这两个方法,原本复杂的 if 判断就可以简化成一个简单的语句了:
public Person getPersonByIdOldWay(Long id) {
Optional<Person> personOpt = repository.findById(id);
if (personOpt.isPresent())
return personOpt.get();
else
throw new NoSuchElementException();
}
public void printPersonById(Long id) {
Optional<Person> personOpt = repository.findById(id);
personOpt.ifPresentOrElse(
System.out::println,
() -> System.out.println("Person not found")
);
}
Java9 开始,List 和 Map 都支持了 of 方法,用来简单的生成不可变的集合对象,尽管这个特性在 guava 的 ImmutableMap、ImmutableList 中早已支持:
List<String> fruits = List.of("apple", "banana", "orange");
Map<Integer, String> numbers = Map.of(1, "one", 2,"two", 3, "three");
当然,这样生成的集合是不可变集合,这意味着,我们不能扩充集合的容量,如果想方便的生成可变的 List,还是推荐:
public List<String> fruitsFromArray() {
String[] fruitsArray = {"apple", "banana", "orange"};
return Arrays.asList(fruitsArray);
}
对于一个 java 的数据类,我们经常会为它设置 getter 和 setter 方法,以及 toString、equals、hasCode 等方法,这看起来非常繁琐,所以我们往往会使用 Lombok 的 @Data 注解来简化这一过程。
但大部分时候,对于一个数据类,我们通常只需要生成 getter 方法,因为一旦对象创建,我们只需要频繁获取其字段的值,而不需要重新设置。
java14 中,引入了 record 关键字,简化了上述类定义的过程:
public record Person(String name, int age) {}
这个 record 类型的类声明会等效于为 Person 类添加了 name 和 age 两个字段的 getter 方法,同时也会自动创建 toString、equals、hasCode 等方法,使用起来就非常简单了。
在 java8 中,引入了 default 关键字,允许为接口定义方法的默认实现。到了 java9 中,允许为接口定义 private 的方法,这个特性可以说得到了完全的实现:
public interface ExampleInterface {
private void printMsg(String methodName) {
System.out.println("Calling interface");
System.out.println("Interface method: " + methodName);
}
default void method1() {
printMsg("method1");
}
default void method2() {
printMsg("method2");
}
}
自 java10,你可以使用 var 关键字来定义运行时类型的变量,从 java11 开始,var 关键字可以用在 lambda 表达式中了:
public String sumOfString() {
BiFunction<String, String, String> func = (var x, var y) -> x + y;
return func.apply("abc", "efg");
}
本文介绍了自 java8 以来,对开发者友好的 java 新特性,实际上,除了这些编写代码过程上的便捷与简化,java 在运行上和垃圾回收上的性能提升也十分值得关注,这部分我们就留待后续文章来进行介绍吧。