java 8 stream_深度分析:java8的新特性lambda和stream流,看完你学会了吗?
1. lambda表達(dá)式
1.1 什么是lambda
以java為例,可以對一個java變量賦一個值,比如int a = 1,而對于一個方法,一塊代碼也是賦予給一個變量的,對于這塊代碼,或者說被賦給變量的函數(shù),就是一個lambda表達(dá)式
//為變量賦值 int a = 1;//將代碼塊賦值給變量 var = public void fun(int x){x+1; }//可以簡化 var = (x)->x+1;1.2 java為什么要引入lambda
lambda是為函數(shù)式編程服務(wù)的 編程語言共性之------什么是函數(shù)式編程? 函數(shù)式編程是一種編程范式,也就是如何編寫程序的方法論,主要思想是把運算過程盡量編寫成一系列嵌套的函數(shù)調(diào)用,FP強(qiáng)調(diào)“everything is lambda",并且強(qiáng)調(diào)在邏輯處理中不變性的重要性
OOP強(qiáng)調(diào)“everything is object”,以及object之間的消息傳遞。通過消息傳遞改變每個Object的內(nèi)部狀態(tài),但是很多情況代碼的編寫實際上是用不到對象的,比如,對一組數(shù)據(jù)做加工,先查詢,然后聚合,聚合后排序,再join,再排序,再聚合,再轉(zhuǎn)換(map)得到最終的結(jié)果。這個過程,用FP的函數(shù)就很自然
result = func1(func2(func3...funcN(x))))java為了在原先oop的思想上增加函數(shù)式編程的使用,在java8上增加了lambda函數(shù)的新特性
除此之外,lambda表達(dá)式的引入還使得代碼更為簡潔,可以避免生成過多的污染環(huán)境的無用實現(xiàn)類(下面說)
1.3 如何使用lambda表達(dá)式
lambda表達(dá)式的引入可以避免生成過多的污染環(huán)境的實現(xiàn)類; lambda表達(dá)式可以被賦值給一個變量,那么這個變量的類型是什么? 在java中,所有的Lambda的類型都是一個接口,而Lambda表達(dá)式本身,需要是這個接口的實現(xiàn),這個接口需要具備三個特征,具備這些特征的接口叫做函數(shù)式接口
函數(shù)式接口只有一個抽象方法 default方法為默認(rèn)實現(xiàn),不計入抽象方法 如果接口聲明了一個覆蓋java.lang.Object的全局方法之一的抽象方法,那么它不會計入接口的抽象方法數(shù)量中,因為接口的任何實現(xiàn)都將具有java.lang.Object或其他地方的實現(xiàn) 如何使用lambda表達(dá)式 比如Comparator接口就是一個函數(shù)式接口,所以他可以使用lambda表達(dá)式,在之前使用comparator對一個list排序是下面這樣的
List<Integer> list = new ArrayList<>(); Collections.sort(list, new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return o1-o2;} });可以看到上面實際真正有用的是return o1 - o2,上面的代碼使用lambda表達(dá)式寫如下
Collections.sort(list, ((o1, o2) -> o1-o2));Lambda 表達(dá)式的基礎(chǔ)語法:Lambda 操作符->將 Lambda 表達(dá)式拆分成兩部分: 左側(cè):Lambda 表達(dá)式的參數(shù)列表; 右側(cè):Lambda 表達(dá)式中所需執(zhí)行的功能, 即 Lambda 體;
語法格式一:無參數(shù),無返回值 () -> System.out.println("Hello Lambda!");語法格式二:有一個參數(shù),并且無返回值 (x) -> System.out.println(x)語法格式三:若只有一個參數(shù),小括號可以省略不寫 x -> System.out.println(x)語法格式四:有兩個以上的參數(shù),有返回值,并且 Lambda 體中有多條語句 Comparator<Integer> com = (x, y) -> { System.out.println("函數(shù)式接口"); return Integer.compare(x, y); };語法格式五:若 Lambda 體中只有一條語句, return 和 大括號都可以省略不寫 Comparator<Integer> com = (x, y) -> Integer.compare(x, y);1.4 lambda表達(dá)式方法引用,構(gòu)造器引用和數(shù)組引用
方法引用 若 Lambda 體中的功能,已經(jīng)有方法提供了實現(xiàn),可以使用方法引用
對象的引用 :: 實例方法名 類名 :: 靜態(tài)方法名 類名 :: 實例方法名①方法引用所引用的方法的參數(shù)列表與返回值類型,需要與函數(shù)式接口中抽象方法的參數(shù)列表和返回值類型保持一致! ②若Lambda 的參數(shù)列表的第一個參數(shù),是實例方法的調(diào)用者,第二個參數(shù)(或無參)是實例方法的參數(shù)時,格式: 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("----------------------------------"); // 現(xiàn)在我們是這樣寫的Supplier<String> sup2 = emp::getName;System.out.println(sup2.get()); }//類名 :: 靜態(tài)方法名 @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"));}構(gòu)造器引用
對于person類,有兩個構(gòu)造器
class Person {String firstName;String lastName;Person() {}Person(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;} }現(xiàn)在有一個工廠接口用來生成person類
// Person 工廠 interface PersonFactory<P extends Person> {P create(String firstName, String lastName); }我們可以通過 :: 關(guān)鍵字來引用 Person 類的構(gòu)造器,來代替手動去實現(xiàn)這個工廠接口:
// 直接引用 Person 構(gòu)造器 PersonFactory<Person> personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");Person::new 這段代碼,能夠直接引用 Person 類的構(gòu)造器。然后 Java 編譯器能夠根據(jù)上下文選中正確的構(gòu)造器去實現(xiàn) PersonFactory.create 方法
2.1 什么是Stream
Java 8引入了全新的Stream API,這里的Stream和I/O流不同,Java 8 中的 Stream 是對集合(Collection)對象功能的增強(qiáng),它專注于對集合對象進(jìn)行各種非常便利、高效的聚合操作,或者大批量數(shù)據(jù)操作,Stream API 借助于同樣新出現(xiàn)的 Lambda 表達(dá)式,極大的提高編程效率和程序可讀性
Stream 就如同一個迭代器(Iterator),單向,不可往復(fù),數(shù)據(jù)只能遍歷一次,遍歷過一次后即用盡了,就好比流水從面前流過,一去不復(fù)返
List<String> myList =Arrays.asList("a1", "a2", "b1", "c2", "c1");myList.stream() // 創(chuàng)建流.filter(s -> s.startsWith("c")) // 執(zhí)行過濾,過濾出以 c 為前綴的字符串.map(String::toUpperCase) // 轉(zhuǎn)換成大寫.sorted() // 排序.forEach(System.out::println); // for 循環(huán)打印①:中間操作會再次返回一個流,所以,我們可以鏈接多個中間操作,注意這里是不用加分號的。上圖中的filter 過濾,map 對象轉(zhuǎn)換,sorted 排序,就屬于中間操作。 ②:終端操作是對流操作的一個結(jié)束動作,一般返回 void 或者一個非流的結(jié)果。上圖中的 forEach循環(huán) 就是一個終止操作上面是Stream的簡單實用,可以看出它也是函數(shù)式編程,更多的表達(dá)了業(yè)務(wù)邏輯
2.2 常用api
創(chuàng)建Stream
1. Arrays.stream()
當(dāng)在日常編程中面對的是一個數(shù)組,可以使用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()
當(dāng)面對數(shù)組時除了可以使用Arrays.stream()方法外,還可以使用Stream將需要的數(shù)組轉(zhuǎn)成Stream。這個方法不但支持傳入數(shù)組,將數(shù)組轉(zhuǎn)成Stream,也支持傳入多個參數(shù),將參數(shù)最終轉(zhuǎn)成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中的集合都繼承或?qū)崿F(xiàn)了此接口。所以Java中的集合都可以使用此方法來創(chuàng)建一個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的過濾轉(zhuǎn)換,此方法會生成一個新的流,其中包含符合某個特定條件的所有元素,filter接受一個函數(shù)作為參數(shù),該函數(shù)用Lambda表達(dá)式表示
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,565.map
map方法指對一個流中的值進(jìn)行某種形式的轉(zhuǎn)換。需要傳遞給它一個轉(zhuǎn)換的函數(shù)作為參數(shù)
List<Integer> integerList = Lists.newArrayList(); integerList.add(15); integerList.add(32); integerList.add(5); integerList.add(232); integerList.add(56); //將Integer類型轉(zhuǎn)換成String類型 List<String> afterString = integerList.stream().map(i->String.valueOf(i)).collect(Collectors.toList());6.flatMap
將多個Stream連接成一個Stream,這時候不是用新值取代Stream的值,與map有所區(qū)別,這是重新生成一個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方法一起使用就可以實現(xiàn)日常的分頁功能:
List<Integer> pageList = myList.stream().skip(pageNumber*pageSize).limit(pageSize).collect(Collectors.toList());8.distinct方法和sorted方法
distinct方法會根據(jù)原始流中的元素返回一個具有相同順序、去除了重復(fù)元素的流,這個操作顯然是需要記住之前讀取的元素。
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); 運行結(jié)果: distinctList:[10, 39, 78]sorted方法是需要遍歷整個流的,并在產(chǎn)生任何元素之前對它進(jìn)行排序。因為有可能排序后集合的第一個元素會在未排序集合的最后一位。
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); 運行結(jié)果: sortList:[10, 22, 39, 56, 78]9.Collect
collect在流中生成列表,map,等常用的數(shù)據(jù)結(jié)構(gòu)
將一個流收集到一個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方法結(jié)合起來使用 Integer first = hearList.stream().filter(i->i>100).findFirst().get();findAny方法可以在集合中只要找到任何一個所匹配的元素,就返回,此方法在對流并行執(zhí)行時十分有效11.分組
對具有相同特性的值進(jìn)行分組是一個很常見的功能
將一個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流的中間操作具有延遲性,當(dāng)且僅當(dāng)存在終端操作時,中間操作才會被執(zhí)行
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {System.out.println("filter: " + s);return true;});執(zhí)行此代碼段時,不會打印任何內(nèi)容,對上面的代碼添加 forEach終端操作,就有打印內(nèi)容了
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但是可以看到輸出結(jié)果并不是先將所有filter操作的打印語句打印出來;事實上,輸出的結(jié)果卻是隨著鏈條垂直移動的,比如說,當(dāng) Stream 開始處理 d2 元素時,它實際上會在執(zhí)行完 filter 操作后,再執(zhí)行 forEach 操作,接著才會處理第二個元素
原因是出于性能的考慮。這樣設(shè)計可以減少對每個元素的實際操作數(shù),比如下面操作
Stream.of("d2", "a2", "b1", "b3", "c").map(s -> {System.out.println("map: " + s);return s.toUpperCase(); // 轉(zhuǎn)大寫}).anyMatch(s -> {System.out.println("anyMatch: " + s);return s.startsWith("A"); // 過濾出以 A 為前綴的元素});// map: d2 // anyMatch: D2 // map: a2 // anyMatch: A2終端操作 anyMatch()表示任何一個元素以 A 為前綴,返回為 true,就停止循環(huán)。所以它會從 d2 開始匹配,接著循環(huán)到 a2 的時候,返回為 true ,于是停止循環(huán)。
由于數(shù)據(jù)流的鏈?zhǔn)秸{(diào)用是垂直執(zhí)行的,map這里只需要執(zhí)行兩次。相對于水平執(zhí)行來說,map會執(zhí)行盡可能少的次數(shù),而不是把所有元素都 map 轉(zhuǎn)換一遍
stream --> filter --> map --> sorted --> collect2.4 并行流
和迭代器不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顧名思義,當(dāng)使用串行方式去遍歷時,每個 item 讀完后再讀下一個 item; Stream具有平行處理能力,處理的過程會分而治之,也就是將一個大任務(wù)切分成多個小任務(wù),這表示每個任務(wù)都是一個操作
//parallel方法可以將任意的串行流轉(zhuǎn)換為一個并行流 Stream.of(roomList).parallel(); List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); numbers.parallelStream().forEach(out::println); //展示順序不一定會是1、2、3、4、5、6、7、8、9,而可能是任意的順序最后
大家看完有什么不懂的歡迎在下方留言討論
作者:前程有光鏈接:https://juejin.im/post/5f0d9beef265da22a8515f6f
來源:掘金
總結(jié)
以上是生活随笔為你收集整理的java 8 stream_深度分析:java8的新特性lambda和stream流,看完你学会了吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 家庭常见的14种理财方式有哪些?
- 下一篇: fread读结构体返回值是0无错误_嵌入