java 8 新特性

2022-06-08 09:00:08

java8 是一個有里程碑的一個版本,提供了很多的新特性,但這些新特性是實打實有用的,而不是一些雞肋

Interface

介面新特性

java8 之前,往介面裡新加一個方法,那麼所有的實現類都需要變動,都需要同步實現這個方法。
java8 給介面新增了兩個關鍵字:default static
使得可以往介面裡新增預設方法,子類可以無需變動。

interface InterfaceOne {

    /**
     * 實現類必須要實現的方法
     */
    void method1();

    /**
     * 介面方法,子類可以選擇實現也可以選擇不實現
     */
    default void method2() {
        System.out.println("this is InterfaceOne#method2");
    }

    /**
     * 靜態方法,子類無需實現
     */
    static void method3() {
        System.out.println("this is InterfaceOne#method3");
    }
}

interface InterfaceTwo {

    /**
     * 介面方法,子類可以選擇實現也可以選擇不實現
     */
    default void method2() {
        System.out.println("this is InterfaceTwo#method2");
    }

    /**
     * 介面方法,子類可以選擇實現也可以選擇不實現
     */
    default void method3() {
        System.out.println("this is InterfaceTwo#method3");
    }

    default void method4() {
        System.out.println("this is InterfaceTwo#method4");
    }
}

public class InterfaceDemo implements InterfaceOne, InterfaceTwo {

    /**
     * 由於 method1 是 InterfaceOne 介面裡的抽象方法,因此必須實現
     */
    @Override
    public void method1() {

    }

    /**
     * 由於 InterfaceOne InterfaceTwo 都有一個 default 方法 method2,因此也必須重新實現
     */
    @Override
    public void method2() {
        InterfaceOne.super.method2();
    }

    public static void main(String[] args) {
        InterfaceDemo demo = new InterfaceDemo();
        demo.method4();
        InterfaceOne.method3();
    }
}

如上程式碼塊,InterfaceOne#method1 是一個抽象方法,因此 InterfaceDemo 實現類
必須重寫該類,InterfaceOne 介面有一個 default method2 方法,InterfaceTwo 也有一個 default method2 方法
,InterfaceDemo 實現類不知道使用哪個,因此我們需要重寫該方法。

其中,default 方法可以通過實現類的範例直接呼叫,這個 default 方法就很像繼承特性,沒有重寫的話直接呼叫父類別方法,
但是介面不是繼承,而是實現,兩者概念上還是有區別的。static 方法可以通過 介面.方法 直接呼叫,跟平時的靜態方法一樣

介面和抽象類的區別

這樣看下來,介面和抽象類越來越像了,這樣下去,抽象類會不會被淘汰呢?

其實抽象類和介面本質上還是不同的

  • 介面是一個更加抽象的概念,一個實現類可以實現多個介面,並且介面裡的方法預設都是抽象方法,欄位預設都是靜態常數
  • 抽象類是一個父類別,只能單繼承,抽象類中的抽象方法必須手動指定,抽象類底層還是一個類

Lambda

函數式介面

定義

函數式介面是指一個介面內只有一個抽象方法,若干個 default 方法的介面,一般函數式介面都會使用 @FunctionalInterface 註解進行標註,但是並不是說沒有該註解就不是一個函數式介面了,該註解只是用於編譯期檢查,如果有多個抽象方法,那麼編譯將不同過。

先來看下怎麼定義一個函數式介面:

@FunctionalInterface
interface FunctionInterfaceOne {

    void methodOne(String msg);

}

public class FunctionInterfaceDemo {


    public static void main(String[] args) {
        // 匿名類
        FunctionInterfaceOne firstInstance = new FunctionInterfaceOne() {
            @Override
            public void methodOne(String msg) {
                System.out.println("this is " + msg);
            }
        };
        // lambda 表示式
        FunctionInterfaceOne secondInstance = msg -> System.out.println("this is " + msg);
        firstInstance.methodOne("firstInstance");
        secondInstance.methodOne("secondInstance");
    }
}

如上程式碼,FunctionInterfaceOne 就是一個函數式介面,在 main 方法中使用兩種方式定義了一個 FunctionInterfaceOne 物件,一個是以前常用的匿名類形式,一個就是 java8 的 lambda 表示式。該函數式介面表達的是接受一個引數,並且不輸出返回值。

目前我們常用的函數式介面主要有:

  • BiConsumer<T,U>:代表了一個接受兩個輸入引數的操作,並且不返回任何結果
  • BiFunction<T,U,R>:代表了一個接受兩個輸入引數的方法,並且返回一個結果
  • BinaryOperator:代表了一個作用於於兩個同型別操作符的操作,並且返回了操作符同型別的結果
  • BiPredicate<T,U>:代表了一個兩個引數的boolean值方法
  • BooleanSupplier:代表了boolean值結果的提供方
  • Consumer:代表了接受一個輸入引數並且無返回的操作
  • Function<T,R>:接受一個輸入引數,返回一個結果。
  • Predicate:接受一個輸入引數,返回一個布林值結果。
  • Supplier:無引數,返回一個結果。
  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.util.Comparator

除了幾個 java8 前就有的複合函數式介面定義的介面,函數式介面大都定義在 java.utils.function 包下

Lambda 實戰

  1. Lambda 表示式,也可稱為閉包,它是推動 Java 8 釋出的最重要新特性。

  2. Lambda 允許把函數作為一個方法的引數(函數作為引數傳遞進方法中),用程式碼來展示會更加清晰明瞭

@FunctionalInterface
interface FunctionInterfaceOne {

    void methodOne(String msg);

}

public class FunctionInterfaceDemo {

    public void methodOne(FunctionInterfaceOne functionInterfaceOne) {
        functionInterfaceOne.methodOne("FunctionInterfaceDemo#methodOne");
    }

    public static void main(String[] args) {
        FunctionInterfaceDemo demo = new FunctionInterfaceDemo();
        demo.methodOne(msg -> System.out.println(msg));
    }
}

如上程式碼,又一個函數式介面 FunctionInterfaceOne,類 FunctionInterfaceDemo#methodOne 接收一個 FunctionInterfaceOne 型別的範例,在 main 方法中,直接通過 lambda 表示式(msg -> System.out.println(msg))構建出來一個範例傳入該方法中。按照以前的寫法就是設計一個匿名內部類,傳入該方法,或者為該介面定義一個實現類,然後生成一個範例,將範例作為引數傳入 FunctionInterfaceDemo#methodOne 方法中。

  1. 使用 Lambda 表示式可以使程式碼變的更加簡潔緊湊。

語法

(parameters) -> expression

(parameters) ->{ statements; }

  • 可選型別宣告:不需要宣告引數型別,編譯器可以統一識別引數值。
() -> 5  // 不需要引數,返回值為 5 
  • 可選的引數圓括號:一個引數無需定義圓括號,但多個引數需要定義圓括號。

msg -> System.out.println(msg)

(a,b) -> a+b

  • 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。
(a, b) -> {
 System.out.println(a + b);
 return a + b
}
  • 可選的返回關鍵字:如果主體只有一個表示式返回值則編譯器會自動返回值,大括號需要指定表示式返回了一個數值。

(a,b) -> a+b

(a,b) -> return a+b

Stream

stream 是 java8 卓越的新特性之一,這種風格是將要處理的資料看成一種管道流,在管道中進行一系列的中間操作,然後使用最終操作得到最終想要的資料以及資料結構。由於中間操作以及最終操作的很多方法的入參都是函數式介面,因此,stream 往往配合 lambda 表示式進行使用。

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

生成流

流的資料來源可以是陣列、集合等

  • stream() -> 生成序列流
  • ParallelStream -> 生成並行流
public class StreamDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 3, 1, 2, 3, 4, 1, 2, 3, 41, 1, 23, 1, 23213, 43);
        list.stream().sorted().collect(Collectors.toList());
        list.parallelStream().sorted().collect(Collectors.toList());
    }
}

中間操作

這裡簡單記錄下幾個常見的中間操作

Stream<T> filter(Predicate<? super T> predicate)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream<T> distinct();
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
Stream<T> peek(Consumer<? super T> action);
Stream<T> limit(long maxSize);
Stream<T> skip(long n);
public class StreamDemo {

    /**
     * 塞選出大於 10 的元素並且列印出來
     * filter 入參是一個 Predicate 函數式介面,接收一個引數並且返回一個布林值,這裡可以使用 lambda 表示式配合使用
     */
    public static void filterDemo(Stream<Integer> stream) {
        System.out.println(stream.filter(item -> item > 10).collect(Collectors.toList()));
    }

    /**
     * 將流內元素都乘 2 並且列印出來
     * map 入參是一個 Function 函數式介面,接收一個引數並且返回另一個引數
     */
    public static void mapDemo(Stream<Integer> stream) {
        System.out.println(stream.map(item -> item * 2).collect(Collectors.toList()));
    }

    /**
     * 將流元素去重(使用元素的 equal 方法判斷元素是否相等)並且列印出來
     */
    public static void distinctDemo(Stream<Integer> stream) {
        System.out.println(stream.distinct().collect(Collectors.toList()));
    }

    /**
     * 將元素排序,如果 sorted 方法沒有傳引數,那麼是按照自然順序生序排,否則按照 Comparator 介面介面定義的規則排
     * 一個 Stream 物件不能重複利用,否則會報錯
     */
    public static void sortedDemo(Stream<Integer> stream) {
//        System.out.println(stream.sorted().collect(Collectors.toList()));
        System.out.println(stream.sorted((one, two) -> two - one).collect(Collectors.toList()));
    }

    /**
     * peek 方法的入參是一個 Consumer 函數式介面,該介面接收一個引數但是不返回引數,因此,peek 方法只能根據元素做一些操作,但是無法返回,與遍歷有點像
     * 此方法的存在主要是為了支援偵錯,在偵錯中,我們希望在元素流過管道中的某個點時看到它們:
     */
    public static void peekDemo(Stream<Integer> stream) {
//        stream.peek(System.out::println).collect(Collectors.toList());
    }

    /**
     * limit 方法用於取出前 n 個元素,然後基於這 n 個元素生成一個新的流用於後續的操作,和 sql 中的 limit 關鍵字用法一樣
     */
    public static void limitDemo(Stream<Integer> stream) {
        System.out.println(stream.limit(2).collect(Collectors.toList()));
    }

    /**
     * skip 方法用於跳過前 n 個元素,將剩下的元素生成一個新的流用於後續操作,和 sql 中的 offset 關鍵字用法一樣
     */
    public static void skipDemo(Stream<Integer> stream) {
        System.out.println(stream.skip(2).collect(Collectors.toList()));
    }


    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 3, 1, 2, 3, 4, 1, 2, 3, 41, 1, 23, 1, 23213, 43);
        filterDemo(list.stream());
        mapDemo(list.stream());
        distinctDemo(list.stream());
        sortedDemo(list.stream());
        peekDemo(list.stream());
        limitDemo(list.stream());
        skipDemo(list.stream());
    }
}

這裡需要注意的一個點是流內所有的中間操作都必須遇到終端操作才會執行,否則就是一個定義,不會真正跑,如下程式碼

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 3, 1, 2, 3, 4, 1, 2, 3, 41, 1, 23, 1, 23213, 43);

    Stream<Integer> integerStream = list.stream().map(item -> {
        int i = item * 2;
        System.out.println(i);// 1
        return i;
    });
    System.out.println("this is a temp");// 2
    integerStream.collect(Collectors.toList());// 3
}

按照程式碼的自上而下執行的順序原則,1 處的列印應該先執行,2 處的列印應該後執行,但是事實是 2 處的列印反而先執行,如果我們將 3 處的程式碼註釋掉,那麼 1 處的程式碼就不會執行了,因此,所有流的中間操作都必須遇到終端操作才會真正執行。

終端操作

流的終端操作是得到之前經過一系列中間操作後的結果

常見的終端操作有

<R, A> R collect(Collector<? super T, A, R> collector);
void forEach(Consumer<? super T> action);
Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
long count();
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
Optional<T> findFirst();
Optional<T> findAny();
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
public class StreamDemo {

    /**
     * 將流以集合的形式輸出
     */
    public static void collectDemo(Stream<Integer> stream) {
        System.out.println(stream.collect(Collectors.toList()));
//        System.out.println(stream.collect(Collectors.toSet()));
    }

    /**
     * 對此流的每個元素執行操作,也就是遍歷
     */
    public static void forEachDemo(Stream<Integer> stream) {
        stream.forEach(item -> System.out.println(item));
    }

    /**
     * 將流轉化成陣列元素
     */
    public static void toArrayDemo(Stream<Integer> stream) {
//        Object[] objects = stream.toArray();
        Integer[] integers = stream.toArray(Integer[]::new);
    }

    /**
     * 獲取流中的元素個數
     */
    public static void countDemo(Stream<Integer> stream){
        System.out.println(stream.count());
    }

    /**
     * 流中元素是否有任意一個滿足條件
     */
    public static void anyMatchDemo(Stream<Integer> stream) {
        System.out.println(stream.anyMatch(item -> Objects.equals(item, 1)));
    }

    /**
     * 流中元素是否都滿足條件
     */
    public static void allMatchDemo(Stream<Integer> stream) {
        System.out.println(stream.allMatch(item -> Objects.equals(item, 1)));
    }

    /**
     * 流中元素是否沒有一個滿足條件
     */
    public static void noneMatchDemo(Stream<Integer> stream) {
        System.out.println(stream.noneMatch(item -> Objects.equals(item, 1)));
    }

    /**
     * 獲取流中的第一個元素
     */
    public static void findFirstDemo(Stream<Integer> stream) {
        Optional<Integer> first = stream.findFirst();
        first.ifPresent(System.out::println);
    }

    /**
     * 獲取流中的任意一個元素
     */
    public static void findAnyDemo(Stream<Integer> stream) {
        Optional<Integer> element = stream.findAny();
        element.ifPresent(System.out::println);
    }

    /**
     * 獲取流中最大元素(根據自己定義的 Comparator 介面來)
     */
    public static void maxDemo(Stream<Integer> stream) {
        Optional<Integer> max = stream.max((a, b) -> a - b);
        max.ifPresent(System.out::println);
    }

    /**
     * 獲取流中最小元素(根據自己定義的 Comparator 介面來)
     */
    public static void minDemo(Stream<Integer> stream) {
        Optional<Integer> max = stream.min((a, b) -> a - b);
        max.ifPresent(System.out::println);
    }

    /**
     * 聚合操作
     */
    public static void reduceDemo(List<Integer> list) {
        // 對 list 元素求和,然後加上 0
        System.out.println(list.stream().reduce(0, Integer::sum));
        // 對 list 元素求和,然後加上 10000
        System.out.println(list.stream().reduce(10000, Integer::sum));
        System.out.println(list.stream().reduce(0, (a, b) -> a - b));
        System.out.println(list.stream().reduce(0, (a, b) -> a / b));
        System.out.println(list.stream().reduce(0, (a, b) -> a * b));

        // 最大和最小
        System.out.println(list.stream().reduce(0, Integer::min));
        System.out.println(list.stream().reduce(0, Integer::max));
    }
}

Optional

optional 是 java 8 新的判空特性,

老的判空方法

public class OptionalDemo {

    static class A {
        B b;
    }

    static class B {
        Integer c;
    }

    private static A getA() {
        return new A();
    }

    public static void main(String[] args) {
        A a = getA();
        if(a != null){
            B b = a.b;
            if(b != null){
                System.out.println(a.b.c);
            }
        }
    }

}

optional 判空方法

public class OptionalDemo {

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class A {
        B b;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class B {
        Integer c;
    }

    private static A getA() {
        return new A(new B(1));
    }

    public static void main(String[] args) {
        A a = getA();
        Optional.ofNullable(a).map(A::getB).map(B::getC).ifPresent(System.out::println);
    }

}

optional 特性

optional

// 空物件對應的 Optional 範例
private static final Optional<?> EMPTY = new Optional<>();
// Optional 範例的操作物件,可能為 null,也可能有值
private final T value;

// 建構函式,value 不能為空,為空丟擲 npe 異常
private Optional(T value) {
  this.value = Objects.requireNonNull(value);
}

// 如果 value 為空,則返回 empty 物件,否則構造一個 Optional 範例
public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

// 返回一個 EMPTY 物件
public static<T> Optional<T> empty() {
  @SuppressWarnings("unchecked")
  Optional<T> t = (Optional<T>) EMPTY;
  return t;
}

// 構造一個 Optional 範例,如果 value 為空,則丟擲 npe 異常
public static <T> Optional<T> of(T value) {
  return new Optional<>(value);
}

常用的構造 Optional 物件通常使用 ofNullable 方法,如果想要讓異常透傳出來,才使用 of 方法

map

// 通過入參 mapper 構造一個新的 Optional 物件
// 如果入參 mapper 為null,丟擲 npe 異常,如果對應的 Optional 範例的 value 為 null,返回 EMPTY 物件,否則生成新的 Optional // 物件用於後續操作
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
  Objects.requireNonNull(mapper);
  if (!isPresent())
    return empty();
  else {
    return Optional.ofNullable(mapper.apply(value));
  }
}

isPresent

// 如果操作的 Optional 物件的 value 值不為空,返回 true,否則返回 false
public boolean isPresent() {
    return value != null;
}
// 如果操作的 Optional 物件的 value 值不為空,則執行對應的邏輯
public void ifPresent(Consumer<? super T> consumer) {
  if (value != null)
    consumer.accept(value);
}
// 用法
Optional.ofNullable(a).map(A::getB).map(B::getC).ifPresent(System.out::println);

獲取值

// 如果 Optional 物件的 value 不為 null,返回 value,否則返回 other
public T orElse(T other) {
    return value != null ? value : other;
}
Integer integer = Optional.ofNullable(a).map(A::getB).map(B::getC).orElse(0);

// 如果 Optional 物件的 value 不為 null,返回 value,否則執行 other 的邏輯
public T orElseGet(Supplier<? extends T> other) {
  return value != null ? value : other.get();
}
Integer value = Optional.ofNullable(a).map(A::getB).map(B::getC).orElseGet(() -> {
  System.out.println("value is null");
  return 0;
});

// 如果value != null 返回value,否則丟擲引數返回的異常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
  if (value != null) {
    return value;
  } else {
    throw exceptionSupplier.get();
  }
}
try {
  Integer value = Optional.ofNullable(a).map(A::getB).map(B::getC).orElseThrow(() -> {
    System.out.println("value is null");
    return new Exception("value is null");
  });
} catch (Exception e) {
  e.printStackTrace();
}

/**
* value為null丟擲NoSuchElementException,不為空返回value。
*/
public T get() {
  if (value == null) {
      throw new NoSuchElementException("No value present");
  }
  return value;
}
Integer integer = Optional.ofNullable(a).map(A::getB).map(B::getC).get();

過濾值

/**
* 1. 如果是empty返回empty
* 2. predicate.test(value)==true 返回this,否則返回empty
*/
public Optional<T> filter(Predicate<? super T> predicate) {
  Objects.requireNonNull(predicate);
  if (!isPresent())
    return this;
  else
    return predicate.test(value) ? this : empty();
}

常見用法

Optional.ofNullable(a).map(A::getB).map(B::getC).filter(v->v==1).orElse(0);

Date-Time

Java 8 新的時間特性

LocalDateTime.class //日期+時間 format: yyyy-MM-ddTHH:mm:ss.SSS
LocalDate.class //日期 format: yyyy-MM-dd
LocalTime.class //時間 format: HH:mm:ss

格式化

//format yyyy-MM-dd HH:mm:ss
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String dateTimeStr = dateTime.format(dateTimeFormatter);
System.out.println(String.format("dateTime format : %s", dateTimeStr));

字串轉日期

LocalDate date = LocalDate.of(2021, 1, 26);
LocalDate.parse("2021-01-26");

LocalDateTime dateTime = LocalDateTime.of(2021, 1, 26, 12, 12, 22);
LocalDateTime.parse("2021-01-26 12:12:22");

LocalTime time = LocalTime.of(12, 12, 22);
LocalTime.parse("12:12:22");

參考

https://github.com/CSanmu/JavaGuide/blob/main/docs/java/new-features/java8-common-new-features.md

https://www.runoob.com/java/java8-streams.html