JDK8 Lambda表達式和匿名內部類

 

前言

Java Lambda表達式的一個重要用法是簡化某些匿名內部類Anonymous Classes)的寫法。實際上Lambda表達式並不單單是匿名內部類的語法糖,JVM內部是經過invokedynamic指令來實現Lambda表達式的。具體原理放到下一篇。本篇咱們首先感覺一下使用Lambda表達式帶來的便利之處。java

取代某些匿名內部類

本節將介紹如何使用Lambda表達式簡化匿名內部類的書寫,但Lambda表達式並不能取代全部的匿名內部類,只能用來取代函數接口(Functional Interface)的簡寫。先別在意細節,看幾個例子再說。ide

例子1:無參函數的簡寫

若是須要新建一個線程,一種常見的寫法是這樣:函數

// JDK7 匿名內部類寫法
new Thread(new Runnable(){// 接口名
    @Override
    public void run(){// 方法名
        System.out.println("Thread run()");
    }
}).start();

上述代碼給Tread類傳遞了一個匿名的Runnable對象,重載Runnable接口的run()方法來實現相應邏輯。這是JDK7以及以前的常見寫法。匿名內部類省去了爲類起名字的煩惱,但仍是不夠簡化,在Java 8中能夠簡化爲以下形式:spa

// JDK8 Lambda表達式寫法
new Thread(
        () -> System.out.println("Thread run()")// 省略接口名和方法名
).start();

上述代碼跟匿名內部類的做用是同樣的,但比匿名內部類更進一步。這裏連接口名和函數名都一同省掉了,寫起來更加神清氣爽。若是函數體有多行,能夠用大括號括起來,就像這樣:線程

// JDK8 Lambda表達式代碼塊寫法
new Thread(
        () -> {
            System.out.print("Hello");
            System.out.println(" Hoolee");
        }
).start();

例子2:帶參函數的簡寫

若是要給一個字符串列表經過自定義比較器,按照字符串長度進行排序,Java 7的書寫形式以下:code

// JDK7 匿名內部類寫法
List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list, new Comparator<String>(){// 接口名
    @Override
    public int compare(String s1, String s2){// 方法名
        if(s1 == null)
            return -1;
        if(s2 == null)
            return 1;
        return s1.length()-s2.length();
    }
});

上述代碼經過內部類重載了Comparator接口的compare()方法,實現比較邏輯。採用Lambda表達式可簡寫以下:對象

// JDK8 Lambda表達式寫法
List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list, (s1, s2) ->{// 省略參數表的類型
    if(s1 == null)
        return -1;
    if(s2 == null)
        return 1;
    return s1.length()-s2.length();
});

上述代碼跟匿名內部類的做用是同樣的。除了省略了接口名和方法名,代碼中把參數表的類型也省略了。這得益於javac類型推斷機制,編譯器可以根據上下文信息推斷出參數的類型,固然也有推斷失敗的時候,這時就須要手動指明參數類型了。注意,Java是強類型語言,每一個變量和對象都必需有明確的類型。排序

簡寫的依據

也許你已經想到了,可以使用Lambda的依據是必須有相應的函數接口(函數接口,是指內部只有一個抽象方法的接口)。這一點跟Java是強類型語言吻合,也就是說你並不能在代碼的任何地方任性的寫Lambda表達式。實際上Lambda的類型就是對應函數接口的類型Lambda表達式另外一個依據是類型推斷機制,在上下文信息足夠的狀況下,編譯器能夠推斷出參數表的類型,而不須要顯式指名。Lambda表達更多合法的書寫形式以下:接口

// Lambda表達式的書寫形式
Runnable run = () -> System.out.println("Hello World");// 1
ActionListener listener = event -> System.out.println("button clicked");// 2
Runnable multiLine = () -> {// 3 代碼塊
    System.out.print("Hello");
    System.out.println(" Hoolee");
};
BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4
BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5 類型推斷

上述代碼中,1展現了無參函數的簡寫;2處展現了有參函數的簡寫,以及類型推斷機制;3是代碼塊的寫法;4和5再次展現了類型推斷機制。ci

自定義函數接口

自定義函數接口很容易,只須要編寫一個只有一個抽象方法的接口便可。

// 自定義函數接口
@FunctionalInterface
public interface ConsumerInterface<T>{
    void accept(T t);
}

上面代碼中的@FunctionalInterface是可選的,但加上該標註編譯器會幫你檢查接口是否符合函數接口規範。就像加入@Override標註會檢查是否重載了函數同樣

有了上述接口定義,就能夠寫出相似以下的代碼:

ConsumerInterface<String> consumer = str -> System.out.println(str);

進一步的,還能夠這樣使用:

class MyStream<T>{
    private List<T> list;
    ...
    public void myForEach(ConsumerInterface<T> consumer){// 1
        for(T t : list){
            consumer.accept(t);
        }
    }
}
MyStream<String> stream = new MyStream<String>();
stream.myForEach(str -> System.out.println(str));// 使用自定義函數接口書寫Lambda表達式
相關文章
相關標籤/搜索