匿名內部類仍然是一個類,只是不須要程序員顯示指定類名,編譯器會自動爲該類取名。所以若是有以下形式的代碼,編譯以後將會產生兩個class文件:java
public class MainAnonymousClass { public static void main(String[] args) { new Thread(new Runnable(){ @Override public void run(){ System.out.println("Anonymous Class Thread run()"); } }).start();; } }
編譯以後文件分佈以下,兩個class文件分別是主類和匿名內部類產生的:git
進一步分析主類MainAnonymousClass.class的字節碼,可發現其建立了匿名內部類的對象:程序員
// javap -c MainAnonymousClass.class public class MainAnonymousClass { ... public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/Thread 3: dup 4: new #3 // class MainAnonymousClass$1 /*建立內部類對象*/ 7: dup 8: invokespecial #4 // Method MainAnonymousClass$1."<init>":()V 11: invokespecial #5 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V 14: invokevirtual #6 // Method java/lang/Thread.start:()V 17: return }
Lambda表達式經過invokedynamic指令實現,書寫Lambda表達式不會產生新的類。若是有以下代碼,編譯以後只有一個class文件:github
public class MainLambda { public static void main(String[] args) { new Thread( () -> System.out.println("Lambda Thread run()") ).start();; } }
編譯以後的結果:編程
經過javap反編譯命名,咱們更能看出Lambda表達式內部表示的不一樣:數組
// javap -c -p MainLambda.class public class MainLambda { ... public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/Thread 3: dup 4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; /*使用invokedynamic指令調用*/ 9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V 12: invokevirtual #5 // Method java/lang/Thread.start:()V 15: return private static void lambda$main$0(); /*Lambda表達式被封裝成主類的私有方法*/ Code: 0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #7 // String Lambda Thread run() 5: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
反編譯以後咱們發現Lambda表達式被封裝成了主類的一個私有方法,並經過invokedynamic指令進行調用。數據結構
你可能沒意識到Java對函數式編程的重視程度,看看Java 8加入函數式編程擴充多少功能就清楚了。Java 8之因此費這麼大功夫引入函數式編程,緣由有二:app
parallel()
方法。
圖中4種stream接口繼承自BaseStream
,其中IntStream, LongStream, DoubleStream
對應三種基本類型(int, long, double
,注意不是包裝類型),Stream
對應全部剩餘類型的stream視圖。爲不一樣數據類型設置不一樣stream接口,能夠1.提升性能,2.增長特定接口函數。ide
雖然大部分狀況下stream是容器調用Collection.stream()
方法獲得的,但stream和collections有如下不一樣:函數式編程
對stream的操做分爲爲兩類,中間操做(intermediate operations)和結束操做(terminal operations),兩者特色是:
若是你熟悉Apache Spark RDD,對stream的這個特色應該不陌生。
下表彙總了Stream
接口的部分常見方法:
操做類型 | 接口方法 |
---|---|
中間操做 | concat() distinct() filter() flatMap() limit() map() peek() skip() sorted() parallel() sequential() unordered() |
結束操做 | allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray() |
區分中間操做和結束操做最簡單的方法,就是看方法的返回值,返回值爲stream的大都是中間操做,不然是結束操做。
函數原型爲<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
,做用是對每一個元素執行mapper
指定的操做,並用全部mapper
返回的Stream
中的元素組成一個新的Stream
做爲最終返回結果。提及來太拗口,通俗的講flatMap()
的做用就至關於把原stream中的全部元素都」攤平」以後組成的Stream
,轉換先後元素的個數和類型均可能會改變。
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2), Arrays.asList(3, 4, 5)); stream.flatMap(list -> list.stream()) .forEach(i -> System.out.println(i));
上述代碼中,原來的stream
中有兩個元素,分別是兩個List<Integer>
,執行flatMap()
以後,將每一個List
都「攤平」成了一個個的數字,因此會新產生一個由5個數字組成的Stream
。因此最終將輸出1~5這5個數字。
截止到目前咱們感受良好,已介紹Stream
接口函數理解起來並不費勁兒。若是你就此覺得函數式編程不過如此,恐怕是高興地太早了。下一節對Stream
規約操做的介紹將刷新你如今的認識。
reduce操做能夠實現從一組元素中生成一個值,sum()
、max()
、min()
、count()
等都是reduce操做,將他們單獨設爲函數只是由於經常使用。reduce()
的方法定義有三種重寫形式:
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
雖然函數定義愈來愈長,但語義未曾改變,多的參數只是爲了指明初始值(參數identity),或者是指定並行執行時多個部分結果的合併方式(參數combiner)。reduce()
最經常使用的場景就是從一堆值中生成一個值。用這麼複雜的函數去求一個最大或最小值,你是否是以爲設計者有病。其實否則,由於「大」和「小」或者「求和」有時會有不一樣的語義。
需求:從一組單詞中找出最長的單詞。這裏「大」的含義就是「長」。
// 找出最長的單詞 Stream<String> stream = Stream.of("I", "love", "you", "too"); Optional<String> longest = stream.reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2); //Optional<String> longest = stream.max((s1, s2) -> s1.length()-s2.length()); System.out.println(longest.get());
上述代碼會選出最長的單詞love,其中Optional是(一個)值的容器,使用它能夠避免null值的麻煩。固然可使用Stream.max(Comparator<? super T> comparator)
方法來達到同等效果,但reduce()
自有其存在的理由。
需求:求出一組單詞的長度之和。這是個「求和」操做,操做對象輸入類型是String,而結果類型是Integer。
// 求單詞長度之和 Stream<String> stream = Stream.of("I", "love", "you", "too"); Integer lengthSum = stream.reduce(0, // 初始值 // (1) (sum, str) -> sum+str.length(), // 累加器 // (2) (a, b) -> a+b); // 部分和拼接器,並行執行時纔會用到 // (3) // int lengthSum = stream.mapToInt(str -> str.length()).sum(); System.out.println(lengthSum);
上述代碼標號(2)處將i. 字符串映射成長度,ii. 並和當前累加和相加。這顯然是兩步操做,使用reduce()
函數將這兩步合二爲一,更有助於提高性能。若是想要使用map()
和sum()
組合來達到上述目的,也是能夠的。
reduce()
擅長的是生成一個值,若是想要從Stream生成一個集合或者Map等複雜的對象該怎麼辦呢?終極武器collect()
橫空出世!
不誇張的講,若是你發現某個功能在Stream接口中沒找到,十有八九能夠經過collect()
方法實現。collect()
是Stream接口方法中最靈活的一個,學會它纔算真正入門Java函數式編程。先看幾個熱身的小例子:
// 將Stream轉換成容器或Map Stream<String> stream = Stream.of("I", "love", "you", "too"); List<String> list = stream.collect(Collectors.toList()); // (1) // Set<String> set = stream.collect(Collectors.toSet()); // (2) // Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length)); // (3)
上述代碼分別列舉了如何將Stream轉換成List、Set和Map。雖然代碼語義很明確,但是咱們仍然會有幾個疑問:
Function.identity()
是幹什麼的?String::length
是什麼意思?Function是一個接口,那麼Function.identity()
是什麼意思呢?這要從兩方面解釋:
identity()
就是Function接口的一個靜態方法。Function.identity()
返回一個輸出跟輸入同樣的Lambda表達式對象,等價於形如t -> t
形式的Lambda表達式。上面的解釋是否是讓你疑問更多?不要問我爲何接口中能夠有具體方法,也不要告訴我你以爲t -> t
比identity()
方法更直觀。我會告訴你接口中的default方法是一個無奈之舉,在Java 7及以前要想在定義好的接口中加入新的抽象方法是很困難甚至不可能的,由於全部實現了該接口的類都要從新實現。試想在Collection接口中加入一個stream()
抽象方法會怎樣?default方法就是用來解決這個尷尬問題的,直接在接口中實現新加入的方法。既然已經引入了default方法,爲什麼再也不加入static方法來避免專門的工具類呢!
諸如String::length
的語法形式叫作方法引用(method references),這種語法用來替代某些特定形式Lambda表達式。若是Lambda表達式的所有內容就是調用一個已有的方法,那麼能夠用方法引用來替代Lambda表達式。方法引用能夠細分爲四類:
方法引用類別 | 舉例 |
---|---|
引用靜態方法 | Integer::sum |
引用某個對象的方法 | list::add |
引用某個類的方法 | String::length |
引用構造方法 | HashMap::new |
咱們會在後面的例子中使用方法引用。
相信前面繁瑣的內容已完全打消了你學習Java函數式編程的熱情,不過很遺憾,下面的內容更繁瑣。但這不能怪Stream類庫,由於要實現的功能自己很複雜。
收集器(Collector)是爲Stream.collect()
方法量身打造的工具接口(類)。考慮一下將一個Stream轉換成一個容器(或者Map)須要作哪些工做?咱們至少須要兩樣東西:
List.add()
仍是Map.put()
。若是並行的進行規約,還須要告訴collect() 3. 多個部分結果如何合併成一個。
結合以上分析,collect()方法定義爲<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
,三個參數依次對應上述三條分析。不過每次調用collect()都要傳入這三個參數太麻煩,收集器Collector就是對這三個參數的簡單封裝,因此collect()的另外一定義爲<R,A> R collect(Collector<? super T,A,R> collector)
。Collectors工具類可經過靜態方法生成各類經常使用的Collector。舉例來講,若是要將Stream規約成List能夠經過以下兩種方式實現:
https://objcoding.com/2019/03...
本篇文章由一文多發平臺ArtiPub自動發佈