Java8流特性和Lambda表達式

Java8主要的改變是爲集合框架增長了流的概念,提升了集合的抽象層次。相比於舊有框架直接操做數據的內部處理方式,流+高階函數的外部處理方式對數據封裝更好。同時流的概念使得對併發編程支持更強。java

在語法上Java8提供了Lambda表達式來傳遞方法體,簡化了以前方法必須藏身在沒必要要的類中的繁瑣。Lambda表達式體現了函數式編程的思想,即一個函數亦能夠做爲另外一個函數參數和返回值,使用了函數做參數/返回值的函數被稱爲高階函數。python

1. Lambda表達式

Java 被詬病爲繁瑣的地方就在於不支持傳遞方法,Java中的方法必須依賴類存在,也不能將方法做爲參數或返回值,這是與python等語言相比的弱勢。Java 8中使用新特性Lambda表達式來改善這一點。編程

1.1 使用示例

以Runnable接口爲例,若是須要執行一個線程,實際只須要run()方法中的代碼塊,但形式上必需要先製造一個Runnable接口實現類(一般是匿名內部類)。數組

使用Lambda表達式僅僅須要一行代碼,達到傳遞run方法的效果,而沒必要定義匿名內部類。數據結構

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

1.2 類型參數推斷機制(Type Argument Inference)

Lambda表達式之因此可以作如此簡化得益於Java的類型參數推斷機制。全部省略的內容均可以由編譯器經過上下文推斷出來。併發

類型推斷機制在Java中的應用普遍,例如數組類型肯定,Java7引入的菱形操做符等等。app

類型參數推斷機制要推斷的是Lambda表達式的目標類型,每每須要與Java的重載解析機制配合。其解析規則是框架

  • 只有一個可能目標類型時,由響應函數接口裏的參數類型推導得出ide

  • 有多個可能目標類型,選擇最具體的類型函數式編程

  • 有多個可能目標類型但沒法明確最具體類型,則編譯報錯

1.3 函數接口(Functional Interface)

一個方法能夠抽象成函數接口。函數接口相似於一個黑箱,只須要關注其參數和返回值類型,函數接口中只有單方法。Runnable的函數接口以下:
圖片描述

能夠看到這是一個空接口。能夠用它表明全部參數和返回值都爲空的方法。

Java8中定義若干函數接口(位於包java.util.function)。

接口 參數 返回類型 示例
Pridicate<T> T boolean 條件判斷
Consumer<T> T void 消費者
Function<T, R> T R 再加工
Supplier<T> void T 供應者
BinaryOperator<T> <T,T> T 求和
UnaryOperator<T> T T 邏輯非

以Pridicate函數接口爲例,這是一個泛型接口,參數能夠是任意類型,返回值是boolean類型,表明根據數值做判斷的一類方法。

1.4 並不是語法糖

從類型推斷的角度看很容易以爲Lambda表達式是和泛型,裝箱等機制同樣的語法糖,編譯器在背後補全了省略信息,但實際上並不是如此。

class Apple{

    public String toString() {return "apple";};
    
    Runnable r1 = ()->{System.out.println(this);};
    
    Runnable r2 = new Runnable() {
        public void run() {
            System.out.println(this);
        }
    };
}
--------------------
//執行兩個線程獲得的結果是
apple
Day0917.Apple$1@22e90474

正常的匿名內部類中 this關鍵字 指向內部類對象自身,同時將生成Apple$1.class文件。

Lambda表達式中this所指向的則是外部類對象,並不會生成內部類class文件,這說明Lambda表達式並非語法糖,它沒有產生一個內部類,也沒有引入一個新的做用域。

Lambda與內部類相同之處在於其內部所定義的變量均爲final或既成事實上的final.

1.5 默認方法

Java8最重要的改變就是對類庫的改造,使得接口中方法能夠擁有代碼體。這種定義在接口中的包含方法體的方法,須要用default修飾,稱之爲默認方法。

interface Apple{

    default void show(){
        System.out.println("interface");
    }
}

class MyApple implements Apple{

    @Override
    public void show() {
        Apple.super.show();
    }
}

若是實現類中重寫了默認方法,則接口中默認方法就被覆蓋了。
若是兩個接口定義了相同的默認方法,則實現類中能夠經過指定全稱來肯定使用哪一個父類的方法。

1.6 方法引用

若是將匿名內部類改造爲Lambda表達式是偷懶的話,那方法引用則是懶到連Lambda表達式都不想寫了。

在以前,咱們知道Lambda表達式能夠做爲函數參數和返回值,表示傳遞一個方法。方法引用就是使用 ClassName::MethodName 的形式來指定方法。故而方法引用與Lambda表達式徹底同源同種,能夠相互替代。

//1,創建一個字符串
String::new 
//2.創建一個字符串數組
String[]::new

注意 lambda表達式與方法引用表示的是方法自己,將要被用太高階函數的參數/返回值,並不能單獨使用。

2. 流stream

任務:建立一個姓名集合,要求出全部初始字母爲a的人的總數目。使用流處理的代碼以下:

ArrayList<String> person = new ArrayList<>();
----init----
//1.由集合得到流對象
Stream<String> steam = person.stream();
//2.對流對象進行過濾和統計
steam.filter((s)->s.startsWith("a")) //1.流過濾
        .count(); //2.計算流對象中元素數目

使用函數接口(形式上表現爲Lambda表達式)做爲參數和返回值的函數就是所謂的高階函數,如此處的filter,其參數爲函數接口Predicate,亦能夠理解爲一個接口爲 T--->boolean 的方法。

上述示例中爲流對象的高階函數傳入一個函數接口Predicate,避免了直接處理集合中的數據對象。示例展現了流使用的通用格式:

  • 得到流對象Stream

  • 對流對象Stream進行惰性求值,返回值仍然是一個Stream對象。

  • 對流對象Stream進行及早求值,返回值不在是一個Stream對象。

2.1常見高階函數

1.collect方法

collect方法屬於一個及早求值方法,負責將流對象轉換成其餘數據結構,如列表,集合,值等。這項工做由收集器Collector完成。java8爲此提供了Collectors工具類。

1.1 轉換成集合

List<Person> list = stream.collect(Collectors.toList());
List<Person> arraylist = stream.collect(Collectors.toCollection(ArrayList::new));
        
Set<Person> set = stream.collect(Collectors.toSet());
Set<Person> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));

使用Collectors.toList()將流對象轉換成集合時並不須要指定具體類型,Java默認選擇了實現類型,若是要本身指定,可使用Collectors.toCollection(ArrayList::new),其參數ArrayList::new就是上文中的方法引用,表示一個創建ArrayList對象的方法,ArrayList就是想要轉換成的數據類型;

1.2 轉換成值

//1.得到最大最小值
Function<Person, Integer> getLevel = p->p.age; 
Comparator<Person> comparator = Comparator.comparing(getLevel);
stream.collect(Collectors.maxBy(comparator));
stream.collect(Collectors.minBy(comparator));
//2.得到平均值
ToIntFunction<Person> getAverage = p->p.age;
stream.collect(Collectors.averagingInt(getAverage));

1.3 數據分塊

將流對象按某種條件分紅兩部分

Predicate<Person> isTang = p->p.country.equals(Country.Tang);
stream.collect(Collectors.partitioningBy(isTang));

1.4 數據分組

Function<Person, Integer> country= p -> p.country.ordinal();
stream.collect(Collectors.groupingBy(country));

分塊和分組看似相同,但意義不一樣,分塊使用判斷做爲方法,只能將流分紅兩塊;分組則靈活的多。

1.5 字符串

stream.map(Person::getName).collect(Collectors.joining("/", "[", "]"));

1.6 合併收集器

stream.collect(Collectors.groupingBy(country,Collectors.counting()));

2.map

map是一個惰性求值方法。函數接口爲Function<T, R>函數接口,負責將數據從一個類型轉換爲另外一個類型;高階函數map的做用就是將數據從一個流轉換爲另外一個流。

3.filter

filter 是一個惰性求值方法。函數接口爲Pridicate<T>,此方法負責對數據進行判斷,filter高階函數負責根據判斷結果對流進行過濾。

4.flatMap系列

flatMap 是一個惰性求值方法。其參數亦爲Function<T, R>,將多個流組合爲一個流。

//1.a1,a2是兩個列表,map處理後還是兩個列表
Stream.of(a1,a2).map(s->s)
-------------
[1, 2, 3, 4]
[]

//2.flatMap將兩者合併爲一個流
Stream.of(a1,a2).map(s->s)
.flatMap(s->s.stream())
-------------
1234

看源碼可知,flatMap中函數接口Function的輸出類型爲Stream<R>

5.max/min

屬於一個及早求值方法。須要傳入一個Comparator函數接口,Java8提供了Comparator.comparing方法得到該函數接口的實現,該靜態方法是接口的靜態方法,得到一個函數返回一個Comparator對象。

min(Comparator.comparing(s->s.toString()));

max/min的返回值是 Optional,表明一個或有或無的值,主要是用來取代萬惡的null值;使用get方法能夠獲取其值。

6.reduce

屬於一個及早求值方法。意爲流數據的累加,有兩個版本。

//1.無初始值累加
T t = person.stream().reduce((a,b)->a+b);
//2.帶初始值累加
Optional<T> t = person.stream().reduce("1",(a,b)->a+b);

7. foreach

屬於一個及早求值方法,用來遍歷流對象。

總而言之,Java8中流對象的引入使得能夠在更高的層次上對集合進行處理,使得抽象的方法和具體的行爲邏輯分離開來,也增強了數據的封裝性,另外一個好處是對併發的支持更強,之後再補充。

相關文章
相關標籤/搜索