java8(一)不斷進化的java 一、java8有哪些變化 1.1 流處理

當前軟件行業的氣候正在不斷地變化。數據量的爆炸式增長,導致程序員在開發時不得不去面對存在的各種效率問題,並希望利用多核計算機或計算集羣來有效地處理。這意味着需要使用並行處理,而曾經的java對於並行的支持並不好,這使得其不得不進行進化,以適應當前的行業氣候。

Java 8中開發出並行和編寫更簡潔通用代碼的功能,下面我們一起學習。

一、java8有哪些變化

1.1 流處理

Java 8在 java.util.stream 中添加了一個Stream API。通過這個流處理有兩個較爲直觀的好處:

1)把這樣的流變成那樣的流。

什麼意思?簡單舉個小例子,在Stream提供了很多方法,此處以mapToInt舉例:

IntStream mapToInt(ToIntFunction<? super T> mapper);

用法:

    public static void main(String[] args) {
        // 有如下的字符串列表
        List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
        // 打印出每個字符串的長度
        list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
        // 經過Lambda替換方法後,儘量的簡化代碼
        list.stream().mapToInt(String::length).forEach(System.out::println);
    }

結果:

4
3
3
6
6

在上面的代碼我們看到只通過一行代碼就可以完成操作,最大化的簡化了代碼。比傳統的for循環更容易查看。

2)集合處理
幾乎每個Java應用都會製造和處理集合。但集合用起來並不總是那麼理想。比方說,你需要從一個列表中篩選金額較高的交易,然後按貨幣分組。你需要寫一大堆套路化的代碼來實現這個數據處理命令。

通過Stream API 可以簡單的完成上面的步驟,這裏舉個例子:

    Map<Currency, List<Transaction>> transactionsByCurrencies =
            transactions.stream()
                    .filter((Transaction t) -> t.getPrice() > 1000)//篩選金額較高的
                    .collect(groupingBy(Transaction::getCurrency));//按照金額分組

相比於使用集合Collection,使用Stream API我們能獲得兩點直觀的優點:
1)不需要自己去通過Collection API去迭代處理複雜的過程(外部迭代),使用流式處理的內部迭代,不需要操心循環的事情。
2)如果數據量非常龐大,那麼使用集合的方式,處理將會非常慢;引入多線程將會是原本就複雜的集合迭代變得更加困難,使用Steam API可以較好的解決這一問題,看3)中的介紹。

3)在不同cpu上執行Stream操作,不需要Thread
Stream提供了並行操作,通過下面的例子簡單看下:

    public static void main(String[] args) {
        // 有如下的字符串列表
        List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
        // 打印出每個字符串的長度
        //list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
        // 經過Lambda替換方法後,儘量的簡化代碼
        long startTime = System.currentTimeMillis();
        list.stream().mapToInt(String::length).forEach(l -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(l);
        });
        System.out.println("耗時:" + (System.currentTimeMillis() - startTime));
    }

結果:

4
3
3
6
6
耗時:5104

這時我們添加上並行操作parallel,代碼如下:

    public static void main(String[] args) {
        // 有如下的字符串列表
        List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
        // 打印出每個字符串的長度
        //list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
        // 經過Lambda替換方法後,儘量的簡化代碼
        long startTime = System.currentTimeMillis();
        list.stream().mapToInt(String::length).parallel().forEach(l -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(l);
        });
        System.out.println("耗時:" + (System.currentTimeMillis() - startTime));
    }

結果:

3
6
4
3
6
耗時:1096

總體耗時基本是五分之一,或者可以使用parallelStream():

list.parallelStream().mapToInt(String::length).forEach....

關於具體的內容後面會詳細講解。

1.2 用行爲參數化把代碼(方法)傳遞給方法

Java 8增加了把方法(你的代碼)作爲參數傳遞給另一個方法的能力。我們把這一概念稱爲行爲參數化

下面會簡單講解時什麼意思。

1.2.1 函數

編程語言中的函數一詞通常是指方法,尤其是靜態方法。

Java 8中新增了函數:值的一種新形式

編程語言的本質就是操作值。按照傳統的編程語言歷史,我們以java舉例:例如int,long,double這些可以直接被作爲參數傳遞的值,都是一等值(一等公民);而還有很多涉及到結構的內容,例如方法和類等,不能作爲參數傳遞,這些被稱爲二等公民。

java8中增加了新的功能,將二等公民添加到運行時傳遞,則二等公民就成爲了一等公民。

1.2.1.1 方法和 Lambda 作爲一等公民

Java 8的設計者決定允許方法作爲值,讓編程更輕鬆。此外,讓方法作爲值也構成了其他若干Java 8功能(如 Stream )的基礎。

我們介紹的Java 8的第一個新功能是方法引用

舉個例子,你想要篩選一個目錄中的所有隱藏文件。 File類裏面有一個叫作 isHidden 的方法。我們可以把它看作一個函數,接受一個 File ,返回一個布爾值。但要用它做篩選,你需要把它包在一個 FileFilter 對象裏,然後傳遞給 File.listFiles方法,如下所示:

    public void test(){
        File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return file.isHidden();
            }
        });
    }

在java8當中我們可以使用如下的方式:

    public void test(){
        File[] hiddenFiles = new File(".").listFiles(File::isHidden);
    }

你已經有了函數 isHidden ,因此只需用Java 8的方法引用 :: 語法(即“把這個方法作爲值”)將其傳給 listFiles 方法,我們也開始用函數代表方法了。

當使用File::isHidden時,其實創建了一個方法引用,與對象引用類似,我們就可以將其作爲一等公民傳遞。

除了允許(命名)函數成爲一等值外,Java 8還體現了更廣義的將函數作爲值的思想,包括Lambda(匿名函數)。直白說,將匿名函數作爲一等值

將匿名函數傳遞有什麼好處呢?寫代碼的時候相信都會遇到一種情況,當你只需要一個簡單的運算的時候,還需要去重新定義一個類或者方法嗎?如果沒有方便的類或者方法可用的話,lambda就能幫助你使語法更簡潔。

1.2.1.2 使用案例

假設有一個手機倉庫,裏面放了很多品牌的手機,包括蘋果、華爲等。現在我們需要篩選出華爲手機有多少。那麼需要些如下的代碼:

    public List<Phone> brandFilter(){
        List<Phone> phones = new ArrayList<>();
        List<Phone> results = new ArrayList<>();
        for (Phone phone : phones) {
            if ("華爲".equals(phone.getBrand())){
                results.add(phone);
            }
        }
        return results;
    }

如果又需要篩選黑色的手機,那麼還需要寫下面的方法:

    public List<Phone> colorFilter(){
        List<Phone> phones = new ArrayList<>();
        List<Phone> results = new ArrayList<>();
        for (Phone phone : phones) {
            if ("黑色".equals(phone.getColor())){
                results.add(phone);
            }
        }
        return results;
    }

這樣一來重複代碼就會很多了,如果我們使用java8,可以像下面這樣寫:

package com.cloud.bssp.java8.stream;

import lombok.Data;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

/**
 * @description: 挑選手機
 * @author:weirx
 * @date:2021/10/15 16:02
 * @version:3.0
 */
public class ChoicePhone {

    public static void main(String[] args) {
        List<Phone> list = new ArrayList<>();
        list.add(new Phone("蘋果", "黑色"));
        list.add(new Phone("華爲", "黑色"));
        List<Phone> blackPhones = phoneFilter(list, ChoicePhone::isBlack);
        System.out.println(blackPhones.toString());
        List<Phone> huaweiPhones = phoneFilter(list, ChoicePhone::isHuawei);
        System.out.println(huaweiPhones.toString());
    }

    @Data
    static class Phone {
        private String brand;

        public Phone(String brand, String color) {
            this.brand = brand;
            this.color = color;
        }

        private String color;
    }

    /**
     * 篩選華爲方法
     */
    public static boolean isHuawei(Phone phone) {
        return "華爲".equals(phone.getBrand());
    }

    /**
     * 篩選黑色方法
     */
    public static boolean isBlack(Phone phone) {
        return "黑色".equals(phone.getColor());
    }

    /**
     * 手機過濾方法,參數是手機list和Predicate<T>函數
     */
    public static List<Phone> phoneFilter(List<Phone> phones, Predicate<Phone> p) {
        List<Phone> results = new ArrayList<>();
        for (Phone phone : phones) {
            if (p.test(phone)) {
                results.add(phone);
            }
        }
        return results;
    }
}

前面的代碼傳遞了方法 Apple::isGreenApple (它接受參數 Apple 並返回一個boolean )給 filterApples ,後者則希望接受一個 Predicate<Apple> 參數。

Java 8也會允許你寫 Function<Apple,Boolean>。

關於上面的代碼請同學們細細體會啊,一遍看不懂就多看幾遍。

前面就簡單介紹了lambda的作用,像我們前面的例子中,只是一個判斷的方法實在沒有必要單獨提供一個方法去維護,所以我們可以使用lambda的方式進行優化,就不需要單獨定義判斷方法isBlack和isHuawei了,使代碼更簡潔,如下所示:

List<Phone> blackPhones = phoneFilter(list, (Phone p) -> p.getColor().equals("黑色"));
List<Phone> huaweiPhones = phoneFilter(list, (Phone p) -> p.getBrand().equals("華爲"));

List<Phone> blackPhones = phoneFilter(list, (Phone p) -> 
  p.getColor().equals("黑色") || p.getBrand().equals("華爲")
);

1.3 默認方法

在java8之前的版本當中,接口被一個類實現,必須要求這個類實現該接口的全部方法,如果一個接口增加一個方法,那麼所有的實現類都需要增加這個方法的實現。那麼如何解決這個問題呢?

既然不想讓實現類自己實現這個方法,那麼只能由接口自己來實現了。這就給開發者提供了一個擴充接口的方式,而不會破壞現有的代碼。在java8中使用關鍵字default來表示這個關鍵點。

比如在java8當中,我們可以直接調用List接口中的sort方法:

    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

這意味着 List 的任何實體類都不需要顯式實現 sort。


本篇主要針對java的變化有個簡單的認識,後續文章會逐漸深入到細節。

如果對您有幫助,給點個贊吧,感謝!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章