Java11新特性 – 新加一些實用的API

  • 2019 年 10 月 30 日
  • 筆記

1. 新的本機不可修改集合API

自從Java9開始,JDK裡面為集合(List/Set/Map)都添加了of和copyOf方法,他們可以來創建不可變的集合。

Question1:什麼叫做不可變集合?

不能對集合進行添加、刪除、替換、排序等操作,否則會報java.lang.UnsupportedOperationException錯誤。
示例程式碼:

List<String> list = List.of("aa", "bb", "cc");  list.add("dd");

報錯資訊:

Exception in thread "main" java.lang.UnsupportedOperationException      at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:71)      at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:75)

擴展:Arrays.asList()創建的集合也是一個不可變的集合

Question2:of方法與copyOf方法之間的區別

示例程式碼:

var list1 = List.of("aa", "bb", "cc");  var copy1 = List.copyOf(list1);  System.out.println(list1 == copy1);  //運行結果: true    var list2 = new ArrayList<String>();  var copy2 = List.copyOf(list2);  System.out.println(list2 == copy2);  //運行結果: false

注意:var也是Java11新推出的特性局部變數類型推斷,這裡面不再贅述。

第一段程式碼和第二段程式碼差不多,為什麼返回結果不一樣呢?我們來看一下他們的源碼:

    static <E> List<E> of(E e1, E e2, E e3) {          return new ImmutableCollections.ListN<>(e1, e2, e3);      }        static final class ListN<E> extends AbstractImmutableList<E>              implements Serializable {            static final List<?> EMPTY_LIST = new ListN<>();            @Stable          private final E[] elements;            @SafeVarargs          ListN(E... input) {              // copy and check manually to avoid TOCTOU              @SuppressWarnings("unchecked")              E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input              for (int i = 0; i < input.length; i++) {                  tmp[i] = Objects.requireNonNull(input[i]);              }              elements = tmp;          }            @Override          public boolean isEmpty() {              return size() == 0;          }            @Override          public int size() {              return elements.length;          }            @Override          public E get(int index) {              return elements[index];          }            private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {              throw new InvalidObjectException("not serial proxy");          }            private Object writeReplace() {              return new CollSer(CollSer.IMM_LIST, elements);          }      }
    static <E> List<E> copyOf(Collection<? extends E> coll) {          return ImmutableCollections.listCopy(coll);      }        static <E> List<E> listCopy(Collection<? extends E> coll) {          if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) {              return (List<E>)coll;          } else {              return (List<E>)List.of(coll.toArray());          }      }

從源碼可以看出,copyOf 方法會先判斷來源集合是不是 AbstractImmutableList 類型的,如果是,就直接返回,如果不是,則調用 of 創建一個新的集合。第二段程式碼因為用的 new 創建的集合,不屬於不可變 AbstractImmutableList 類的子類,所以 copyOf 方法又創建了一個新的實例,所以為false。

擴展:使用Set.of()方法創建Set對象時,不可以包含重複數據

2. Stream加強

Stream是Java8的新特性,我之前的部落格對其進行了詳細的介紹:Java8新特性 – Stream API。Java9開始對Stream新增了4個新方法。

2.1 增加單個參數構造方法,可為null

在Java8,新建一個值為null的Stream,會報錯java.lang.NullPointerException錯誤。
示例程式碼:

Stream stream = Stream.of(null);

錯誤:

Exception in thread "main" java.lang.NullPointerException      at java.util.Arrays.stream(Arrays.java:5004)      at java.util.stream.Stream.of(Stream.java:1000)

錯誤分析:

查看Stream.of()源碼

    @SafeVarargs      @SuppressWarnings("varargs") // Creating a stream from an array is safe      public static<T> Stream<T> of(T... values) {          return Arrays.stream(values);      }        public static <T> Stream<T> stream(T[] array) {          return stream(array, 0, array.length);      }

可以看見傳入null會被解析為時一個數組對象,會進一步訪問它的長度資訊,導致了NullPointerException異常。

在Java11中,新增了ofNullable方法,可以傳入null。因為無法推斷類型,所以返回的值為Object,示例程式碼如下:

Stream stream = Stream.ofNullable(null);  stream.forEach(System.out::println);

從源碼中可以看出,當傳入的是一個null時,返回的是一個空的對象。

    public static<T> Stream<T> ofNullable(T t) {          return t == null ? Stream.empty()                           : StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);      }

2.2 增加 takeWhile 和 dropWhile 方法

2.2.1 takeWhile

首先來看一段示例程式碼:

Stream<Integer> stream = Stream.of(3, 6, 9, 12, 15);  stream.takeWhile(n -> n % 2 != 0)          .forEach(System.out::println);

第一次看這段程式碼,可能第一印象就是找出流中的奇數列印出來,但是實際輸出為:

3

所以takeWhile方法在計算到n % 2 == 0的時候就終止了,takeWhile方法的作用是:從流中一直獲取判定器為真的元素,一旦遇到元素為假,就終止處理!

2.2.2 dropWhile

與上面takeWhile一樣的程式碼:

Stream<Integer> stream = Stream.of(3, 6, 9, 12, 15);  stream.dropWhile(n -> n % 2 != 0)          .forEach(System.out::println);

返回的結果為:

6
9
12
15

結果正好與takeWhile相反,dropWhile方法的作用為:只要判定器為真,就一直丟棄元素,直到為假,取為假後的所有元素!

2.3 iterate重載

流的迭代,主要用於創建流,在指定初值的情況下,經過處理,然後將處理過的值作為初值,不斷迭代。所以iterate創建的是一個無限流。
示例程式碼:

Stream.iterate(1, t -> 2 * t + 1)          .limit(10)          .forEach(System.out::println);

無限流有一個問題,在數值溢出之後,會一直重複-1的值。Java11進行了優化,以讓你提供一個 Predicate (判斷條件)來指定什麼時候結束迭代,進行有限的迭代,示例程式碼如下:

Stream.iterate(1, t -> t< 1000, t -> 2 * t + 1)          .forEach(System.out::println);

3. 增加了一系列的字元串處理方法

3.1 判斷字元串是否為空白

示例程式碼:

String s1 = " t rn";  System.out.println(s1.isBlank()); // true

3.2 去除字元串首尾空白

示例程式碼:

String s2 = "    t  123rn".strip();  System.out.println(s2); // 123

trim()大部分情況下效果等同於strip(),但是trim()只能去除碼值小於等於32的空白字元,不能去除中文情況下的空白字元,strip()可以去除所有語言中的空白

3.3 去除尾/首部空格

示例程式碼:

String s2 = "    t  123rn";  System.out.println(s2.strip().length()); // 3  // 去除首部的空白  System.out.println(s2.stripLeading().length()); // 5  // 去除尾部的空白  System.out.println(s2.stripTrailing().length()); // 10

3.4 複製字元串

String s3 = "做一個好人";  System.out.println(s3.repeat(3));  // 做一個好人做一個好人做一個好人

3.5 行數統計

String s2 = "    t  123rn123";  System.out.println(s2.lines().count()); //2

s2.lines()獲取的是一個流,將字元串根據換行符切割轉換為Stream

4. Optional加強

Optional是Java中引進的容器類,主要用於避免空指針異常,我之前的部落格對其進行了詳細的介紹:Java8新特性 – Optional容器類。Java11中Opthonal 也增加了幾個非常酷的方法。

4.1 ofNullable方法

參照前面Stream增強的介紹,使用Optional.of(T value);傳入的參數是null時,會拋出空指針異常,所以Java11中新增了ofNullable,可以兼容空指針,但是實際傳入null後要小心,不能用get接收,最好利用orElse方法接收,示例程式碼如下。

Optional<String> optional = Optional.ofNullable(null);  // 如果內部引用為空,則返回參數中引用,否則返回內部引用  System.out.println(optional.orElse("做一個好人")); // 做一個好人

4.2 orElseThrow方法

也可以使用orElseThrow()方法接收,直接在orElseThrow()時拋出異常。

Optional<String> optional = Optional.ofNullable(null);  System.out.println(optional.orElseThrow());

錯誤如下:
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:382)

源碼如下:

    public T orElseThrow() {          if (value == null) {              throw new NoSuchElementException("No value present");          }          return value;      }

4.3 or方法

也可以使用or()方法接收,當一個空 Optional 時給它一個替代的Optional,示例程式碼如下:

Optional<String> optional = Optional.ofNullable(null);  System.out.println(optional.or(() -> Optional.of("做一個好人!"))          .get()); // 做一個好人!

5. InputStream加強

Java11中新增了transferTo方法,把輸入流中的所有數據直接自動地複製到輸出流中,這是在處理原始數據流時非常實用的一種用法,不用寫循環,也不需要緩衝區,示例程式碼如下:

public void test() throws Exception {      var classLoader = this.getClass().getClassLoader();      var inputStream = classLoader.getResourceAsStream("file");      try (var outputStream = new FileOutputStream("file2")) {          inputStream.transferTo(outputStream);      }      inputStream.close();  }