专栏名称: Java知音
专注于Java,推送技术文章,热门开源项目等。致力打造一个有实用,有情怀的Java技术公众号!
目录
相关文章推荐
芋道源码  ·  防止超卖的七种实现 ·  昨天  
芋道源码  ·  DeepSeek+Spring有搞头么? ·  昨天  
Java编程精选  ·  国产 DeepSeek V3 ... ·  4 天前  
Java编程精选  ·  手把手教你Java文件断点下载 ·  3 天前  
芋道源码  ·  Spring Boot集成iText实现电子签章 ·  3 天前  
51好读  ›  专栏  ›  Java知音

从Java8到Java17,这些新特性让你的代码起飞!

Java知音  · 公众号  · Java  · 2024-09-22 10:40

主要观点总结

本文主要介绍了Java新特性的盘点,包括接口私有方法、Stream API增强、局部变量类型推断、新的HTTP客户端、Switch表达式增强、文本块、Record类、instanceof增强、密封类和接口等内容。

关键观点总结

关键观点1: 接口私有方法

Java9允许在接口中定义私有方法,减少代码重复和多余的类。

关键观点2: Stream API增强

Java中的Stream API提供了强大的数据处理能力,新的方法如takeWhile、dropWhile等增强了其功能性。

关键观点3: 局部变量类型推断

Java10引入了var关键字,允许在定义局部变量时自动推断类型,提高开发效率。

关键观点4: 新的HTTP客户端

Java 11中引入了新的HTTP客户端API,更简单易用,支持HTTP/1.1和HTTP/2,以及同步和异步编程模式。

关键观点5: Switch表达式增强

Java12开始,switch可以作为表达式使用,简化代码量和提高可读性。

关键观点6: 文本块

Java13引入了多行文本块,支持字符串插值,使多行字符串的书写更加便捷。

关键观点7: Record类

Java14引入了Record类,用于创建只包含数据的类,是不可变的,自动生成常用方法。

关键观点8: instanceof增强

Java 16中,instanceof操作符后面可以直接定义变量,自动转型,简化代码。

关键观点9: 密封类和接口

Java 17引入的密封类和接口允许对类或接口的继承进行更精确的控制,提供额外的类型安全性。


正文

前言

一直想找时间做一篇关于Java新特性的盘点清单,一切以实用为主,不多赘述,不讲空谈,不整虚头巴脑的概念,从实战的角度出发,根据实际开发需求,盘点值得使用的新特性。

因此对于垃圾回收器、性能提升等不会直接在编码层面体现的特性,不在此次盘点范围内。

耐心看完,你一定有所收获。

正文

接口私有方法(Java9)

众所周知,在Java9之前,interface接口只能定义abstract抽象方法和default默认方法。

如果有多个默认方法使用了相同的处理逻辑,那只能写重复代码,或者再单独建个类进行调用。

Java9解决了此类问题,其允许在接口中定义private私有方法,减少重用代码和多余的类。

比如下面这个例子:

public interface MyInterface {
    default void method1() {
        System.out.println("Default method1");
        commonMethod();
    }

    default void method2() {
        System.out.println("Default method2");
        commonMethod();
    }

 /**
 * 这是一段通用的处理逻辑
 */

    private void commonMethod() {
        System.out.println("Common method in the interface");
    }
}

在这个示例中, MyInterface 接口有两个默认方法 method1() method2() ,它们都调用了私有方法 commonMethod() ,避免了代码重复。

并且当实现 MyInterface 时,只需要调用 method1() 或者 method2() ,无需关心其共同调用的 commonMethod() 的具体实现。

Optional增强(Java9)

stream()

在Java 9之前,如果想对Optional对象中的值进行操作,还得使用 ifPresent() 方法或者 orElse() 方法。例如,以下是一个Java 8的例子:

Optional optional = ...;
optional.ifPresent(value -> System.out.println(value.length()));

在Java 9中,可以直接使用 stream() 方法和Stream的 map() 方法来达到相同的效果,代码如下:

Optional optional = ...;
optional.stream().map(String::length).forEach(System.out::println);

这个例子可能不太好,但还是能看出来有了 stream() 方法后,对于对象的操作也变得方便了许多。

ifPresentOrElse()

这个方法允许提供两个Runnable,第一个在Optional对象包含值时执行,第二个在Optional对象为空时执行。例如下面这两段代码,对比了Java8和Java9中不同的处理:

Optional optionalValue = Optional.of("Hello");

// Java 8
if (optionalValue.isPresent()) {
 System.out.println("Value is present: " + optionalValue.get());
else {
 System.out.println("Value is absent");
}

// Java 9
optionalValue.ifPresentOrElse(
  value -> System.out.println("Value is present: "  + value),
  () -> System.out.println("Value is absent")
);

通过 ifPresentOrElse 方法,可以更加简洁地处理类似情况,而不再需要频繁使用条件语句。

or()

这个方法允许你在Optional对象为空时,提供一个备选的Optional对象。例如:

Optional optional = Optional.empty();
Optional backup = Optional.of("Backup value");
Optional result = optional.or(() -> backup);
System.out.println(result.get());  // Prints "Backup value"

isEmpty()

用于检查Optional对象是否为空。例如:

Optional optional = Optional.empty();
if (optional.isEmpty()) {
    System.out.println("Optional is empty");
}  // Prints "Optional is empty"

其实这个方法等价于!optionalValue.isPresent(),只是不再需要取反,一定程度上能够减少心智负担。

Stream API增强(Java9)

takeWhile()

这个方法接收一个指定条件,它可以从一个有序的Stream中取出满足条件的所有元素,一旦遇到不满足条件的元素,就会停止处理后续元素。例如:

Stream.of("a""b""c""de""f")
    .takeWhile(s -> s.length() == 1)
    .forEach(System.out::print);  // Prints "abc"

在这个例子中,我们使用 takeWhile() 方法从一个Stream中取出所有长度为1的字符串,直到遇到一个长度不为1的字符串。

dropWhile()

该方法和takeWhile逻辑正好相反,通过指定条件来丢弃Stream流中满足条件的元素,一旦遇到不满足条件的元素,才会开始处理后续元素。

Stream.of("a""b""c""de""f")
    .dropWhile(s -> s.length() == 1)
    .forEach(System.out::print);  // Prints "def"

在这个例子中,使用 dropWhile() 方法丢弃所有长度为1的字符串,直到遇到一个长度不为1的字符串才开始处理后续的逻辑。

ofNullable()

该方法允许我们使用Optional对象来创建流。如果提供的元素为非空,则生成一个包含该元素的流;如果提供的元素为空,则生成一个空流。

Stream.ofNullable(null).forEach(System.out::print);  // Prints nothing

iterate()

该方法提供了一个新的重载形式,允许我们指定一个条件来定义流的终止条件,这样可以更灵活地控制Stream流的生成。

Stream stream = Stream.iterate(1, n -> n 10, n -> n * 2);
        stream.forEach(System.out::print);
// Prints 1248

局部变量类型推断(Java10)

局部变量类型推断,其实就是引入了var关键字,类似js的var或者kotlin的val,在定义变量时不需要明确指定变量的类型,编译器将根据上下文自动推断。

但是要注意的是,var关键字仅适用于局部变量,包括如下场景:

  • 方法的局部变量
  • for循环中的索引变量
  • try-with-resources语句中的变量

可以看下面的示例:

var str = "Hello, World!";  // String
var num = 123;  // int
var list = new ArrayList();  // ArrayList

for(var i = 0; i 10; i++) { // int
    System.out.println(i);
}

try(var reader = new BufferedReader(new FileReader("file.txt"))) { // BufferedReader

}

在这个示例中,str、num和list的类型全都是由编译器自动推断的。可以想见,在开发时熟练运用var关键字,必然能显著提升开发效率。

新的HTP客户端(Java11)

在Java 11中,引入了一个新的HTTP客户端API,替代了老旧的 HttpURLConnection API 。新的HTTP客户端API支持HTTP/1.1和HTTP/2,以及同步和异步编程模式,整体上来看确实更简单易用。

新的HTTP客户端API主要包括以下几个部分:

  • HttpClient: 用来发送HttpRequest并返回HttpResponse。
  • HttpRequest: 用来创建请求,支持设置HTTP方法(GET、POST等)、URI、头部、请求体等。
  • HttpResponse: 包含状态代码、头部和响应体。
  • BodyHandler、BodyPublisher和BodySubscriber: 这些都是处理HTTP请求体和响应体的工具。

以下是一个发送GET请求的例子:

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(new URI("http://example.com"))
    .build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());

可以很明显看到代码量少了一大截,没有太多的心智负担。

并且新的客户端还支持异步请求,看这个例子:

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(new URI("http://example.com"))
    .build();
CompletableFuture> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
future.thenAccept(response -> {
    System.out.println(response.statusCode());
    System.out.println(response.body());
});

利用 CompletableFuture 处理回调,程序不会被阻塞,更加灵活和高效,可以同时处理多个HTTP请求或者同时进行其他任务。

Switch表达式增强(Java12)

在之前的版本中,switch只能作为语句来使用,看下面的示例:

int day = 2;
String dayOfWeek;
switch (day) {
    case 1
        dayOfWeek = "Monday";
        break;
    case 2
        dayOfWeek = "Tuesday";
        break;
    case 3
        dayOfWeek = "Wednesday";
        break;
    case 4
        dayOfWeek = "Thursday";
        break;
    case 5
        dayOfWeek = "Friday";
        break;
    case 6
        dayOfWeek = "Saturday";
        break;
    case 7
        dayOfWeek = "Sunday";
        break;
    default
        throw new IllegalArgumentException("Invalid day of week: " + day);
}
System.out.println(dayOfWeek);  // Prints "Tuesday"

在Java12以后,switch可以直接作为表达式使用,直接看示例:

int day = 2;
String dayOfWeek = switch (day) {
    case 1 -> "Monday";
    case 2 -> "Tuesday";
    case 3 -> "Wednesday";
    case 4 -> "Thursday";
    case 5 -> "Friday";
    case 6 -> "Saturday";
    case 7 -> "Sunday";
    default -> throw new IllegalArgumentException("Invalid day of week: " + day);
};
System.out.println(dayOfWeek);  // Prints "Tuesday"

无论是代码量还是可读性上都有了改进。

对于过去很排斥switch,宁愿用if-elseif的的同学,可能是个新的选择。

文本块(Java13)

在之前的版本中,要写一个多行字符串,得用许多的转义字符和字符串连接,既繁琐又容易出错,代码看着还丑陋,比如下面这样:

String html = "\n" +
              "    \n" +
              "        

Hello, world

\n"
 +
              "    \n" +
              "\n";
System.out.println(html);

真是狗皮膏药一样,又臭又长。

这时候就很羡慕python或者js了,看看它们的多行字符串,真是优雅,比如下面这段python的示例:

html = """

    
        

Hello, world


    

"
""
print(html)

什么时候咱们Javaer也能这样写,就泰裤辣。

然后它就来了,Java自己的多行文本块,直接看例子:

String html = """
              
                  
                      

Hello, world


                  
              
              "
"";
System.out.println(html);

并且多行文本快还支持字符串插值,可以用 ${} 来包含一个表达式,表达式的结果会被插入到字符串中,比如下面这样:

String name = "world";
String greeting = """
                   Hello, ${name}!
                   Welcome to our website.
                   "
"";
System.out.println(greeting);

在这个例子中,name变量的值被插入到了greeting字符串中。输出的结果是:

Hello, world!
Welcome to our website.

这使得在字符串中包含动态内容变得更简单,你可以在 ${} 中包含任何Java表达式,包括变量、算术表达式、方法调用等。

等等,你说这跟Python怎么这么像?这……文化人的事情,什么像不像的。

Record类(Java14)

在Java14中引入了一个新的关键字record,这是一种特殊的类,用来创建只包含数据的类。并且Record类是不可变的,所有字段都是只读的,无法修改它们的值。

目前来看,一般用于替换过去的各类DTO、BO等仅用于数据传输的实体类,但是如果原有的类存在继承关系,或者内部存在其他方法逻辑,那就不适用record类了。

看这个例子:

public record Point(int x, int y) { }

在这个例子里,我们创建了Point类,编译时还会自动生成一个构造函数、 equals() hashCode() toString() 方法,以及每个字段的getter方法。

其实这个Record类和Kotlin的Data类存在诸多相似之处,但是也存在差异,主要体现在解构声明上。

Kotlin的Data类支持解构声明,如下所示:

val point = Point(12)
val (x, y) = point

可以直接解构声明x和y变量,并且能被初始化为point对象的x和y字段的值。

这是Java的Record类所不具备的能力,所以还有待进步。

instanceof增强(Java16)

在Java 16以前,当我们使用instanceof来检查一个对象是否是某个类型的实例时,如果检查成功,还得显式地将这个对象转型为对应类型,然后才能调用这个类型的方法或访问它的字段。例如:

Object obj = ...;
if (obj instanceof String) {
    String str = (String) obj;
    System.out.println(str.length());
}

在这个例子中,我们首先检查obj对象是否是String类型的实例,然后将obj对象转型为String类型,并将结果赋值给str变量,最后调用str变量的 length() 方法。

但是在Java 16中,可以在 instanceof 操作符后面直接定义一个变量,这个变量会自动被初始化为转型后的对象,可以直接使用这个变量,再也不用显式转型。例如:

Object obj = ...;
if (obj instanceof String str) {
    System.out.println(str.length());
}

我们在 instanceof 操作符后面定义了一个str变量,这个变量自动被初始化为obj对象转型为String类型后的结果,然后我们直接调用str变量的 length() 方法,无需显式转型。

这又是一个利于开发的特性,让代码更加简洁和直观。







请到「今天看啥」查看全文