Java8之深入理解Lambda 頂
- 2019 年 12 月 20 日
- 筆記
lambda表達式實戰
從例子引出lambda
傳遞Runnable創建Thread
- java8之前
Thread thread=new Thread(new Runnable() { @Override public void run() { // do something } });
- java 8 之後
new Thread(()->{});
上邊的例子比較簡單,但是有兩個疑問。什麼是Lambda表達式?怎麼使用lambda表達式?
什麼是Lambda表達式?
從上述例子入手,首先我們知道Lambda一般代表的是一個匿名對象;其次我們點擊「->」,IDE會幫助我們進入到符合Lambda規範的函數接口。我們來觀察下這個符合規範的類的變化。
- java7 Runnable
// 省略注釋 package java.lang; public interface Runnable { public abstract void run(); }
- java8 Runnable
// 省略注釋 package java.lang; @FunctionalInterface public interface Runnable { public abstract void run(); }
我們發現java8後Runnable接口新增了一個註解@FunctionalInterface。下邊我們一起來看下這個註解是什麼。
FunctionalInterface
/** * 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 {}
- 上邊文檔的核心意思是:@FunctionInterface註解是為了表明這個類是一個函數式接口。
- 函數式接口有這樣的特點:只有一個抽象方法。java8提供了default方法,以及超類Object中的方法(toString,Equals),這些方法不計算抽象方法數量的統計中。
- 使用上:函數式接口可以配合lambda表達式、方法引用、構造引用使用。
- 如果類上標記了這個註解,編譯器會在編譯期進行檢查
- 最後,即使我們沒有標註這個註解,編譯器也會將它看待成一個函數式接口
好了,從上邊我們知道了lambda的特點,接下來我們來聊下怎麼使用?
如何使用Lambda
首先,我們去官網查閱Java8新特性,找到Lambda表達式的說明。我們從這個文檔的**「Syntax of Lambda Expressions」**部分入手,大概可以得到如下的結論。
Lambda的組成
Lambda主要由下邊幾部分組成;參數列表,連接符,主體。
- 參數列表
- 圓括號內部,參數以「,」分割開來。如(String a,Object b)。
- 此外,參數的類型和括號,有些時候是可以省略
- 箭頭記號
- 通過「->」這種特殊符號形式,連接前後。
- 主體
- 可以由單個表達式,或者語句塊組成。
- 單個表達式,如"System.out.println("xxx")"
- 語句塊
- 示例1
{ System.out.println("xxx"); }
- 示例2
{ // do something return some result return 100; }
Lambda的完整用法示例
無返回值的lambda的用例
目的,將具體業務實現交給調用者處理。
- 定義一個無返回值,符合FunctionInterface規範的接口對象
interface Print<String>{ void printName(String string); }
使用示例1
我這裡的業務邏輯是根據輸入參數,執行日誌打印操作。實際業務場景下,可能對應的是發送郵件或者MQ這樣的具體操作。
public class LambdaDemo { public static void main(String[] args) { PrintSomeThing(name->System.out.println(name),"Hello baigt"); } public static void PrintSomeThing(Print<String> str,String name) { str.printName(name); } }
使用示例1 的延伸使用
- 定義 一個使用類
class Doctor{ String name; String interest; public Doctor(String name, String interest) { this.name = name; this.interest = interest; } public void printName(Print<String> str) { str.printName(name); } }
- 具體使用
Doctor doctor=new Doctor("baigt","java and javascript"); doctor.printName(name->System.out.println(name));
有返回值的lambda的用例
目的,將具體業務實現交給調用者處理,並將結果返回。
- 定義一個有返回值,符合FunctionInterface規範的接口對象
interface GetSomething<String>{ String getThing(); }
- 定義一個使用者
class Doctor{ String name; String interest; public Doctor(String name, String interest) { this.name = name; this.interest = interest; } public String getInterest(GetSomething<String> get) { return get.getThing()+","+name; } }
- 使用示例
我這裡的業務邏輯是根據輸入參數(隱式interest),計算出一個結果返回出來,並對這個結果執行打印操作。
Doctor doctor=new Doctor("baigt","java and javascript"); System.out.println(doctor.getInterest(() -> "Hi"));
到此處,我們已經大概明白lambda表達式的基本用法。但是還會有兩個疑問?
- 上邊例子我們自定義了幾個函數式接口,那麼還有其他常用的函數式接口?
- 函數式接口不僅可以通過lambda表達式使用,還可以通過方法引用和構造引用來使用。那麼這種引用又是怎麼回事?
常用函數接口
我們選中@FunctionInterface註解類,通過Ide的Find Usages功能,會發現在java.util.function包下java8新增了很多類。這裡挑幾個基礎的(其他的基本是功能上的增強或變種)來說。大致上有這麼幾種。
- Consumer
- Supplier
- Predicate
- Function
下邊會做一個簡單的說明和使用。可能不會細緻的去講每一個Api。旨在讓大家快速熟悉使用java8 lambda。
Consumer
/** * Represents an operation that accepts a single input argument and returns no * result. Unlike most other functional interfaces, {@code Consumer} is expected * to operate via side-effects. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #accept(Object)}. * * @param <T> the type of the input to the operation * * @since 1.8 */ @FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t); /** * Returns a composed {@code Consumer} that performs, in sequence, this * operation followed by the {@code after} operation. If performing either * operation throws an exception, it is relayed to the caller of the * composed operation. If performing this operation throws an exception, * the {@code after} operation will not be performed. * * @param after the operation to perform after this operation * @return a composed {@code Consumer} that performs in sequence this * operation followed by the {@code after} operation * @throws NullPointerException if {@code after} is null */ default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
首先此接口只有一個抽象方法accept,該方法接收一個入參,不返回結果。
定義使用類
public static void doConsumer(Consumer consumer,String input) { consumer.accept(input); }
- 使用示例1
接收 「something input」輸入,並執行打印操作
Consumer consumer = input -> System.out.println(input); doConsumer(consumer,"something input");
- 使用示例2
將兩個Consumer操作串連起來,andThen的後執行。
Consumer consumer = input -> System.out.println(input); doConsumer(consumer.andThen(input2->{ System.out.println("input2"); }),"something input");
Supplier
/** * Represents a supplier of results. * * <p>There is no requirement that a new or distinct result be returned each * time the supplier is invoked. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #get()}. * * @param <T> the type of results supplied by this supplier * * @since 1.8 */ @FunctionalInterface public interface Supplier<T> { /** * Gets a result. * * @return a result */ T get(); }
首先此接口只有一個抽象方法get,該方法不接收參數,返回一個T類型的結果。
定義使用類
public static <T> T doSupplier(Supplier<T> supplier) { return supplier.get(); }
- 使用示例1
不傳入參數,生成一個指定類型為String或Integer的對象
System.out.println(doSupplier(() -> "baigt")); System.out.println(doSupplier(() -> {return Integer.valueOf("10");}));
Predicate
import java.util.Objects; /** * Represents a predicate (boolean-valued function) of one argument. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #test(Object)}. * * @param <T> the type of the input to the predicate * * @since 1.8 */ @FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t); /** * Returns a composed predicate that represents a short-circuiting logical * AND of this predicate and another. When evaluating the composed * predicate, if this predicate is {@code false}, then the {@code other} * predicate is not evaluated. * * <p>Any exceptions thrown during evaluation of either predicate are relayed * to the caller; if evaluation of this predicate throws an exception, the * {@code other} predicate will not be evaluated. * * @param other a predicate that will be logically-ANDed with this * predicate * @return a composed predicate that represents the short-circuiting logical * AND of this predicate and the {@code other} predicate * @throws NullPointerException if other is null */ default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } }
首先此接口只有一個抽象方法test,該方法接受一個T類型的對象,返回一個boolean類型的結果。
定義使用類
public static boolean doPredicate(Predicate<String> predicate,String string) { return predicate.test(string); }
- 使用示例1
根據條件,判斷輸入對象是否符合過濾規則。
System.out.println(doPredicate(input -> input.length() > 5, "12345")); System.out.println(doPredicate(((Predicate<String>) (input -> input.length() > 5)) .and(input -> input.equalsIgnoreCase("12345")), "12345"));
Function
import java.util.Objects; /** * Represents a function that accepts one argument and produces a result. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #apply(Object)}. * * @param <T> the type of the input to the function * @param <R> the type of the result of the function * * @since 1.8 */ @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; } }
首先此接口只有一個抽象方法apply,該方法接收一個T類型對象,返回一個R類型的結果。
定義使用類
public static Integer doFunction(Function<String,Integer> function,String input) { return function.apply(input); }
- 使用示例1
接收一個String類型的入參,返回Integer類型的結果。示例中沒做具體異常判斷。
System.out.println(doFunction(input -> input.length(), "baigt")); // 上述結果為 5 System.out.println(doFunction(((Function<String, Integer>) (input -> input.length())).compose(input -> String.valueOf(input.length() * 3)), "baigt")); // 上述結果為 2 System.out.println(doFunction(((Function<String, Integer>) (input -> { System.out.println("notcompose:"+input); return Integer.valueOf(input)+1; })).compose(input -> { System.out.println("compose:"+input); return String.valueOf(Integer.valueOf(input)*3); }), "22")); // 上述結果為 67
compose是先執行的部分,上述例子中,是根據輸入參數進行進一步的加工,再作為輸入參數傳遞給具體調用者。
引用
前邊提到了方法引用和構造引用兩種,其實構造引用是一種特殊方法引用。具體參照官方文檔說明中「Kinds of Method References」部分。
種類 |
用例 |
---|---|
類名::靜態方法 |
String::valueOf |
實例對象::實例方法 |
doctor1::getInterest |
類名::實例方法 |
String::toUpperCase |
類名::new (構造引用) |
String::new |
靜態引用
- 使用類
public static String doStaticReference(Function<Integer,String> function, Integer input) { return function.apply(input); }
- 示例
doStaticReference(String::valueOf,123456);
實例對象引用實例方法
- 使用類
class Doctor{ String name; String interest; public Doctor(String name, String interest) { this.name = name; this.interest = interest; } public String getStringInstance(){ return new String(name); } }
- 示例
Doctor doctor1=new Doctor("baigt007","java"); Supplier<String> instance = doctor1::getInterest;
類引用實例方法
- 使用類
public static String doMethodReference(Function<String,String> function, String input) { return function.apply(input); }
- 示例
doMethodReference(String::toUpperCase,"baigt");O
構造引用
- 示例
Supplier<String> stringInstance = String::new;
結語
上述是個人心得,不對之處,歡迎指正。