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規範的函數接口。我們來觀察下這個符合規範的類的變化。

// 省略注釋  package java.lang;  public interface Runnable {     public abstract void run();  }
// 省略注釋  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;

結語

上述是個人心得,不對之處,歡迎指正。