你真的了解 lambda 吗(纠错篇)?
來源:http://uee.me/aWWS2
1. 用法
示例:最普遍的一個例子,執行一個線程
new Thread(() -> System.out.print("hello world")).start();
->??我們發現它指向的是?Runnable接口
@FunctionalInterface public interface Runnable {/*** When an object implementing interface <code>Runnable</code> is used* to create a thread, starting the thread causes the object's* <code>run</code> method to be called in that separately executing* thread.* <p>* The general contract of the method <code>run</code> is that it may* take any action whatsoever.** @see ? ? java.lang.Thread#run()*/public abstract void run();}分析
->這個箭頭是lambda表達式的關鍵操作符
->把表達式分成兩截,前面是函數參數,后面是函數體。
Thread的構造函數接收的是一個Runnable接口對象,而我們這里的用法相當于是把一個函數當做接口對象傳遞進去了,這點理解很關鍵,這正是函數式編程的含義所在。
我們注意到Runnable有個注解?@FunctionalInterface,它是jdk8才引入,它的含義是函數接口。它是lambda表達式的協議注解,這個注解非常重要,后面做源碼分析會專門分析它的官方注釋,到時候一目了然。
2. 由此引發的一些案例
有參數有返回值的實例:集合排序
List<String> list = new ArrayList<>(); Collections.sort(list, (o1, o2) -> {if(o1.equals(o2)) {return 1;}return -1; })我們知道Collections.sort方法的第二個參數接受的是一個?Comparator<T>的對象,它的部分關鍵源碼是這樣的:
@FunctionalInterface public interface Comparator<T> {int compare(T o1, T o2); }如上已經去掉注釋和部分其他方法。
我們可以看到sort的第二個參數是Comparator的compare方法,參數類型是T,分別是o1和o2,返回值是一個int。
3. 疑問
-
上面的示例我們看到接口都有個?@FunctionalInterface的注解,但是我們在實際編程中并沒有加這個注解也可以實現lambda表達式,例如:
如上所示,確實不需要增加?@FunctionInterface注解就可以實現。
-
如果在1中的示例的ITest接口中增加另外一個接口方法,我們會發現不能再用lambda表達式。
我們帶著這兩個疑問來進入源碼解析。
4. 源碼解析
必須了解注解?@FunctionInterface
上源碼:
package java.lang;import java.lang.annotation.*;/** * An informative annotation type used to indicate that an interface * type declaration is intended to be a <i>functional interface</i> as * defined by the Java Language Specification. * * Conceptually, a functional interface has exactly one abstract * method. ?Since {@linkplain java.lang.reflect.Method#isDefault() * default methods} have an implementation, they are not abstract. ?If * an interface declares an abstract method overriding one of the * public methods of {@code java.lang.Object}, that also does * <em>not</em> count toward the interface's abstract method count * since any implementation of the interface will have an * implementation from {@code java.lang.Object} or elsewhere. * * <p>Note that instances of functional interfaces can be created with * lambda expressions, method references, or constructor references. * * <p>If a type is annotated with this annotation type, compilers are * required to generate an error message unless: * * <ul> * <li> The type is an interface type and not an annotation type, enum, or class. * <li> The annotated type satisfies the requirements of a functional interface. * </ul> * * <p>However, the compiler will treat any interface meeting the * definition of a functional interface as a functional interface * regardless of whether or not a {@code FunctionalInterface} * annotation is present on the interface declaration. * * @jls 4.3.2. The Class Object * @jls 9.8 Functional Interfaces * @jls 9.4.3 Interface Method Body * @since 1.8 */@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FunctionalInterface {}我們說過這個注解用來規范lambda表達式的使用協議的,那么注釋中都說了哪些呢?
-
一種給interface做注解的注解類型,被定義成java語言規范。
* An informative annotation type used to indicate that an functional interface
* type declaration is intended to be a <b>functional interface</b> as
* defined by the Java Language Specification.
-
一個被它注解的接口只能有一個抽象方法,有兩種例外。
第一是接口允許有實現的方法。
這種實現的方法是用default關鍵字來標記的
(java反射中java.lang.reflect.Method#isDefault()方法用來判斷是否是default方法),例如:
當然這是jdk8才引入的特性,到此我們才知道,知識是一直在變化的,我們在學校中學到interface接口不允許有實現的方法是錯誤的,隨著時間推移,一切規范都有可能發生變化。
如果聲明的方法和java.lang.Object中的某個方法一樣,它可以不當做未實現的方法,不違背這個原則:一個被它注解的接口只能有一個抽象方法
例如同樣是Compartor接口中,它重新聲明了equals方法:
這些是對如下注釋的翻譯和解釋。
* Conceptually, a functional interface has exactly one abstract * method. ?Since {@linkplain java.lang.reflect.Method#isDefault() * default methods} have an implementation, they are not abstract. ?If * an interface declares an abstract method overriding one of the * public methods of {@code java.lang.Object}, that also does * <em>not</em> count toward the interface's abstract method count * since any implementation of the interface will have an * implementation from {@code java.lang.Object} or elsewhere.-
如果一個類型被這個注解修飾,那么編譯器會要求這個類型必須滿足如下條件:
這個類型必須是一個interface,而不是其他的注解類型、枚舉enum或者類class
這個類型必須滿足function interface的所有要求,如你個包含兩個抽象方法的接口增加這個注解,會有編譯錯誤。
-
編譯器會自動把滿足function interface要求的接口自動識別為function interface,所以你才不需要對上面示例中的 ITest接口增加@FunctionInterface注解。
通過了解function interface我們能夠知道怎么才能正確的創建一個function interface來做lambda表達式了。接下來的是了解java是怎么把一個函數當做一個對象作為參數使用的。
5. 穿越:對象變身函數
讓我們重新復盤一下上面最開始的實例:
new Thread(() -> System.out.print("hello world")).start();
我們知道在jdk8以前我們都是這樣來執行的:
Runnable r = new Runnable(){System.out.print("hello world"); }; new Thread(r).start();我們知道兩者是等價的,也就是說 r 等價于?()->System.out.print("hello world"),一個接口對象等于一個lambda表達式?那么lambda表達式肯定做了這些事情(未看任何資料,純粹推理,有誤再改正):
創建接口對象
實現接口對象
返回接口對象
6. 關于?UnaryOperator
上篇文章(聊一聊JavaFx中的TextFormatter以及一元操作符UnaryOperator)關于 UnaryOperator草草收尾,在這里給大家重新梳理一下,關于它的使用場景以及它與lambda表達式的關系。
-
使用場景
要先理解它的作用,它是接受一個參數并返回與該類型同的值,來看一個List怎么用它的,java.util.List中的replaceAll就用它了:
?
default void replaceAll(UnaryOperator<E> operator) {Objects.requireNonNull(operator);final ListIterator<E> li = this.listIterator();while (li.hasNext()) {li.set(operator.apply(li.next()));}}我們可以看到這個方法的目的是把list中的值經過operator操作后重新返回一個新值,例如具體調用。
?
List<String> list = new ArrayList<>();list.add("abc");list.replaceAll(s -> s + "efg");System.out.println(list);其中lambda表達式 s->s+"efg"就是這個operator對象,那么最終list中的值就變成了["abcefg"],由此我們可以知道它的作用就是對輸入的值再加工,并返回同類型的值,怎么用就需要你自己擴展發揮了。
-
與lambda表達式的關系?
在我看來,它跟lambda表達式的關系并不大,只是它是jdk內置的一種標準操作,類似的二元操作符 BinaryOperator它可以接受兩個同類型參數,并返回同類型參數的值。
7. 關于UnaryOperator,我們百尺竿頭更進一步,深入到核心
先貼出它的源碼:
@FunctionalInterface public interface UnaryOperator<T> extends Function<T, T> {/*** Returns a unary operator that always returns its input argument.** @param <T> the type of the input and output of the operator* @return a unary operator that always returns its input argument*/static <T> UnaryOperator<T> identity() {return t -> t;}}我們看到這個function interface居然沒有抽象方法,不,不是沒有,我們繼續看Function接口。
@FunctionalInterface public interface Function<T, R> {/*** Applies this function to the given argument.** @param t the function argument* @return the function result*/R apply(T t);/*** Returns a composed function that first applies the {@code before}* function to its input, and then applies this function to the result.* If evaluation of either function throws an exception, it is relayed to* the caller of the composed function.** @param <V> the type of input to the {@code before} function, and to the* ? ? ? ? ? composed function* @param before the function to apply before this function is applied* @return a composed function that first applies the {@code before}* function and then applies this function* @throws NullPointerException if before is null** @see #andThen(Function)*/default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {Objects.requireNonNull(before);return (V v) -> apply(before.apply(v));}/*** Returns a composed function that first applies this function to* its input, and then applies the {@code after} function to the result.* If evaluation of either function throws an exception, it is relayed to* the caller of the composed function.** @param <V> the type of output of the {@code after} function, and of the* ? ? ? ? ? composed function* @param after the function to apply after this function is applied* @return a composed function that first applies this function and then* applies the {@code after} function* @throws NullPointerException if after is null** @see #compose(Function)*/default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}/*** Returns a function that always returns its input argument.** @param <T> the type of the input and output objects to the function* @return a function that always returns its input argument*/static <T> Function<T, T> identity() {return t -> t;}}既然他們都被注解為?@FunctionInterface了,那么他們肯定有一個唯一的抽象方法,那就是 apply。
我們知道?->lambda表達式它是不需要關心函數名字的,所以不管它叫什么, apply也好, apply1也好都可以,但jdk肯定要叫一個更加合理的名字,那么我們知道 s->s+"efg"中?->調用的就是 apply方法。
而且我們注意到這里有一個 identity()的靜態方法,它返回一個Function對象,它其實跟lambda表達式關系也不大,它的作用是返回當前function所要表達的lambda含義。相當于創建了一個自身對象。
Function算是lambda的一種擴展應用,這個Function的的作用,是 Representsafunctionthat accepts one argumentandproduces a result.意思是接受一個參數,并產生(返回)一個結果(類型可不同)。
類似的還有很多Function,都在包java.util.Function中。
你也可以創建自己的Function,它是用來表達操作是怎樣的。如傳入的參數是什么,返回的是什么。
其實你只要明白它抽象的是操作就可以了。
到此就知道,原來UnaryOperator沒啥神秘的,jdk把這些操作放在java.util.function中也正說明了它是一個工具類,是為了提取重復代碼,讓它可以重用,畢竟需要用到這樣的操作的地方太多了,提取是有必要的。
總結
以上是生活随笔為你收集整理的你真的了解 lambda 吗(纠错篇)?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从工具到社区,美图秀秀大规模性能优化实践
- 下一篇: 最强 AWS 的十条军规,首席技术官总结