這篇 JDK 8,講的真牛逼

fans news 發佈 2022-01-05T14:08:26+00:00

一、基礎知識1)為什麼要學習 Java8Java 8 所做的改變,在許多方面比Java 歷史上任何一次改變都更加深遠,這些改變會讓你的編程更加容易例子:傳統寫法:List<Person> personList = Arrays.

一、基礎知識

1)為什麼要學習 Java8

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

例子:

傳統寫法

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 操作的同學對這個指令應該不默認

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

兩者的區別就是操作的順序不一樣

二、函數式數據處理

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());//終端操作

中間操作:

操作返回類型操作參數filterStreamPredicatemapStreamFuncation<T, R>limitStream
sortedStreamComparatordistinctStream
skipStreamlonglimitStreamlongflatMapStreamFuncation<T, Steam>

終端操作

操作返回類型操作參數forEachvoidConsumercountlong
collectRCollector<T, A, R>anyMatchbooleanPredicatenoneMatchbooleanPredicateallMatchbooleanPredicatefindAnyOptional
findFirstOptional
reduceOptionalBinaryOperator

(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());

「篩選跳躍」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方法就是讓你把一個流中的每個值都轉成另一個流,然後把所有的流連接起來成為一個流。

(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) 找到交易額最小的交易

答案:

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 的常用方法

方法返回類型用途toListList把流中所有項目都收集到一個ListtoSetSet把流中所有項目都收集到一個Set,刪除重複項toCollectionCollection把流中所有項目收集到給定的供應源創建的集合countingLong計算流中元素的個數summingIntInteger對流中項目的一個整數屬性求和averagingIntDouble計算流中項目Integer屬性的平均值joiningString連接對流中每個項目調用toString方法所生成的字符串maxByOptional一個包裹了流中按照給定比較器選出最大元素的Optional,如果為空則為Optional.empty()minByOptional一個包裹了流中按照給定比較器選出最小元素的Optional,如果為空則為Optional.empty()reducing歸約操作產生的類型從一個作為累加器的初始值開始,利用 BinaryOperator 與流中的元素組個結合,從而將流歸約成單個值collectingAndThen轉換函數返回的類型包裹另一個收集器,對其結果應用轉換函數groupingByMap<K, List>根據項目的一個屬性的值對流中的項目作為組,並將屬性值作為結果Map的鍵

三、學會使用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,否則返回falsemap如果值存在,就對該值執行提供的 mapping 函數調用of將指定值用Optional封裝後返回,如果該值為 null,則拋出一個 NullPointException異常ofNullable將指定值用 Optional 封裝之後返回,如果該值為null,則返回一個空的 Optional 對象orElse如果有值則將其返回,否則返回一個默認值orElseGet如果有值則將其返回,否則返回一個由指定的 Supplier 接口生成的值orElseThrow如果有值則將其放回,否則拋出一個由指定的 Supplier 接口生成的異常

四、新的日期和時間

在 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是創建兩個時間點之間的 intervalfrom是由一個臨時時間點創建 intervalof是由它的組成部分創建 interval 的實例parse是由字符串創建 interval 的實例addTo否創建該 interval 的副本,並將其疊加到某個指定的 temporal 對象get否讀取該 interval 的狀態isNegative否檢查該 interval 是否為負值,不包含零isZero否檢查該 interval 的時長是否為零minus否通過減去一定的時間穿件該 interval 的副本multipliedBy否將 interval 的值乘以某個標量創建該 interval 的副本negated否以忽略某個時長的方式創建該 interval 的副本plus否以增加某個指定的時長的方式創建該 interval 的副本subtractFrom否從指定的temporal對象中減去該 interval

原文連結:https://mp.weixin.qq.com/s?__biz=MzkwMDE1MzkwNQ==&mid=2247500901&idx=1&sn=2aa004a593951c616e28327b4e8e38d8&chksm=c04ad33bf73d5a2d7b6013f1dcc7ce750cf6db4553567487b4ee6834fd6b22c0b5a597cb3cf4&token=88290978&lang=zh_CN#rd

作者:小菜良記

關鍵字: