java8學習:lambda表示式(1)

期待l發表於2018-11-07

內容來自《 java8實戰 》,本篇文章內容均為非盈利,旨為方便自己查詢、總結備份、開源分享。如有侵權請告知,馬上刪除。
書籍購買地址:java8實戰

  • 上一篇內容解釋了行為引數化的概念並用例項演示瞭如何是行為引數化,文末提到了lambda表示式,那麼本文將講解lambda表示式
  • 如平常的使用方法如下

    Comparator<Apple> comparator = new Comparator<Apple>() {
        @Override
        public int compare(Apple o1, Apple o2) {
            return Integer.compare(o1.getWeight(),o2.getWeight());
        }
    };
    lambda:Comparator<Apple> comparator = (Apple a1,Apple a2) -> Integer.compare(a1.getWeight(),a2.getWeight());
    • 對照上面的普通使用方法和lambda,lambda沒有名稱,但是一樣有引數列表,函式主體以及返回值,還可以擁有丟擲的異常型別,但是上面的lambda還是可以進一步簡化寫法的,下面講提到
  • lambda各部分名稱

    Comparator<Apple> comparator = (Apple a1,Apple a2) -> Integer.compare(a1.getWeight(),a2.getWeight());
                                        引數列表     ->只是用來分割引數列表和函式主體的    函式主體    
    • 如上清楚的標明瞭lambda各部分的名稱,那麼就會有疑問,他的返回值是什麼?上面lambda的函式主體的結果就是其返回值,只是沒有寫return而已,如下
    Comparator<Apple> comparator = (Apple a1,Apple a2) -> {
        return Integer.compare(a1.getWeight(),a2.getWeight());
    };       //注意大括號
    • 下面是一些lambda的例子
    (String s) -> s.length();    //引數為String,函式主體已給出,函式返回值是int
    (int x , int y ) ->{         //引數為(int,int),函式主體給出,函式無返回值void
      System.out.println(x);
      System.out.println(y);
    }
    () -> 42;                     //沒有引數,但是有返回值是int,就好比是   public int get(){return 42;}
    • 下面這個兩個lambda是不正確的
    (Integer i) -> return i + 1;  //如果是有return明確返回要使用花括號了,如上的Comparator程式碼中的return
    (String s) -> {"mes";}        //“mes”是一個表示式不是一個語句,如果想修正此問題,那麼可以去掉花括號,或者顯示宣告return
  • lambda可以在那裡使用呢?

    • 我們現在只是在跟著文章懵逼的一個字元字元的敲上去,然後執行看結果,只是知道這樣一種lambda形式,但是根本就不知道這個鬼東西可以出現在哪裡啊?在這就要涉及到一個函式式介面的概念了
    • 函式式介面就是介面中只有一個抽象方法,此方法不允許過載,而且你只要保證此介面中只有一個抽象方法,其他的default之類的其他方法任你編寫,如下
    @FunctionalInterface
    public interface PP {
        boolean isX ();
        default void cc(){
            System.out.println("ccc");
        }
    }
    • 如上註解就類似與@Override註解一樣,檢測此介面是否是函式式介面,如果不是就會有報錯資訊了
    • 瞭解了函式式介面是什麼東西,那麼我們回憶一下上一篇中,我們其實已經編寫了函式式介面,但是我們只是不知道函式式介面這個概念,比如
interface Filter<T>{
    boolean filter(T t);
}
public class Java8 {
        @Test
        public void test() throws Exception {
            List<Apple> apples = Arrays.asList();
            getAppleByColor(apples,apple -> apple.getWeight() > 1000);
        }
        public <T> void getAppleByColor(List<T> ts,Filter<T> filter){
            for (T t : ts) {
                if (filter.filter(t)){
                    System.out.println("...");
                }
            }
        }
}
  • 如上的函式式介面Filter就是我們上次編寫的,他只有一個抽象方法,然後我們就可以利用lambda對含有此介面為引數的方法傳入lambda了,然後實現了行為引數化
  • 我們也可以看上一篇提到的例項:Comparator和Runnable介面的定義,其中有很多方法,但是隻有一個抽象方法,這就不貼出來了,自己檢視原始碼就可以、
  • 用函式式介面可以幹啥?lambda表示式允許直接以內聯的形式為函式式介面的抽象方法提供實現,並把整個表示式作為函式式介面的例項,我們也可以用匿名內部類完後才能同樣的事情,(這段話是書中的原話:自己的理解就是lambda例項化了介面併為唯一的抽象方法提供了實現),比如
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("xx");
    }
};
Runnable runnable1 = () -> System.out.println("xx");
  • 如上兩段程式碼,一個是匿名內部類實現的,一個是lambda提供實現,那麼都是為Runnable介面中的抽象方法run提供了邏輯實現即System.out.println("xx")

函式描述符

  • 抽象方法的方法簽名基本就是lambda的表示式簽名,這種就叫做函式描述符
  • 就如上面程式碼Runnable,run方法的方法簽名是void無返回並且無引數,那麼對應的lambda就是無引數和無返回的void
  • 例項
public static Callable<String> fe(){
    return () -> "sd";
}
  • 上面的lambda是否是正確的可以通過編譯呢?我們來呼叫一下

  1. static void main(String[] args) throws Exception {

    Callable<String> fe = fe();
    System.out.println(fe.call());   //sd

    }

  • 證明了上面的是正確的,我們來一步步的分析為什麼它編譯是正確的,首先觀察Callable的定義

  1. interface Callable {

    V call() throws Exception;

    }

  • 首先它是一個函式是藉口,那麼返回一個lambda作為介面的實現肯定是沒有問題的
  • 其次我們Callable的泛型是String,並且此泛型作為了抽象方法的返回值,方法的返回值也就是String,到這就是看出Callable的lambda的描述資訊是() -> String
  • 我們再看return () -> “sd”; 這個lambda的返回值也是String,與上面我們分析的一致,所以編譯無誤
  • 我們來一個錯誤的

    Predicate<Apple> predicate = (Apple apple) -> apple.getWeight();
  • 為什麼她是錯誤的,我們還的看Predicate的抽象方法的定義

  1. test(T t)

  • 她是返回boolean,但是我們的 apple.getWeight();返回int,這並匹配不上,所以是編譯有錯的

小實戰

  • 內容要求,讀取文字,讓你返回幾行你就返回幾行
  • 觀察要求,返回行的話便捷操作就使用到了BufferReader讀取類的readLine,然後讓返回隨機行,那麼這個肯定是根據意思隨機返回了,很明顯這裡需要一個自定義函式式介面,以實現行為引數化
@FunctionalInterface
interface MyLineReader {
    String process(BufferedReader reader) throws IOException;
}
public class A{
    private String readLine(BufferedReader reader,MyLineReader lineReaderInterface) throws IOException {
        return lineReaderInterface.process(reader);
    }
    @Test
    public void test() throws Exception {
        BufferedReader reader = new BufferedReader(new FileReader("zookeeper.log"));
        String s = readLine(reader, readerIn -> readerIn.readLine() + "
" + readerIn.readLine());
        System.out.println(s);
        reader.close();
    }
}
  • 如上定義函式式介面,然後建立方法,該方法引數一個是流,一個是函式式介面
  • 然後傳入流和利用lambda實現抽象方法的實現邏輯,這裡我們想讀幾行出來就可以了

這裡我們還是有囉嗦的地方,那麼就是定義介面!每次我們實現一個lambda的不同的邏輯,那麼就必須定義一個新介面,xxxoooo,其實java8已經為我們準備好了一些已經寫好的介面,我們拿來用就好了,如果以後變成找不到合適的已經預設實現的介面,那麼我們還是的自己定義介面。emmmm

  • 介面全部都是java.util.funcation包下,這些介面我會在文末列出來的,如果寫到這,那麼就又懵逼了
  • 下面我們先來看內建函式介面Predicate
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    ...
    ...
}
  • 上面這個貨不就是我們們編寫的Filter嘛,拿過來比較一下
interface Filter<T>{
    boolean filter(T t);
}
  • 除了缺註解缺public,類名方法名不一樣,其他的都一樣好吧!!雖然這麼多不一樣,但是使用方法是一樣的啊!我們把方法引數Filter改為Predicate就好了,這種介面的定義為傳入T返回boolean:(T t) -> boolean;這樣的介面有人叫斷言型介面,不過我感覺稱為判斷介面更容易理解,傳入一個T引數,根據自己邏輯判斷出true和false
  • 試用一下
  Predicate<Integer> predicate = (Integer i) -> i > 3;
  System.out.println(predicate.test(4)); //true
  System.out.println(predicate.test(1)); //false
  • 下面是Consumer內建函式介面
@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
  ...
  ...
}
  • 只要理解了上面的Predicate,那麼這個很容易理解了,函式描述符為:(T t) -> void,不返回任何東西,那麼這個介面就是肉包子打狗,T就是肉包子,傳入後進行邏輯執行完畢後,是不反回東西的
  • 試用一下
  Consumer<Integer> consumer = (Integer i) -> System.out.println(i);
  consumer.accept(1);    //1
  consumer.accept(2);    //2
  • 下面是Supplier函式介面
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
  • 這個理解也不難,就是不給他東西,但是他會造東西給你,函式描述符為:() - T
  • 試用一下
  Supplier<Integer> supplier = () -> new Integer(new Random().nextInt());
  System.out.println(supplier.get()); //-1853289089
  System.out.println(supplier.get()); //829915024
  • 內建函式Function
@FunctionalInterface
public interface Function<T, R> {
  R apply(T t);
  ...
  ...
}
  • 傳入一個T,返回一個R,你就可以用它傳入一個apple,返回apple的顏色或者是重量,當讓你也可以返回apple,泛型的限定並沒有限制你必須返回什麼東西
  • 試用一下
  Function<String,Integer> function = (String str) ->  Integer.parseInt(str);
  System.out.println(function.apply("123"));   //123
  public class A{
      @Test
      public void test() throws Exception {
          Function<A,A> function = (A a) -> a;
          A a = new A();
          A apply = function.apply(a);
          System.out.println(a == apply); //true
      }
  }
  • 好了到這算是瞭解了基本的函式式介面,那我們平常如果需要傳入兩個引數,返回一個東西,但是上面的也沒有類似的泛型規定的函式式介面啊,這時候我們應該是去java.util.function包下找對應泛型的介面使用,如果沒有就需要自己定義,定義方法跟上面定義Filter一樣,只是泛型多加一個就好了,對於我們先在的要求是有內建的函式式介面的,比如BiFunction
@FunctionalInterface
public interface BiFunction<T, U, R> {
  R apply(T t, U u);
  ....
}
  • 傳入T和U兩個引數,返回R
  • 到這我們基本是能應對各種的泛型要求的函式式介面了,但是需要注意一個問題,那就是拆箱和裝箱的問題,我們知道基本型別是要比它對應的包裝型別的佔用空間要小的,如下

    • 會裝箱
    Predicate<Integer> predicate = (Integer i) -> i == 3;
    
    • 不會裝箱
    IntPredicate intPredicate = (int i) -> i == 3;
    • 所以一般來說針對專門的輸入引數型別的函式式介面的名稱都要加上對應的原始型別字首,比如上面的IntPredicate,IntConsumer,IntFunction等

好了這一篇內容以及內容不少了,下面是總結的java8內建的函式式介面,等下一篇內容繼續詳細瞭解lambda

BiConsumer<T,U>
BiFunction<T,U,R>
BinaryOperator<T>
BiPredicate<T,U>
BooleanSupplier
Consumer<T>
DoubleBinaryOperator
DoubleConsumer
DoubleFunction<R>
DoublePredicate
DoubleSupplier
DoubleToIntFunction
DoubleToLongFunction
DoubleUnaryOperator
Function<T,R>
IntBinaryOperator
IntConsumer
IntFunction<R>
IntPredicate
IntSupplier
IntToDoubleFunction
IntToLongFunction
IntUnaryOperator
LongBinaryOperator
LongConsumer
LongFunction<R>
LongPredicate
LongSupplier
LongToDoubleFunction
LongToIntFunction
LongUnaryOperator
ObjDoubleConsumer<T>
ObjIntConsumer<T>
ObjLongConsumer<T>
Predicate<T>
Supplier<T>
ToDoubleBiFunction<T,U>
ToDoubleFunction<T>
ToIntBiFunction<T,U>
ToIntFunction<T>
ToLongBiFunction<T,U>
ToLongFunction<T>
UnaryOperator<T>


相關文章