這篇最全 Java 8 講解,有沒有之一先看後說!

你們好,我是小菜,一個渴望在互聯網行業作到蔡不菜的小菜。可柔可剛,點贊則柔,白嫖則剛! 死鬼~看完記得給我來個三連哦!java

本文主要介紹 Java 8中的新特性編程

若有須要,能夠參考數組

若有幫助,不忘 點贊安全

微信公衆號已開啓,【小菜良記】,沒關注的同窗們記得關注哦!微信

1、基礎知識

1)爲何要學習 Java8

Java 8 所作的改變,在許多方面比Java 歷史上任何一次改變都更加深遠,這些改變會讓你的編程更加容易markdown

例子:數據結構

傳統寫法多線程

List<Person> personList = Arrays.asList(new Person(21,50),new Person(22,55),new Person(23,60));
Collections.sort(personList, new Comparator<Person>() {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.getWeight().compareTo(o2.getWeight());
    }
});
複製代碼

Java 8寫法架構

personList.sort(Comparator.comparing(Person::getWeight));
複製代碼

熟悉 Linux 操做的同窗對這個指令應該不默認app

cat testFile | tr "[A-Z]" "[a-z]" | sort | tail -3

這種操做即是基於流式操做,cat 會把文件轉換建立成一個流,而後tr會轉換流中字符,sort會對流中的行進行排序,tail -3則會輸出流的最後三行。這種就像是流水線操做,通過每一箇中轉站,將處理完的結果轉入下一個處理中心,最後獲得最終結果。

Java 8 的第一個編程思想就是流處理,流式一系列數據項,一次只生成一項,程序能夠從輸入流中一個一個讀取數據項,而後以一樣的方式將數據項寫入輸出流。一個程序的輸出流極可能就是另外一個程序的輸入流。

函數傳遞

已知一個集合中有如下幾種花:

List<Flower> flowerList = Arrays.asList(new Flower("red", 6), new Flower("yellow", 7), new Flower("pink", 8));
複製代碼

這個時候若是我想要紅花,那麼傳統寫法是這樣子的:

List<Flower> resList = new ArrayList<>();
for (Flower flower : flowerList) {
    if (StringUtils.equals("red", flower.getColor())) {
        resList.add(flower);
    }
}
複製代碼

那麼若是我想要8塊錢如下的花,那麼寫法就是這樣的:

List<Flower> resList = new ArrayList<>();
for (Flower flower : flowerList) {
    if (flower.getPrice() < 8) {
        resList.add(flower);
    }
}
複製代碼

其實代碼寫法大部分都是同樣的,只是判斷的條件不同,那麼咱們進行初版優化

咱們將判斷方法抽取出來:

public static boolean isRed(Flower flower) {
    return StringUtils.equals("red", flower.getColor());
}

public static boolean isLowPrice(Flower flower) {
    return flower.getPrice() < 8;
}
複製代碼

藉助函數式接口Predicate,將咱們自定義的方法傳遞進去:

public static List<Flower> filterFlower(List<Flower> flowers, Predicate<Flower> p) {
    List<Flower> resList = new ArrayList<>();
    for (Flower flower : flowers) {
        if (p.test(flower)) {
            resList.add(flower);
        }
    }
    return resList;
}
複製代碼

使用:

filterFlower(flowerList,Flower::isRed);
filterFlower(flowerList,Flower::isLowPrice);
複製代碼

咱們也能夠藉助 Lambda 流來傳遞函數,就能夠不用事先寫好判斷函數了:

filterFlower(flowerList, (Flower f) -> StringUtils.equals("red", f.getColor()));
filterFlower(flowerList, (Flower f) -> f.getPrice() < 8);
複製代碼

默認方法

在 Java 8 以前咱們能夠實現一個接口而後被強制重寫這個接口的方法,那麼隱含着不少問題:若是動物類新增一個fly()方法,那麼其實dog這個類是不須要fly這個方法的,可是若是不重寫就會編譯報錯。所以在 Java 8 以後也設計了默認方法這一種方式巧妙的解決了這種問題。

interface Animal {
    void eat();
    void fly();
}

class bird implements Animal {
    @Override
    public void eat() {}

    @Override
    public void fly() {
        System.out.println("bird fly");
    }
}

class dog implements Animal {
    @Override
    public void eat() {}
}
複製代碼

Java 8 以後能夠這樣寫:

interface Animal {
    void eat();
    default void fly() {
        System.out.println("animal fly");
    }
}

class bird implements Animal {
    @Override
    public void eat() {}

    @Override
    public void fly() {
        System.out.println("bird fly");
    }
}

class dog implements Animal {
    @Override
    public void eat() {}
}
複製代碼

以上即是 Java 8 的部分特性,那麼接下來就讓咱們來了解 Java 8的使用

2)行爲參數化

開發中,咱們須要應對不斷的需求,怎麼樣才能作到自適應可擴展就是咱們要關注的地方。

需求1:篩選出紅色的花

public static List<Flower> filterFlower(List<Flower> flowers) {
    List<Flower> resList = new ArrayList<>();
    for (Flower flower : flowers) {
        if (StringUtils.equals("red", flower.getColor())) {
            resList.add(flower);
        }
    }
}
複製代碼

需求2:篩選出綠色的話

聰明的你確定想到了咱們能夠經過傳遞一個顏色參數來過濾花朵,而不用每一次都修改主要代碼。

public static List<Flower> filterFlowerByColor(List<Flower> flowers, String color) {
    List<Flower> resList = new ArrayList<>();
    for (Flower flower : flowers) {
        if (StringUtils.equals(color, flower.getColor())) {
            resList.add(flower);
        }
    }
}
複製代碼

需求3:篩選出價格小於8塊錢的花

這樣子咱們只能再寫一個方法來實現這個需求,爲了防止後續價格的變化,聰明的咱們提早將價格設置成可變參數。

public static List<Flower> filterFlowerByPrice(List<Flower> flowers, Integer price) {
    List<Flower> resList = new ArrayList<>();
    for (Flower flower : flowers) {
        if (flower.getPrice() < price) {
            resList.add(flower);
        }
    }
}
複製代碼

爲了保持代碼的整潔,咱們被迫重寫了一個方法來實現上述的需求:

public static List<Flower> filterFlower(List<Flower> flowers, String color, Integer price, Boolean flag) {
    List<Flower> resList = new ArrayList<>();
    for (Flower flower : flowers) {
        if ((flag && flower.getPrice() < price) ||
            (!flag && StringUtils.equals(color, flower.getColor()))) {
            resList.add(flower);
        }
    }
    return resList;
}
複製代碼

經過flag來控制要篩選價格類型的花仍是顏色類型的花,可是這種寫法實在是不美觀。

那麼,咱們既然都能把花的屬性做爲參數進行傳遞,那麼咱們能不能咱們能不能把過濾花的這種行爲也做爲一個參數進行傳遞,想着想着,你就動起了手:

首先定義一個過濾行爲的接口:

interface FilterPrecidate {
    boolean test(Flower flower);
}
複製代碼

而後自定義兩個行爲過濾類繼承這個接口:

class RedColorFilterPredicate implements FilterPrecidate {
    @Override
    public boolean test(Flower flower) {
        return StringUtils.equals("red", flower.getColor());
    }
}

class LowPriceFilterPredicate implements FilterPrecidate {
    @Override
    public boolean test(Flower flower) {
        return flower.getPrice() < 8;
    }
}
複製代碼

而後重寫咱們的過濾方法,經過將行爲做爲參數傳遞:

public static List<Flower> filterFlower(List<Flower> flowers, FilterPrecidate filter) {
    List<Flower> resList = new ArrayList<>();
    for (Flower flower : flowers) {
        if (filter.test(flower)) {
            resList.add(flower);
        }
    }
    return resList;
}

/***** 使用 *****/
filterFlower(flowerList,new RedColorFilterPredicate());
filterFlower(flowerList,new LowPriceFilterPredicate());
複製代碼

這樣子咱們的代碼已經很明瞭,可是咱們再觀察一下上面的方法,filterFlower()這個方法只能傳遞對象做爲參數,而FilterPrecidate對象的核心方法也只有test(),若是咱們有新的行爲就須要新建一個類繼承FilterPrecidate接口實現test()方法。那麼咱們有沒有辦法直接將test()這一個行爲做爲參數傳遞,答案是有的:Lombda.

filterFlower(flowerList, (Flower flower) -> flower.getPrice() > 8);
複製代碼

咱們甚至能夠將多種行爲做爲做爲一個參數傳遞:

filterFlower(flowerList, (Flower flower) -> flower.getPrice() > 8 && StringUtils.equals("red", flower.getColor()));
複製代碼

能夠看到,行爲參數化是一個頗有用的模式,它可以輕鬆地使用不斷變化的需求,這種模式能夠把一個行爲封裝起來,並經過傳遞和使用建立的行爲將方法的行爲參數化。

它能夠替代匿名類

若是咱們將一個鮮花的集合按照價格進行排序,咱們會這樣作:

Collections.sort(flowerList, new Comparator<Flower>() {
    @Override
    public int compare(Flower o1, Flower o2) {
        return o1.getPrice().compareTo(o2.getPrice());
    }
});
複製代碼

那麼經過行爲參數化咱們能夠這樣寫:

Collections.sort(flowerList,(o1, o2) -> o1.getPrice().compareTo(o2.getPrice()));
複製代碼

也能夠這樣寫:

Collections.sort(flowerList, Comparator.comparing(Flower::getPrice));
複製代碼

甚至能夠這樣寫:

flowerList.sort(Comparator.comparing(Flower::getPrice));
複製代碼

對比一下傳統寫法,你是否是已經開始愛上這種方式的寫法了

3)初識 Lambda

Lambda能夠理解爲是一種簡潔的匿名函數的表示方式:它沒有名稱,但它有參數列表函數主體返回類型,還能夠有一個能夠拋出的異常

Lambda表達式鼓勵採用行爲參數化的風格。利用Lambda表達式咱們能夠自定義一個Comparator對象

Lambda 例子

  • (String s)-> s.length()從一個對象中抽取值,具備一個 String 類型的參數,返回一個 int 類型的值,Lambda 表達式沒有 return 語句,已經隱含了 return

  • (Flower f) -> f.getPrice() > 8布爾表達式,具備一個 Flower 類型的參數,返回一個 boolean 類型的值

  • (String s) -> {System.out.print(s);} 消費一個對象,具備一個 String 類型的參數,沒有返回值(void)

  • () -> new Flower("red",8)建立一個對象,沒有傳入參數,返回一個 int 類型的值(1)

函數式接口

函數式接口就是隻定義一個抽象方法的接口,並使用@FunctionalInterface標記。

例如

  • public interface Comparator<T>{
        int compare(T o1, T o2);
    }
    複製代碼
  • public interface Runnable{
        void run();
    }
    複製代碼
  • public interface ActionListener extends EventListener{
    	void actionPerformed(ActionEvent e);
    }
    複製代碼
  • public interface Callable<V>{
    	V call();
    }
    複製代碼

Lambda 表達式能夠容許直接之內聯的形式爲函數式接口的抽象方法提供實現,並把整個表達式做爲函數式接口的示例(Lambda表達式就是函數式接口一個具體實現的示例)

Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("這是傳統的寫法");
        }
    };

Runnable r = () -> System.out.println("這是使用 Lambda 的寫法");
複製代碼

使用函數式接口

Predicate

這個接口中定義了一個test()的抽象方法,它接受泛型 T 對象,並返回一個 boolean。你若是須要 表示一個涉及類型 T 的布爾表達式時,就可使用這個接口。

public static List<Flower> filterFlower(List<Flower> flowers, Predicate<Flower> p) {
    List<Flower> resList = new ArrayList<>();
    for (Flower flower : flowers) {
        if (p.test(flower)) {
            resList.add(flower);
        }
    }
    return resList;
}

/***** 使用方式 *****/
filterFlower(flowerList, (Flower flower) -> flower.getPrice() > 8);
複製代碼

Consumer

這個接口定義了一個accept()的抽象方法,它接受泛型 T 對象,沒有返回(void)。你若是須要訪問類型 T 的對象,並對其執行某些操做,就可使用這個接口。

List<Integer> nums = Arrays.asList(1,2,3,4);
nums.forEach(integer -> System.out.println(integer));
複製代碼

Function

這個接口定義了一個apply()的抽象方法,它接受泛型 T 對象,並返回一個泛型 R 的對象。你若是須要定義一個Lambda,將輸入對象的信息映射輸出,就可使用這個接口。

(String s) -> s.length()
複製代碼

Supplier

這個接口定義了一個get()的抽象方法,它沒有傳入參數,會返回一個泛型 T 的對象,若是你須要定義一個 Lambda,輸出自定義的對象,就可使用這個接口。

Callable<Integer> call = () -> 1 ;
複製代碼

類型檢查

以這個爲例子:

filter(flowerList, (Flower flower) -> flower.getPrice() > 8);

  • 首先找出 filter 方法的聲明
  • 要求第二個參數是 Predicate 類型的對象
  • Predicate 是一個函數式接口,定義了一個 test()的抽象方法,並返回一個boolean 類型的值

類型推斷

filterFlower(flowerList, (Flower flower) -> flower.getPrice() > 8);

咱們能夠繼續將這個代碼簡化爲:

filterFlower(flowerList, f -> f.getPrice() > 8);

使用局部變量

Lambda 表達式不只可以使用主體裏面的參數,也可以使用自由變量(在外層做用域中定義的變量)。

int tmpNum = 1;
Runnable r = () -> System.out.println(tmpNum);
複製代碼

注意點:Lambda 表達式對於全局變量和靜態變量能夠沒有限制的使用,可是對於局部變量必須顯示聲明爲 final

由於實例變量是存儲在中,而局部變量是存儲在中,屬於線程私有的。而 Lambda 是在一個線程中使用的,訪問局部變量只是在訪問這個變量的副本,而不是訪問原始值。

方法引用

方法引用就是讓你根據已有的方法實現來建立 Lambda表達式。能夠看作是單一方法的 Lambda 的語法糖。

例子

List<Flower> flowerList = Arrays.asList(new Flower("red", 6), new Flower("yellow", 7), new Flower("pink", 8));
複製代碼
  • (Flower f)->f.getPrice(); ==> Flower::getPrice
  • flowerList.stream().map(t -> t.getPrice()).collect(Collectors.toList()); ===> flowerList.stream().map(Flower::getPrice).collect(Collectors.toList());
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
複製代碼
  • nums.forEach(integer -> System.out.println(integer)); ===> nums.forEach(System.out::println);

如何構建方法引用

  • 指向靜態方法的方法引用(Integer的sum方法 == Integer::sum
  • 指向任意類型示例方法的方法引用(String的length方法 == String::length
  • 指向現有對象的示例方法的方法引用(flower實例的getPrice方法 == flower::getPrice

複合 Lambda 表達式

比較器複合

咱們有一組鮮花集合以下:

List<Flower> flowerList = Arrays.asList(new Flower("red", 6), new Flower("yellow", 7), new Flower("pink", 8), new Flower("white", 8));
複製代碼

按鮮花的價格進行排序:

flowerList.sort(Comparator.comparing(Flower::getPrice));
複製代碼

這樣子默認是使用升序進行排列的,那麼咱們若是想進項降序:使用 reversed()

flowerList.sort(Comparator.comparing(Flower::getPrice).reversed());
複製代碼

這裏的粉花和白花的價格同樣,那咱們在價格排序完後再按照顏色排序那應該怎麼作:使用 thenComparing()

flowerList.sort(Comparator.comparing(Flower::getPrice).thenComparing(Flower::getColor));
複製代碼

謂詞複合

用於Predicate接口

  • negate
Predicate<Flower> redFlower = (t) -> StringUtils.equals("red",t.getColor());
Predicate<Flower> notRedFlower = redFlower.negate();
複製代碼
  • and
Predicate<Flower> redFlower = (t) -> StringUtils.equals("red", t.getColor());
Predicate<Flower> lowPriceFlower = (t) -> t.getPrice() < 8;
Predicate<Flower> redAndLowPriceFlower = redFlower.and(lowPriceFlower);
複製代碼
  • or
Predicate<Flower> redFlower = (t) -> StringUtils.equals("red", t.getColor());
Predicate<Flower> lowPriceFlower = (t) -> t.getPrice() < 8;
Predicate<Flower> redOrLowPriceFlower = redFlower.or(lowPriceFlower);
複製代碼

函數複合

用於Function接口

  • andThen
Function<Integer, Integer> addRes = a1 -> a1 + 1;
Function<Integer, Integer> mulRes = a1 -> a1 * 2;
Function<Integer, Integer> andThenResult = addRes.andThen(mulRes);
Integer apply = andThenResult.apply(1);   // 結果爲 4 ==> (1 + 1) * 2
複製代碼
  • compose
Function<Integer, Integer> addRes = a1 -> a1 + 1;
Function<Integer, Integer> mulRes = a1 -> a1 * 2;
Function<Integer, Integer> composeResult = addRes.compose(mulRes);
Integer apply = composeResult.apply(1);  // 結果爲 3 ==> (1 * 2) + 1
複製代碼

二者的區別就是操做的順序不同

2、函數式數據處理

1)流的使用

集合是 Java 中使用最多的API。流是 Java API 的新成員,它容許以聲明式方式處理數據集合,能夠看做是遍歷數據集的高級迭代器。並且,劉海能夠透明地並行處理,這樣就能夠無需多寫任何多線程代碼了。

如今有一組花的集合以下:

List<Flower> flowerList = Arrays.asList(new Flower("red", 10), new Flower("yellow", 7), new Flower("pink", 8), new Flower("white", 8), new Flower("black", 12));
複製代碼

需求:獲取10塊錢如下而且按照價格排序的花的顏色

傳統寫法

List<Flower> lowPriceFlowers = new ArrayList<>();
for (Flower flower : flowerList) {
    if (flower.getPrice() < 10) {
        lowPriceFlowers.add(flower);
    }
}
Collections.sort(lowPriceFlowers, new Comparator<Flower>() {
    @Override
    public int compare(Flower o1, Flower o2) {
        return o1.getPrice().compareTo(o2.getPrice());
    }
});
List<String> lowPriceFlowerColor = new ArrayList<>();
for (Flower priceFlower : lowPriceFlowers) {
    lowPriceFlowerNames.add(priceFlower.getColor());
}
複製代碼

爲了完成這個需求不只代碼量大,還多定義了lowPriceFlowers 這個臨時變量,真的是糟糕透了! Java 8 以後,代碼才應該有它該有的樣子:

List<String> colorList =  flowerList.stream().filter(t->t.getPrice()<10).sorted(Comparator.comparing(Flower::getPrice)).map(Flower::getColor).collect(Collectors.toList());
複製代碼

經過filter篩選出10元如下的花,而後經過sorted按照花的價格進行排序,再經過map映射出花的顏色,最後經過collect將流歸約成一個集合。filter 處理的結果傳給了 sorted 方法,再傳給 map 方法,最後傳給 collect 方法。

甚至咱們還能夠利用多核架構並行執行這段代碼,只須要把stream()換成parallelStream()

flowerList.parallelStream().filter(t->t.getPrice()<10).sorted(Comparator.comparing(Flower::getPrice)).map(Flower::getColor).collect(Collectors.toList());
複製代碼

由於 filtersortedmapcollect 等操做是與具體線程模型無關的高層次構件,因此它們的內部實現能夠是單線程的,也可能透明地充分利用你的多核架構!在實踐中,這意味着你用不着爲了讓某些數據處理任務並行而去操心線程和鎖。

2)流和集合

集合與流之間的差別就在於何時進行計算。集合是一個內存中的數據結構,它包含數據結構中目前全部的值——集合中的每一個元素都得先算出來才能添加到集合中。流則是在概念上固定的數據結構(你不能添加或刪除元素),其元素則是按需計算的。從另外一個角度來講,流就像是一個延遲建立的集合:只有在消費者要求的時候纔會計算值

只能遍歷一次:和迭代器相似,流只能遍歷一次。遍歷完以後,這個流已經被消費掉了。你能夠從原始數據源那裏再得到一個新的流來從新遍歷一遍。

List<String> color = Arrays.asList("red", "yellow", "pink");
Stream<String> s = title.stream();
s.forEach(System.out::println);		//在這裏 流已經被消費了
s.forEach(System.out::println);		//若是這裏再消費流則會報錯!
複製代碼

3)流的操做

流能夠拆成三大操做

獲取流 -> 中間操做 -> 終端操做

List<String> colorList =  flowerList.stream()					  //獲取流
                                    .filter(t->t.getPrice()<10)   //中間操做
                                    .limit(3)					  //中間操做
                                    .map(Flower::getColor)		  //中間操做
                                    .collect(Collectors.toList());//終端操做
複製代碼

中間操做:

操做 返回類型 操做參數
filter Stream Predicate
map Stream Funcation<T, R>
limit Stream
sorted Stream Comparator
distinct Stream
skip Stream long
limit Stream long
flatMap Stream Funcation<T, Steam>

終端操做

操做 返回類型 操做參數
forEach void Consumer
count long
collect R Collector<T, A, R>
anyMatch boolean Predicate
noneMatch boolean Predicate
allMatch boolean Predicate
findAny Optional
findFirst Optional
reduce Optional BinaryOperator

(1)使用流

篩選filter

List<String> colorList =  flowerList.stream().filter(t->t.getPrice()<10).collect(Collectors.toList());
複製代碼

3A4U4.jpg

篩選去重distinct

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
複製代碼

篩選截斷limit

List<String> colorList =  flowerList.stream().filter(t->t.getPrice()<10).limit(3).collect(Collectors.toList());
複製代碼

3AmHV.jpg

篩選跳躍skip

List<String> colorList =  flowerList.stream().filter(t->t.getPrice()<10).skip(2).collect(Collectors.toList());
複製代碼

(2)映射

流支持 map() 方法,它會接受一個函數做爲參數,這個行數會被應用到每一個元素上,並將其映射成一個新的元素。

List<String> colors = flowerList.stream().map(Flower::getColor).collect(Collectors.toList());
複製代碼

它是建立一個新的集合,而不是修改原有的集合

(3)流的扁平化

將一個單詞的集合,拆分紅各個字母的集合:

[Hello,World] ===> [H, e, l, o, W, r, d]

首先咱們嘗試使用map看能不能解決問題:

List<String> words = Arrays.asList("Hello","World");
words.stream().map(t->t.split("")).distinct().collect(Collectors.toList());
/***** 結果 ***** [[Ljava.lang.String;@2cdf8d8a, [Ljava.lang.String;@30946e09] **/
複製代碼

能夠看到,這樣處理後的結果是一個數組的集合,並非咱們想要的結果,這是由於map返回的流其實是Stream<String[]>類型的。可是咱們想要的是Stream<String>來表示一個字符流。

既然須要Stream<String>的字符流,那咱們使用Arrays.stream()來處理試一下:

words.stream().map(t -> t.split("")).map(Arrays::stream).distinct.collect(Collectors.toList());
/***** 結果 ***** [java.util.stream.ReferencePipeline$Head@1698c449, java.util.stream.ReferencePipeline$Head@5ef04b5] **/
複製代碼

這是返回了一個Stream<String>的集合,貌似只要將這個集合處理合並一下就能夠解決問題了。因此flatMap()出現了。

words.stream().map(t->t.split("")).flatMap(t -> Arrays.stream(t)).distinct().collect(Collectors.toList());
/***** 結果 ***** [H, e, l, o, W, r, d] **/
複製代碼

果真,已經成功解決了問題,flatMap方法就是讓你把一個流中的每一個值都轉成另外一個流,而後把全部的流鏈接起來成爲一個流。

UDkRAg.jpg

(4)匹配

  • anyMatch()

    流中是否有一個元素可以匹配所給定謂詞,只有有一個匹配上就返回 true

boolean res = flowerList.stream().anyMatch(t -> t.getPrice() < 8);
複製代碼
  • allMatch()

    流中的元素是否都能匹配給定的謂詞,全部匹配上才能返回 true

boolean res = flowerList.stream().allMatch(t -> t.getPrice() < 8);
複製代碼
  • noneMatch()

    流中沒有任何元素與給定的謂詞相匹配,有一個匹配就會返回 false

boolean res = flowerList.stream().noneMatch(t -> t.getPrice() < 8);
複製代碼

(5)查找

  • findAny

    返回當前流中的任意元素

flowerList.stream().filter(t->t.getPrice()<8).findAny();
複製代碼
  • findFirst

    返回當前流中的第一個元素

flowerList.stream().filter(t->t.getPrice()<8).findFirst();
複製代碼

(6)歸約

reduce 接收兩個參數,一個是初始值,一個是將集合中全部元素結合的操做

reduce也支持一個參數,將集合中全部元素結合的操做,不過返回的是一個 Option ,Option 下面會講到

List<Integer> nums = Arrays.asList(1,2,3,4,5,6,7,8,9);

元素求和

傳統寫法:

int res = 0;
for (Integer num : nums) {
    res += num;
}
複製代碼

改進後:

// 兩個參數版
int res = nums.stream().reduce(0,(a, b) -> a + b);
int res = nums.stream().reduce(0,Integer::sum);
// 一個參數版
Optional<Integer> o = nums.stream().reduce(Integer::sum);
複製代碼

最大值和最小值

傳統寫法:

int max = 0;
int min = Integer.MAX_VALUE;
for (Integer num : nums) {
    if (num > max) {
        max = num;
    }
    if (num < min) {
        min = num;
    }
}
複製代碼

改進後:

// 兩個參數版
int max = nums.stream().reduce(0,Integer::max);
int min = nums.stream().reduce(Integer.MAX_VALUE,Integer::min);
// 一個參數版
Optional<Integer> maxOption = nums.stream().reduce(Integer::max);
Optional<Integer> minOption = nums.stream().reduce(Integer::min);
複製代碼

(7)小練習(出於網上)

(1) 找出2011年發生的全部交易,並按交易額排序(從低到高)。 (2) 交易員都在哪些不一樣的城市工做過? (3) 查找全部來自於劍橋的交易員,並按姓名排序。 (4) 返回全部交易員的姓名字符串,按字母順序排序。 (5) 有沒有交易員是在米蘭工做的? (6) 打印生活在劍橋的交易員的全部交易額。 (7) 全部交易中,最高的交易額是多少? (8) 找到交易額最小的交易

Ur1At1.png

答案:

4)流的構建

  • 由值建立流:Stream.of()/Stream.empty()
Stream<String> stream = Stream.of("hello","world");
Stream<String> emptyStream = Stream.empty();
複製代碼
  • 由數組建立流:Arrays.stream()
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
複製代碼
  • 由文件生成流:File.lines()
long uniqueWords = 0;
try(Stream<String> lines =
Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
}catch(IOException e){
}
// 使用 Files.lines 獲得一個流,其中的每一個元素都是給定文件中的一行。而後,你能夠對 line 調用 split 方法將行拆分紅單詞
複製代碼

5)收集器的使用

現在有一組花的集合以下:

List<Flower> flowerList = Arrays.asList(new Flower("red", 10), new Flower("yellow", 7), new Flower("pink", 8), new Flower("yellow", 8), new Flower("red", 12));
複製代碼

這個時候我想按照花的顏色進行分類,獲取一個Map<String, List<Flower>>

傳統寫法:

Map<String, List<Flower>> listMap = new HashMap<>();
for (Flower flower : flowerList) {
    if (null == listMap.get(flower.getColor())) {
        List<Flower> flowers = new ArrayList<>();
        listMap.put(flower.getColor(), flowerList);
    }
    listMap.get(flower.getColor()).add(flower);
}
複製代碼

相信以上代碼是比較常見的,那麼當咱們學習了 Java 8以後有沒有什麼比較好的寫法呢:

Map<String,List<Flower>> map = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor));
複製代碼

一行代碼解決,Java 8 真的是秀啊!

函數式變成的一個主要優點就是,咱們只要告訴它 「作什麼」,而不用關心*「怎麼作」*。就像是上一個例子中,咱們須要的是按顏色分組,因此咱們只要跟收集器說 按照顏色分組就行collect(Collectors.groupingBy(Flower::getColor))。咱們上面也比較常常用到的是collect(Collectors.toList(),它的做用就是將咱們須要的結果收集成一個集合。

用來計算總數

Long c1 = flowerList.stream().collect(Collectors.counting());
//也能夠直接用 count() 方法來計數
Long c2 = flowerList.stream().count();
複製代碼

用來查找最大值和最小值

Optional<Flower> max = flowerList.stream().collect(Collectors.maxBy(Comparator.comparing(Flower::getPrice)));
Optional<Flower> min = flowerList.stream().collect(Collectors.minBy(Comparator.comparing(Flower::getPrice)));
複製代碼

用來求和

Integer sum = flowerList.stream().collect(Collectors.summingInt(Flower::getPrice));
複製代碼

用來求平均數

Double avg = flowerList.stream().collect(Collectors.averagingInt(Flower::getPrice));
複製代碼

用來鏈接字符串

String color = flowerList.stream().map(Flower::getColor).collect(Collectors.joining(", "));
複製代碼

6)分組的使用

現在有一組花的集合以下:

List<Flower> flowerList = Arrays.asList(new Flower("red", 10), new Flower("yellow", 7), new Flower("pink", 8), new Flower("yellow", 8), new Flower("red", 12));

/***** 結果 ***** {red=[Flower(color=red, price=10), Flower(color=red, price=12)], pink=[Flower(color=pink, price=8)], yellow=[Flower(color=yellow, price=7), Flower(color=yellow, price=8)]} **/
複製代碼

按照顏色分組Map<String,List<Flower>>

Map<String,List<Flower>> color = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor));
/***** 結果 ***** {red=[Flower(color=red, price=10), Flower(color=red, price=12)], pink=[Flower(color=pink, price=8)], yellow=[Flower(color=yellow, price=7), Flower(color=yellow, price=8)]} **/
複製代碼

統計每種顏色的數量Map<String, Long>

Map<String, Long> longMap = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor, Collectors.counting()));
/***** 結果 ***** {red=2, pink=1, yellow=2} **/
複製代碼

也能夠支持多級分組

先按顏色分組,再按價格分組Map<String, Map<String, List<Flower>>>

Map<String, Map<String, List<Flower>>> collect = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor, Collectors.groupingBy(t -> {
    if (t.getPrice() < 8) {
        return "LOW_PRICE";
    } else {
        return "HIGHT_PRICE";
    }
})));
/***** 結果 ***** {red={HIGHT_PRICE=[Flower(color=red, price=10), Flower(color=red, price=12)]}, pink={HIGHT_PRICE=[Flower(color=pink, price=8)]}, yellow={HIGHT_PRICE=[Flower(color=yellow, price=8)], LOW_PRICE=[Flower(color=yellow, price=7)]}} **/
複製代碼

先按顏色分組,再找每一個顏色中最貴的花Map<String, Flower>

Map<String, Flower> f = flowerList.stream().collect(Collectors.groupingBy(Flower::getColor, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Flower::getPrice)), Optional::get)));
/***** 結果 ***** {red=Flower(color=red, price=12), pink=Flower(color=pink, price=8), yellow=Flower(color=yellow, price=8)} **/
複製代碼

這個工廠方法接受兩個參數——要轉換的收集器以及轉換函數,並返回另外一個收集器。這個收集器至關於舊收集器的一個包裝, collect 操做的最後一步就是將返回值用轉換函數作一個映射。在這裏,被包起來的收集器就是用 maxBy 創建的那個,而轉換函數 Optional::get 則把返回的 Optional 中的值提取出來。

Collectors 的經常使用方法

方法 返回類型 用途
toList List 把流中全部項目都收集到一個List
toSet Set 把流中全部項目都收集到一個Set,刪除重複項
toCollection Collection 把流中全部項目收集到給定的供應源建立的集合
counting Long 計算流中元素的個數
summingInt Integer 對流中項目的一個整數屬性求和
averagingInt Double 計算流中項目Integer屬性的平均值
joining String 鏈接對流中每一個項目調用toString方法所生成的字符串
maxBy Optional 一個包裹了流中按照給定比較器選出最大元素的Optional,若是爲空則爲Optional.empty()
minBy Optional 一個包裹了流中按照給定比較器選出最小元素的Optional,若是爲空則爲Optional.empty()
reducing 歸約操做產生的類型 從一個做爲累加器的初始值開始,利用 BinaryOperator 與流中的元素組個結合,從而將流歸約成單個值
collectingAndThen 轉換函數返回的類型 包裹另外一個收集器,對其結果應用轉換函數
groupingBy Map<K, List> 根據項目的一個屬性的值對流中的項目做爲組,並將屬性值做爲結果Map的鍵

3、學會使用Optional

開發中最常常遇到的異常某過於NullPointException了吧。由於這就是咱們爲了方便甚至不可避免的像 null 引用這樣的構造所付出的代價。Java 8以後彷彿出現了起色,那就是用Optional來代替null

上面這段代碼乍看之下應該沒啥問題,平時開發的時候也頗有可能會不由自主的寫出相似這種的代碼。可是問題也就來了,真的是每一個人都有手機嗎,若是new Person().getPhone()獲取不到手機,那麼調用getType()是否是就會出現熟悉的NullPointException異常了。

1)防護式檢查

爲了不空指針異常,Java 8出現的Optional爲咱們很好的避免了。

經典預防方式

private String getPhoneType(Person person) {
    if (person != null) {
        Phone phone = person.getPhone();
        if (phone != null) {
            return phone.getType();
        }
    }
    return "";
}
複製代碼

每次引用都作一次判空操做,效果想必也不賴,也能夠避免空指針異常。當時每一次判空都得添加一個 if 判斷,真實讓人頭大。

Optional 預防

從圖中能夠看出 Optional至關因而一個容器,裏面能夠裝 T 類型的對象。當變量不存在的時候,缺失的值就會被建模成一個「空」的Optional對象,由方法Optional.empty()返回。這就是Optional.empty()null的區別,若是引用一個 null,那結果確定是會觸發NullPointException異常,可是引用Optional.empty()則沒事。

上述代碼可修改成:

private String getPhoneType(Person person) {
    return Optional.ofNullable(person).map(Person::getPhone).map(Phone::getType).orElse("");
}
複製代碼

一行代碼搞定,乾淨利落。

2)學會使用Option

建立Optional對象

建立一個空的Optional

Optional<Person> personOpt = Optional.empty()

建立一個非空的Optional

Optional<Person> personOpt = Optional.of(person)

Optional.of()不接受空值。若是 person 是一個空值則會拋出 NullPointException 異常,而不是等你試圖訪問 person 的屬性才拋出異常。

建立一個可接受空值的Optional

Optional<Person> personOpt = Optional.ofNullable(Person)

若是 person 是 null ,那麼獲得的 Optional 對象就是個空對象。

使用map

Optional 中的 map()方法和流中的map()類似,都是從Optional對象中提取和轉換值。

Optional<String> name = Optional.ofNullable(person).map(Person::getName);
複製代碼

獲取到的是一個Optional對象是爲了防止獲取到一個 null,咱們能夠經過Optional.get()來獲取值。

默認行爲

咱們可使用get()方法來獲取 Optional 的值,也可使用orElse()來定義一個默認值,遭遇到空的Optional值的時候,默認值會做爲該方法的調用返回值。如下是Optional的經常使用方法:

  • get()

最簡單但又是最不安全的方法,若是變量存在,則直接返回封裝的變量值,反之則拋出NullpointException異常。

  • orElse(T other)

容許本身定義一個默認值在Optional爲空的時候返回。

  • orElseGet(Supplier<? extend T> other)

orElse()方法的延遲調用版,在Optional對象不含值的時候執行調用。

  • orElseThrow(Supplier<? extend X> excetionSupplier)

get()方法相似,在Optional對象爲空的時候會拋出一個異常,可是這個異常咱們能夠自定義。

  • ifPresent(Consumer<? extend T>)

在Optional對象存在的執行的方法,反之不操做。也接受一個空參數的,若是

方法 描述
empty 返回一個空的Optional實例
filter 若是值存在而且知足提供的謂詞,就會返回包含該值的Optional對象;不然返回一個空的Optional對象
get 若是值存在,將該值用Optional封裝返回,不然拋出一個NullPointException異常
ifPresent 若是值存在,就執行使用該值的方法調用,不然什麼也不作
ifPresent 若是值存在就返回true,不然返回false
map 若是值存在,就對該值執行提供的 mapping 函數調用
of 將指定值用Optional封裝後返回,若是該值爲 null,則拋出一個 NullPointException異常
ofNullable 將指定值用 Optional 封裝以後返回,若是該值爲null,則返回一個空的 Optional 對象
orElse 若是有值則將其返回,不然返回一個默認值
orElseGet 若是有值則將其返回,不然返回一個由指定的 Supplier 接口生成的值
orElseThrow 若是有值則將其放回,不然拋出一個由指定的 Supplier 接口生成的異常

4、新的日期和時間

在 Java 8以前,咱們對日期和時間的支持智能依賴 java.util.Date類,這個類沒法表示日期,只能以毫秒的精度表示時間。並且它的表現方式也不是那麼直觀,在Java1.0的Date這個類中,年份的起始是 1900 年,月份的起始是 0 開始,若是咱們這個時候想要構造一個 2020年7月18號的日期,咱們就得這樣作:

Date date = new Date(120, 6, 18);
System.out.println(date);   // Sat Jul 18 00:00:00 CST 2020
複製代碼

這種的構造方式簡直是糟糕透了不是嗎,對於不瞭解Date 的來講太不友好了。在java1.1 後出現了Calender這個類,而Date中大部分方法都被廢棄了,可是Calender這個類中也有相似的問題和設計缺陷,並且兩個日期類的出現,咱們有時候也難以選擇使用哪個。

LocalDate

建立一個 LocalDate 對象

LocalDate nowDate = LocalDate.of(2020,7,18);    //2020-07-18
int year = nowDate.getYear();                   //2020
Month month = nowDate.getMonth();               //07
int day = nowDate.getDayOfMonth();              //18
DayOfWeek dayOfWeek = nowDate.getDayOfWeek();   //SATURDAY
int days = nowDate.lengthOfMonth();             //31
LocalDate nowdate = LocalDate.now();            //獲取當前時間>2020-07-18
複製代碼

也可使用 TemporalField 讀取 LocalDate 的值

LocalDate nowDate = LocalDate.of(2020,7,18);        //2020-07-18
int year = nowDate.get(ChronoField.YEAR);           //2020
int month = nowDate.get(ChronoField.MONTH_OF_YEAR); //07
int day = nowDate.get(ChronoField.DAY_OF_MONTH);    //18
複製代碼

LocalTime

建立一個 LocalTime 對象

LocalTime nowTime = LocalTime.of(19, 34, 32);  //19:34:32
int hour = nowTime.getHour();                  //19
int minute = nowTime.getMinute();              //34
int second = nowTime.getSecond();              //32
複製代碼

一樣也可使用 TemporalField 讀取 LocalTime 的值

LocalTime nowTime = LocalTime.of(19, 34, 32);           //19:34:32
int hour = nowTime.get(ChronoField.HOUR_OF_DAY);        //19
int minute = nowTime.get(ChronoField.MINUTE_OF_HOUR);   //34
int second = nowTime.get(ChronoField.SECOND_OF_MINUTE); //32
複製代碼

LocalDateTime

LocalDateTime 至關於合併了日期和時間,如下是建立的幾種方式:

LocalDate nowDate = LocalDate.of(2020,7,18);    //2020-07-18
LocalTime nowTime = LocalTime.of(19, 45, 20);   //19:34:32
LocalDateTime dt1 = LocalDateTime.of(2020, Month.JULY, 18, 19, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(nowDate, nowTime);
LocalDateTime dt3 = nowDate.atTime(19, 45, 20);
LocalDateTime dt4 = nowDate.atTime(nowTime);
LocalDateTime dt5 = nowTime.atDate(nowDate);

LocalDate date1 = dt1.toLocalDate();		//2020-07-18
LocalTime time1 = dt1.toLocalTime();		//19:45:20
複製代碼

時間點的日期 時間類的通用方法:

方法名 是否靜態方法 描述
from 依據傳入的 Temporal 對象建立對象實例
now 依據系統時鐘建立 Temporal 對象
of 由 Temporal 對象的某個部分建立該對象的實例
parse 由字符串建立 Temporal 對象的實例
atOffset 將 Temporal 對象和某個時區偏移相結合
atZone 將 Temporal 對象和某個時區相結合
format 使用某個指定的格式器將 Temporal 對象轉換爲字符串( Instant 類不提供該方法)
get 讀取 Temporal 對象的某一部分的值
minus 建立 Temporal 對象的一個副本,經過將當前 Temporal 對象的值減去必定的時長建立該副本
plus 建立 Temporal 對象的一個副本,經過將當前 Temporal 對象的值加上必定的時長建立該副本
with 以該 Temporal 對象爲模板,對某些狀態進行修改建立該對象的副本

Duration和Period

這兩個類是用來表示兩個時間內的間隔的

Duration d1 = Duration.between(time1, time2);
Duration d1 = Duration.between(dateTime1, dateTime2);
Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);

Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
複製代碼

日期 - 時間類中表示時間間隔的通用方法:

方法名 是否靜態方法 方法描述
between 建立兩個時間點之間的 interval
from 由一個臨時時間點建立 interval
of 由它的組成部分建立 interval 的實例
parse 由字符串建立 interval 的實例
addTo 建立該 interval 的副本,並將其疊加到某個指定的 temporal 對象
get 讀取該 interval 的狀態
isNegative 檢查該 interval 是否爲負值,不包含零
isZero 檢查該 interval 的時長是否爲零
minus 經過減去必定的時間穿件該 interval 的副本
multipliedBy 將 interval 的值乘以某個標量建立該 interval 的副本
negated 以忽略某個時長的方式建立該 interval 的副本
plus 以增長某個指定的時長的方式建立該 interval 的副本
subtractFrom 從指定的temporal對象中減去該 interval

看完不讚,都是壞蛋

今天的你多努力一點,明天的你就能少說一句求人的話!

我是小菜,一個和你一塊兒學習的男人。 💋

微信公衆號已開啓,【小菜良記】,沒關注的同窗們記得關注哦!

相關文章
相關標籤/搜索