1. 程式人生 > >Java8:lambda表示式和Stream API

Java8:lambda表示式和Stream API

Java8 的新特性:Lambda表示式、強大的 Stream API、全新時間日期 API、ConcurrentHashMap、MetaSpace。總得來說,Java8 的新特性使 Java 的執行速度更快、程式碼更少、便於並行、最大化減少空指標異常。

本篇部落格將以筆者的一些心得幫助大家快速理解lambda表示式和Stream API.

一:lambda

1.引言

在IDE中,你是否遇到在寫以下列程式碼時,被友情提示的情況:

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread");
            }
        });

這時候,我們按一下快捷鍵,IDE自動幫我們把程式碼優化為醬個樣子:

new Thread(() -> System.out.println("thread"));

這就是Java8的新特性:lambda表示式

2.lambda表示式

借用引言中的示例,在呼叫new Thread的含參構造方法時,我們通過匿名內部類的方式實現了Runnable物件,但其實有用的程式碼只有System.out.println("thread");這一句,而我們卻要為了這一句去寫這麼多行程式碼。正是這個問題,才有了Java8中的lambda表示式。那lambd表示式究竟是如何簡化程式碼的呢?

先來看lambda表示式的語法:

() -> {}
  1. () : 括號就是介面方法的括號,介面方法如果有引數,也需要寫引數。只有一個引數時,括號可以省略。

  2. -> : 分割左右部分的,沒啥好講的。

  3. {} : 要實現的方法體。只有一行程式碼時,可以不加括號,可以不寫return。

看了上面的解釋,也就不難理解IDE優化後的程式碼了。不過看到這裡你也許意識到,如果介面中有多個方法時,按照上面的邏輯lambda表示式恐怕不行了。沒錯,lambda表示式只適用於函式型介面。說白了,函式型介面就是隻有一個抽象方法的介面。這種型別的介面還有一個對應的註解:@FunctionalInterface

為了讓我們在需要這種介面時不再自己去建立,Java8中內建了四大核心函式型介面:

消費型介面(有參無返回值)

Consumer<T>

void accept(T t);

供給型介面(無參有返回值)

Supplier<T>

T get();

函式型介面(有參有返回值)

Function<T, R>

R apply(T t);

斷言型介面(有參有布林返回值)

Predicate<T>

boolean test(T t);

看到這裡如果遇到一般的lambda表示式,你應該可以從容面對了,但高階點的恐怕看到還是懵,不要急,其實也不難。

方法引用

lambda表示式還有兩種簡化程式碼的手段,它們是方法引用、構造引用。

方法引用是什麼呢?如果我們要實現介面的方法與另一個方法A類似,(這裡的類似是指引數型別與返回值部分相同),我們直接宣告A方法即可。也就是,不再使用lambda表示式的標準形式,改用高階形式。無論是標準形式還是高階形式,都是lambda表示式的一種表現形式。

舉例:

   Function function1 = (x) -> x;
   Function function2 = String::valueOf;

對比Function介面的抽象方法與String的value方法,可以看到它們是類似的。

    R apply(T t);
    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

方法引用的語法:

物件::例項方法 類::靜態方法 類::例項方法

前兩個很容易理解,相當於物件呼叫例項方法,類呼叫靜態方法一樣。只是第三個需要特殊說明。

當出現如下這種情況時:

Compare<Boolean> c = (a, b) -> a.equals(b);

用lambda表示式實現Compare介面的抽象方法,並且方法體只有一行,且該行程式碼為引數1呼叫方法傳入引數2。此時,就可以簡化為下面這種形式:

Compare<Boolean> c = String::equals;

也就是“類::例項方法”的形式。

構造引用

先來建立一個供給型介面物件:

Supplier<String> supplier = () -> new String();

在這個lammbda表示式中只做了一件事,就是返回一個新的Test物件,而這種形式可以更簡化:

Supplier<String> supplier = String::new;

提煉一下構造引用的語法:

類名::new

當通過含參構造方法建立物件,並且引數列表與抽象方法的引數列表一致,也就是下面的這種形式:

Function1 function = (x) -> new String(x);

也可以簡化為:

Function1 function = String::new;

特殊點的陣列型別:

Function<Integer,String[]> function = (x) -> new String[x];

可以簡化為:

Function<Integer,String[]> function = String[]::new;

3.lambda總結

上面並沒有給出太多的lambda例項,只是側重講了如何去理解lambda表示式。到這裡,不要懵。要記住lambda的本質:為函式型介面的匿名實現進行簡化與更簡化。

所謂的簡化就是lambda的標準形式,所謂的更簡化是在標準形式的基礎上進行方法引用和構造引用。

方法引用是拿已有的方法去實現此刻的介面。

構造引用是對方法體只有一句new Object()的進一步簡化。

二:Stream

在我看來,學習lambda與學習Stream的聯絡就是因為在許多部落格、文件中對Stream API的講解大量使用lambda表示式,導致不學lambda表示式看不懂Stream API。

1.如何理解Stream

Stream 不是集合元素,它不是資料結構並不儲存資料,它是有關演算法和計算的,它更像一個高階版本的 Iterator。簡單來說,它的作用就是通過一系列操作將資料來源(集合、陣列)轉化為想要的結果。

2.Stream特點

  1. Stream 是不會儲存元素的。

  2. Stream 不會改變原物件,相反,他們會返回一個持有結果的新Stream。

  3. Stream 操作是延遲執行的。意味著它們會等到需要結果的時候才執行。

3.生成Stream的方式

//Collection系的 stream() 和 parallelStream();
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> stringStream = list.parallelStream();

//通過Arrays
Stream<String> stream1 = Arrays.stream(new String[10]);

//通過Stream
Stream<Integer> stream2 = Stream.of(1, 2, 3);

//無限流
//迭代
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
iterate.limit(10).forEach(System.out::println);

//生成
Stream<Double> generate = Stream.generate(() -> Math.random());
generate.forEach(System.out::println);

4.Stream的中間操作

多箇中間操作連線而成為流水線,流水線不遇到終止操作是不觸發任何處理的,所為又稱為“惰性求值”。

list.stream()
                .map(s -> s + 1)  //對映
                .flatMap(s -> Stream.of(s)) 
//和map差不多,但返回型別為Stream,類似list.add()和list.addAll()的區別
                .filter(s -> s < 1000)    //過濾
                .limit(5)   //限制
                .skip(1)    //跳過
                .distinct() //去重
                .sorted()   //自然排序
                .sorted(Integer::compareTo) //自定義排序

關於map方法,引數為一個Function函式型介面的物件,也就是傳入一個引數返回一個物件。這個引數就是集合中的每一項。類似Iterator遍歷。其它的幾個操作思想都差不多。

執行上面的方法沒什麼用,因為缺少終止操作。

5.Stream的終止操作

list.stream().allMatch((x) -> x == 555); // 檢查是否匹配所有元素
list.stream().anyMatch(((x) -> x>600)); // 檢查是否至少匹配一個元素
list.stream().noneMatch((x) -> x>500); //檢查是否沒有匹配所有元素
list.stream().findFirst(); // 返回第一個元素
list.stream().findAny(); // 返回當前流中的任意一個元素
list.stream().count(); // 返回流中元素的總個數
list.stream().forEach(System.out::println); //內部迭代
list.stream().max(Integer::compareTo); // 返回流中最大值
Optional<Integer> min = list.stream().min(Integer::compareTo);//返回流中最小值
System.out.println("min "+min.get());

reduce (歸約):將流中元素反覆結合起來得到一個值

Integer reduce = list.stream()
        .map(s -> (s + 1))
        .reduce(0, (x, y) -> x + y);    
        //歸約:0為第一個引數x的預設值,x是計算後的返回值,y為每一項的值。
System.out.println(reduce);

Optional<Integer> reduce1 = list.stream()
        .map(s -> (s + 1))
        .reduce((x, y) -> x + y); 
         // x是計算後的返回值,預設為第一項的值,y為其後每一項的值。
System.out.println(reduce);

collect(收集):將流轉換為其他形式。需要Collectors類的一些方法。

        //轉集合
        Set<Integer> collect = list.stream()
                .collect(Collectors.toSet());

        List<Integer> collect2 = list.stream()
                .collect(Collectors.toList());

        HashSet<Integer> collect1 = list.stream()
                .collect(Collectors.toCollection(HashSet::new));

        //分組 {group=[444, 555, 666, 777, 555]}
        Map<String, List<Integer>> collect3 = list.stream()
                .collect(Collectors.groupingBy((x) -> "group"));//將返回值相同的進行分組
        System.out.println(collect3);

        //多級分組 {group={777=[777], 666=[666], 555=[555, 555], 444=[444]}}
        Map<String, Map<Integer, List<Integer>>> collect4 = list.stream()
                .collect(Collectors.groupingBy((x) -> "group", Collectors.groupingBy((x) -> x)));
        System.out.println(collect4);

        //分割槽 {false=[444], true=[555, 666, 777, 555]}
        Map<Boolean, List<Integer>> collect5 = list.stream()
                .collect(Collectors.partitioningBy((x) -> x > 500));
        System.out.println(collect5);

        //彙總
        DoubleSummaryStatistics collect6 = list.stream()
                .collect(Collectors.summarizingDouble((x) -> x));
        System.out.println(collect6.getMax());
        System.out.println(collect6.getCount());

        //拼接 444,555,666,777,555
        String collect7 = list.stream()
                .map(s -> s.toString())
                .collect(Collectors.joining(","));
        System.out.println(collect7);

        //最大值
        Optional<Integer> integer = list.stream()
                .collect(Collectors.maxBy(Integer::compare));
        System.out.println(integer.get());