深度分析:java8的新特性lambda和stream流,看完你學會了嗎?

1. lambda表達式

1.1 什麼是lambda

以java爲例,能夠對一個java變量賦一個值,好比int a = 1,而對於一個方法,一塊代碼也是賦予給一個變量的,對於這塊代碼,或者說被賦給變量的函數,就是一個lambda表達式java

//爲變量賦值
int a = 1;

//將代碼塊賦值給變量
var = public void fun(int x){
    x+1;
}

//能夠簡化
var = (x)->x+1;

1.2 java爲何要引入lambda

lambda是爲函數式編程服務的
編程語言共性之------什麼是函數式編程?
函數式編程是一種編程範式,也就是如何編寫程序的方法論,主要思想是把運算過程儘可能編寫成一系列嵌套的函數調用,FP強調「everything is lambda",而且強調在邏輯處理中不變性的重要性編程

OOP強調「everything is object」,以及object之間的消息傳遞。經過消息傳遞改變每一個Object的內部狀態,可是不少狀況代碼的編寫其實是用不到對象的,好比,對一組數據作加工,先查詢,而後聚合,聚合後排序,再join,再排序,再聚合,再轉換(map)獲得最終的結果。這個過程,用FP的函數就很天然api

result = func1(func2(func3...funcN(x))))

java爲了在原先oop的思想上增長函數式編程的使用,在java8上增長了lambda函數的新特性數組

除此以外,lambda表達式的引入還使得代碼更爲簡潔,能夠避免生成過多的污染環境的無用實現類(下面說)數據結構

1.3 如何使用lambda表達式

lambda表達式的引入能夠避免生成過多的污染環境的實現類;
lambda表達式能夠被賦值給一個變量,那麼這個變量的類型是什麼?
在java中,全部的Lambda的類型都是一個接口,而Lambda表達式自己,須要是這個接口的實現,這個接口須要具有三個特徵,具有這些特徵的接口叫作函數式接口編程語言

函數式接口只有一個抽象方法
default方法爲默認實現,不計入抽象方法
若是接口聲明瞭一個覆蓋java.lang.Object的全局方法之一的抽象方法,那麼它不會計入接口的抽象方法數量中,由於接口的任何實現都將具備java.lang.Object或其餘地方的實現
如何使用lambda表達式
好比Comparator接口就是一個函數式接口,因此他可使用lambda表達式,在以前使用comparator對一個list排序是下面這樣的ide

List<Integer> list = new ArrayList<>();
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1-o2;
    }
});

能夠看到上面實際真正有用的是return o1 - o2,上面的代碼使用lambda表達式寫以下函數式編程

Collections.sort(list, ((o1, o2) -> o1-o2));

Lambda 表達式的基礎語法:Lambda 操做符->將 Lambda 表達式拆分紅兩部分:
左側:Lambda 表達式的參數列表;
右側:Lambda 表達式中所需執行的功能, 即 Lambda 體;函數

語法格式一:無參數,無返回值
() -> System.out.println("Hello Lambda!");

語法格式二:有一個參數,而且無返回值
(x) -> System.out.println(x)

語法格式三:若只有一個參數,小括號能夠省略不寫
x -> System.out.println(x)

語法格式四:有兩個以上的參數,有返回值,而且 Lambda 體中有多條語句
Comparator<Integer> com = (x, y) -> {
System.out.println("函數式接口");
return Integer.compare(x, y);
};

語法格式五:若 Lambda 體中只有一條語句, return 和 大括號均可以省略不寫
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

1.4 lambda表達式方法引用,構造器引用和數組引用

方法引用
若 Lambda 體中的功能,已經有方法提供了實現,可使用方法引用oop

對象的引用 :: 實例方法名
類名 :: 靜態方法名
類名 :: 實例方法名

①方法引用所引用的方法的參數列表與返回值類型,須要與函數式接口中抽象方法的參數列表和返回值類型保持一致!
②若Lambda 的參數列表的第一個參數,是實例方法的調用者,第二個參數(或無參)是實例方法的參數時,格式: ClassName::MethodName

//對象的引用 :: 實例方法名
@Test
public void test1(){
        // 以前咱們是這樣寫的
    Employee emp = new Employee(101, "張三", 18, 9999);
    Supplier<String> sup = () -> emp.getName();
    System.out.println(sup.get());  

    System.out.println("----------------------------------");   
        // 如今咱們是這樣寫的
    Supplier<String> sup2 = emp::getName;
    System.out.println(sup2.get());
}

//類名 :: 靜態方法名
@Test
public void test2(){
    Comparator<Integer> com = (x, y) -> Integer.compare(x, y);  
    System.out.println("-------------------------------------");     
    Comparator<Integer> com2 = Integer::compare;
}

//類名 :: 實例方法名
@Test
public void test3(){
    BiPredicate<String, String> bp = (x, y) -> x.equals(y);
    System.out.println(bp.test("abcde", "abcde"));

    System.out.println("-----------------------------------------");

    BiPredicate<String, String> bp2 = String::equals;
    System.out.println(bp2.test("abc", "abc"));

}

構造器引用

對於person類,有兩個構造器

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

如今有一個工廠接口用來生成person類

// Person 工廠
interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

咱們能夠經過 :: 關鍵字來引用 Person 類的構造器,來代替手動去實現這個工廠接口:

// 直接引用 Person 構造器
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

Person::new 這段代碼,可以直接引用 Person 類的構造器。而後 Java 編譯器可以根據上下文選中正確的構造器去實現 PersonFactory.create 方法

2.1 什麼是Stream

Java 8引入了全新的Stream API,這裏的Stream和I/O流不一樣,Java 8 中的 Stream 是對集合(Collection)對象功能的加強,它專一於對集合對象進行各類很是便利、高效的聚合操做,或者大批量數據操做,Stream API 藉助於一樣新出現的 Lambda 表達式,極大的提升編程效率和程序可讀性

Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次後即用盡了,就比如流水從面前流過,一去不復返

List<String> myList =
    Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
    .stream() // 建立流
    .filter(s -> s.startsWith("c")) // 執行過濾,過濾出以 c 爲前綴的字符串
    .map(String::toUpperCase) // 轉換成大寫
    .sorted() // 排序
    .forEach(System.out::println); // for 循環打印

①:中間操做會再次返回一個流,因此,咱們能夠連接多箇中間操做,注意這裏是不用加分號的。上圖中的filter 過濾,map 對象轉換,sorted 排序,就屬於中間操做。
②:終端操做是對流操做的一個結束動做,通常返回 void 或者一個非流的結果。上圖中的 forEach循環 就是一個終止操做

上面是Stream的簡單實用,能夠看出它也是函數式編程,更多的表達了業務邏輯

2.2 經常使用api

建立Stream

1. Arrays.stream()

當在平常編程中面對的是一個數組,可使用Arrays.stream()方法來使用Stream

Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Arrays.stream(array).filter(i->i>20).count();

2. Stream.of()

當面對數組時除了可使用Arrays.stream()方法外,還可使用Stream將須要的數組轉成Stream。這個方法不但支持傳入數組,將數組轉成Stream,也支持傳入多個參數,將參數最終轉成Stream

Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Stream.of(array).filter(i->i>20).count();
long sum = Stream.of(12,77,59,3,654).filter(i->i>20).mapToInt(Integer::intValue).sum();
System.out.println("count:"+count+",sum:"+sum);

3. Collection.stream()

這個就是最多見的Stream了。由於Collection是Java中集合接口的父接口,Java中的集合都繼承或實現了此接口。因此Java中的集合均可以使用此方法來建立一個Stream

List<Integer> numbers = new ArrayList<>();
numbers.add(3);
numbers.add(4);
numbers.add(8);
numbers.add(16);   
numbers.stream().forEach(number->{
    System.out.println(number);
});

4.filter

這是一個Stream的過濾轉換,此方法會生成一個新的流,其中包含符合某個特定條件的全部元素,filter接受一個函數做爲參數,該函數用Lambda表達式表示

List<Integer> integerList = Lists.newArrayList();
integerList.add(15);
integerList.add(32);
integerList.add(5);
integerList.add(232);
integerList.add(56);
List<Integer> after = integerList.stream()
                    .filter(i->i>50)
                    .collect(Collectors.toList());
System.out.println(after);//232,56

5.map

map方法指對一個流中的值進行某種形式的轉換。須要傳遞給它一個轉換的函數做爲參數

List<Integer> integerList = Lists.newArrayList();
integerList.add(15);
integerList.add(32);
integerList.add(5);
integerList.add(232);
integerList.add(56);
//將Integer類型轉換成String類型
List<String> afterString = integerList.stream()
                .map(i->String.valueOf(i)).collect(Collectors.toList());

6.flatMap

將多個Stream鏈接成一個Stream,這時候不是用新值取代Stream的值,與map有所區別,這是從新生成一個Stream對象取而代之

List<String> words = new ArrayList<String>();
words.add("your");
words.add("name");

public static Stream<Character> characterStream(String s){  
    List<Character> result = new ArrayList<>();  
    for (char c : s.toCharArray()) 
        result.add(c);
    return result.stream();  
}

Stream<Stream<Character>> result = words.map(w -> characterStream(w));  
//[['y', 'o', 'u', 'r'], ['n', 'a', 'm', 'e']]
Stream<Character> letters = words.flatMap(w -> characterStream(w));
//['y', 'o', 'u', 'r', 'n', 'a', 'm', 'e']

7.limit方法和skip方法

limit(n)方法會返回一個包含n個元素的新的流(若總長小於n則返回原始流)
skip(n)方法正好相反,它會丟棄掉前面的n個元素
用limit和skip方法一塊兒使用就能夠實現平常的分頁功能:

List<Integer> pageList = myList.stream()
                  .skip(pageNumber*pageSize)
                  .limit(pageSize).collect(Collectors.toList());

8.distinct方法和sorted方法

distinct方法會根據原始流中的元素返回一個具備相同順序、去除了重複元素的流,這個操做顯然是須要記住以前讀取的元素。

List<Integer> myTestList = Lists.newArrayList();
myTestList.add(10);
myTestList.add(39);
myTestList.add(10);
myTestList.add(78);
myTestList.add(10);
List<Integer> distinctList = myTestList.stream()
                        .distinct().collect(Collectors.toList());
System.out.println("distinctList:"+distinctList);
運行結果:
distinctList:[10, 39, 78]

sorted方法是須要遍歷整個流的,並在產生任何元素以前對它進行排序。由於有可能排序後集合的第一個元素會在未排序集合的最後一位。

List<Integer> myTestList = Lists.newArrayList();
myTestList.add(39);
myTestList.add(78);
myTestList.add(10);
myTestList.add(22);
myTestList.add(56);
List<Integer> sortList = myTestList.stream()
                .sorted(Integer::compareTo).collect(Collectors.toList());
System.out.println("sortList:"+sortList);
運行結果:
sortList:[10, 22, 39, 56, 78]

9.Collect

collect在流中生成列表,map,等經常使用的數據結構

將一個流收集到一個List中,只須要這樣寫就能夠。
List<Integer> thereList = hereList.stream().collect(Collectors.toList());

收集到Set中能夠這樣用
Set<Integer> thereSet = hereList.stream().collect(Collectors.toSet());

收集到Set時,控制Set的類型,能夠這樣。
TreeSet<Integer> treeSet = hereList.stream()
                    .collect(Collectors.toCollection(TreeSet::new));

10.聚合操做

聚合是指將流匯聚爲一個值,以便在程序中使用。聚合方法都是終止操做,聚合方法包括sum,count,max,min

long sum = Stream.of(12,77,59,3,654).filter(i->i>20).mapToInt(Integer::intValue).sum();
findFirst方法返回非空集合中的第一個值,它一般與filter方法結合起來使用
Integer first = hearList.stream().filter(i->i>100).findFirst().get();

findAny方法能夠在集合中只要找到任何一個所匹配的元素,就返回,此方法在對流並行執行時十分有效
Integer anyItem = hearList.parallelStream().filter(i->i>100).findAny().get();

11.分組

對具備相同特性的值進行分組是一個很常見的功能

將一個Room對象集合按照高度分組。

List<Room> roomList = Lists.newArrayList(
new Room(11,23,56),
new Room(11,84,48),
new Room(22,46,112),
new Room(22,75,62),
new Room(22,56,75),
new Room(33,92,224));

Map<Integer,List<Room>> groupMap = roomList.stream().collect(Collectors.groupingBy(Room::getHigh));
System.out.println("groupMap:"+groupMap);

2.3 Stream流的處理順序

Stream流的中間操做具備延遲性,當且僅當存在終端操做時,中間操做纔會被執行

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    });

執行此代碼段時,不會打印任何內容,對上面的代碼添加 forEach終端操做,就有打印內容了

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    })
    .forEach(s -> System.out.println("forEach: " + s));
filter:  d2
forEach: d2
filter:  a2
forEach: a2
filter:  b1
forEach: b1
filter:  b3
forEach: b3
filter:  c
forEach: c

可是能夠看到輸出結果並非先將全部filter操做的打印語句打印出來;事實上,輸出的結果倒是隨着鏈條垂直移動的,好比說,當 Stream 開始處理 d2 元素時,它實際上會在執行完 filter 操做後,再執行 forEach 操做,接着纔會處理第二個元素

緣由是出於性能的考慮。這樣設計能夠減小對每一個元素的實際操做數,好比下面操做

Stream.of("d2", "a2", "b1", "b3", "c")
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase(); // 轉大寫
    })
    .anyMatch(s -> {
        System.out.println("anyMatch: " + s);
        return s.startsWith("A"); // 過濾出以 A 爲前綴的元素
    });

// map:      d2
// anyMatch: D2
// map:      a2
// anyMatch: A2

終端操做 anyMatch()表示任何一個元素以 A 爲前綴,返回爲 true,就中止循環。因此它會從 d2 開始匹配,接着循環到 a2 的時候,返回爲 true ,因而中止循環。

因爲數據流的鏈式調用是垂直執行的,map這裏只須要執行兩次。相對於水平執行來講,map會執行儘量少的次數,而不是把全部元素都 map 轉換一遍

stream --> filter --> map --> sorted --> collect

2.4 並行流

和迭代器不一樣的是,Stream 能夠並行化操做,迭代器只能命令式地、串行化操做。顧名思義,當使用串行方式去遍歷時,每一個 item 讀完後再讀下一個 item;
Stream具備平行處理能力,處理的過程會分而治之,也就是將一個大任務切分紅多個小任務,這表示每一個任務都是一個操做

//parallel方法能夠將任意的串行流轉換爲一個並行流
Stream.of(roomList).parallel();
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream()
       .forEach(out::println);  
//展現順序不必定會是一、二、三、四、五、六、七、八、9,而多是任意的順序

最後

你們看完有什麼不懂的歡迎在下方留言討論

相關文章
相關標籤/搜索