10分钟了解7个Java11的新功能

Java
231
0
0
2023-06-02

概述

我知道很多公司和个人还在用 Java8,我们不妨梳理下当下的情况

  • 目前 Java 最新的 GA(General-Availability) Release 版本是 JDK 18.0.2.1
  • Java 17 LTS 是 最新长期支持版本。根据 Oracle 免费条款 JDK 18 JDK 17 可在生产环境中免费使用,至少在 2024 年 9 月之前
  • JDK 18 到 2022 年 9 月它将被 JDK 19 取代

日子总要过,我们也不可能抱着 Java 8 用一辈子,我们来一起看看 Java 11 的一些新玩意儿。

本文算是 Java 11 功能的小教程,没有长篇的文字,都是些短小易懂的代码,让我们沉浸在研究代码的快乐中吧。

1 局部变量 类型推断

Java 10 引入了一个新的语言关键字 var ,它可以在声明局部变量时选择性地替换类型信息

在 Java 10 之前,我们这样声明变量:

 String text ="Hello World"; 

现在可以替换 String var . 编译器从变量的赋值中推断出正确的类型

 var text = "Hello World"; 

注意:声明的变量 ** var ****仍然是静态类型的。不能将不兼容的类型重新分配给此类变量**,比如下面的代码就无法编译通过

 var text = "Hello World";
text = 123; 

当然 你还可以 final 与 结合使用 var 来禁止用另一个值重新分配变量:

 final var text = "Hello World";
text = "hello";   // Cannot assign a value to final variable 'text' 

当编译器无法推断变量的正确类型时,也 var 不允许使用。以下所有代码示例都会导致编译器错误:

 var a;
var nothing = null;
var lambda = () -> System.out.println("Joe!");
var method = this::someMethod; 

**有什么直接的好处? **

比如有一个相当冗长的类型 Map <String, List< Integer >> ,可以将其简化为单个 var 关键字,从而避免您输入一坨类型:

 var myList = new ArrayList<Map<String, List<Integer>>>();

for (var current : myList) {
    // current is infered to type: Map<String, List<Integer>>
    System.out.println(current);
} 

从 Java 11 开始, var 关键字也允许用于 lambda 参数,这使你能够为这些参数添加注释:

 Predicate<String> predicate = (@Nullable var a) -> true; 

2 Http Client

Java 9 引入了一个新的孵化的 HttpClient API 来处理 HTTP 请求。从 Java 11 开始,这个 API 现在是最终可用的了,在包 java.net 中。让我们探索一下这个 API

  • newHttpClient 可以同步或异步使用。同步请求会阻塞当前线程,直到响应可用。
  • BodyHandlers 定义响应主体的预期类型(例如字符串、字节数组或文件):
   var  Request  = HttpRequest.newBuilder()
                .uri(URI.create("#34;"))
                .GET()
                .build();
  var client = HttpClient.newHttpClient();
  HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
  System.out.println(response.body()); 

可以异步执行相同的请求。调用 sendAsync 不会阻塞当前线程,而是返回一个 CompletableFuture 来构造异步操作流水线。

 var request = HttpRequest.newBuilder()
    .uri(URI.create("#34;"))
    .build();
var client = HttpClient.newHttpClient();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println); 
自己跑代码的时候注意,因为是异步的,所以你可以让主线程 sleep 一会儿,不然直接运行啥都没有

最新的这个 HttpClient 还有一些其他功能就不过多介绍了,如果你使用了 Spring 5 及以上版本,我的建议是可以直接用 WeClient ,那玩意儿谁用谁知道呀。

3 Collection

List 等集合已通过新方法进行了扩展。从给定的参数创建一个新的不可变列表。创建列表的不可变副本

 var list = List.of("A", "B", "C");
var  copy  = List.copyOf(list);
System.out.println(list == copy);   // true 

注意这里 of 方法返回的是不可变类型,我们看下源码:

   static  <E> List<E> of(E e1, E e2, E e3) {
        return new ImmutableCollections.ListN<>(e1, e2, e3);
 } 

因为 list 已经是不可变的,所以实际上不需要创建列表实例的副本,因此 list copy 是同一个实例。但是,如果你想复制一个可变列表, copy 则返回一个新实例,因此可以保证在改变原始列表时没有副作用

 var list = new ArrayList<String>();
var copy = List.copyOf(list);
System.out.println(list == copy);   // false 

Map 的 of 方法方便我们直接构造:

 var map = Map.of("A", 1, "B", 2);
System.out.println(map);    // {B=2, A=1} 

注意这里仍然返回的是不可变类型,关于不可变集合的了解可以参考我之前的一篇文章 《 跟着 Guava 学 Java 之 不可变集合》

如果你修改了不可变集合会抛出 java.lang.UnsupportedOperation Exception 异常, IDEA 也会有相应的警告给你标明哪里出了问题。

4 Stream

流是在 Java 8 中引入的,现在接收三个新方法。 Stream .ofNullable 从单个元素构造一个流:

 Stream.ofNullable(null).count()   // 0 

dropWhile takeWhile 是确定要从流中放弃哪些元素。

 Stream.of(1, 2, 3, 2, 1)
    .dropWhile(n -> n < 3)
    .collect(Collectors.toList());  // [3, 2, 1]

Stream.of(1, 2, 3, 2, 1)
    .takeWhile(n -> n < 3)
    .collect(Collectors.toList());  // [1, 2] 
  • takeWhile() 方法使用一个断言作为参数,返回给定 Stream 的子集直到断言语句第一次返回 false。如果第一个值不满足断言条件,将返回一个空的 Stream。
  • dropWhile 方法和 takeWhile 作用相反的,使用一个断言作为参数,直到断言语句第一次返回 false 才返回给定 Stream 的子集。

所以上面

  • 第一段的意思是放弃取小于 3 的元素,直到遇到第一个不小于 3 的则把后面的全部元素收集起来
  • 第二段的意思就是从第一个元素开始收集元素,直到遇到第一个不小于 3 的元素结束

5 Optional

Optional 还接收到一些非常方便的新方法,例如,您现在可以简单地将 optional 转换为流或提供另一个 optional 作为空 optional 的后备

 Optional.of("foo").orElseThrow();     // foo
Optional.of("foo").stream().count();  // 1
Optional.ofNullable(null)
    .or(() -> Optional.of("fallback"))
    .get();                           // fallback  

6 String

最基本的类之一 String 有一些辅助方法来修剪或检查空格以及流式传输字符串的行:

 " ".isBlank();                // true
" Foo Bar ".strip();          // "Foo Bar"
" Foo Bar ".stripTrailing();  // " Foo Bar"
" Foo Bar ".stripLeading();   // "Foo Bar "
"Java".repeat(3);             // "JavaJavaJava"
"A\nB\nC".lines().count();    // 3 

注意你可能觉得 strip 和 trim 方法一样,一般使用的话差不多,但实际上他们不一样,有所区别:

  • trim() 可以去除字符串前后的 半角空白字符
  • strip() 可以去除字符串前后的 全角和半角空白字符

你可以试试:

   String test1="测试、u0020";//半角 unicode
  System.out.println(test1.trim().length());//2
  System.out.println(test1.strip().length());//2

  String test2="测试、u3000";//全角 unicode
  System.out.println(test2.trim().length());//3
  System.out.println(test2.strip().length());//2

  String test3="测试 ";//半角空白字符
  System.out.println(test3.trim().length());//2
  System.out.println(test3.strip().length());//2

  String test4="测试 ";//全角空白字符
  System.out.println(test4.trim().length());//3
  System.out.println(test4.strip().length());//2

  String test5="测试  ";//两个半角空白字符
  System.out.println(test5.trim().length());//2
  System.out.println(test5.strip().length());//2 

7 InputStream

终于有一个非常实用的方法可以将数据从 inputStream 转到 outputStream 了,不用再自己写了

 var classLoader = ClassLoader.getSystemClassLoader();
var inputStream = classLoader.getResourceAsStream("my File .txt");
var tempFile = File.createTempFile("myFileCopy", "txt");
try (var outputStream = new FileOutputStream(tempFile)) {
    inputStream.transferTo(outputStream);
} 

我们看源码的 transferTo 方法

  public long transferTo(OutputStream out) throws IOException {
        Objects.requireNonNull(out, "out");
        long transferred = 0;
         byte [] buffer = new byte[DEFAULT_BUFFER_SIZE];
        int read;
        while ((read = this.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) {
            out.write(buffer, 0, read);
            transferred += read;
        }
        return transferred;
    }
} 

熟悉吗? 再也不用写这破玩意儿了。

最后

当然 Java 11 的更新 中远远不止上面这些内容,还有很多功能和特性值得大家去探索,比如:

  • Flow API 的反应式编程
  • G1: Full Parallel Garbage Collector
  • ZGC: Scalable Low-Latency Garbage Collector