Java SE 8: Lambda表達式

Lambda表達式

要理解lambda表達式,首先要了解的是函數(shù)式接口(functional interface)。簡單來說,函數(shù)式接口是只包含一個抽象方法的接口。比如Java標(biāo)準(zhǔn)庫中的java.lang.Runnablejava.util.Comparator都是典型的函數(shù)式接口。對于函數(shù)式接口,除了可以使用Java中標(biāo)準(zhǔn)的方法來創(chuàng)建實現(xiàn)對象之外,還可以使用lambda表達式來創(chuàng)建實現(xiàn)對象。這可以在很大程度上簡化代碼的實現(xiàn)。在使用lambda表達式時,只需要提供形式參數(shù)和方法體。由于函數(shù)式接口只有一個抽象方法,所以通過lambda表達式聲明的方法體就肯定是這個唯一的抽象方法的實現(xiàn),而且形式參數(shù)的類型可以根據(jù)方法的類型聲明進行自動推斷。

以Runnable接口為例來進行說明,傳統(tǒng)的創(chuàng)建一個線程并運行的方式如下所示:

public void runThread() {
    new Thread(new Runnable() {
        public void run() {
            System.out.println("Run!");
        }
    }).start();
}

在上面的代碼中,首先需要創(chuàng)建一個匿名內(nèi)部類實現(xiàn)Runnable接口,還需要實現(xiàn)接口中的run方法。如果使用lambda表達式來完成同樣的功能,得到的代碼非常簡潔,如下面所示:

public void runThreadUseLambda() {
    new Thread(() -> {
        System.out.println("Run!");
    }).start();
}

相對于傳統(tǒng)的方式,lambda表達式在兩個方面進行了簡化:首先是Runnable接口的聲明,這可以通過對上下文環(huán)境進行推斷來得出;其次是對run方法的實現(xiàn),因為函數(shù)式接口中只包含一個需要實現(xiàn)的方法。

Lambda表達式的聲明方式比較簡單,由形式參數(shù)和方法體兩部分組成,中間通過->分隔。形式參數(shù)不需要包含類型聲明,可以進行自動推斷。當(dāng)然在某些情況下,形式參數(shù)的類型聲明是不可少的。方法體則可以是簡單的表達式或代碼塊。

Collections.sort(list, (x, y) -> y - x);

在Java SE 8之前的標(biāo)準(zhǔn)庫中包含的函數(shù)式接口并不多。Java SE 8增加了java.util.function包,里面都是可以在開發(fā)中使用的函數(shù)式接口。開發(fā)人員也可以創(chuàng)建新的函數(shù)式接口。最好在接口上使用注解@FunctionalInterface進行聲明,以免團隊的其他人員錯誤地往接口中添加新的方法。
下面的代碼使用函數(shù)式接口java.util.function.Function實現(xiàn)的對列表進行map操作的方法。從代碼中可以看到,如果盡可能的使用函數(shù)式接口,則代碼使用起來會非常簡潔。

public class CollectionUtils {
    public static  List map(List input, Function processor) {
        ArrayList result = new ArrayList();
        for (T obj : input) {
            result.add(processor.apply(obj));
        }
        return result;
    }
    
    public static void main(String[] args) {
        List input = Arrays.asList(new String[] {"apple", "orange", "pear"});
        List lengths = CollectionUtils.map(input, (String v) -> v.length());
        List uppercases = CollectionUtils.map(input, (String v) -> v.toUpperCase());
    }
}

方法和構(gòu)造方法引用

方法引用可以在不調(diào)用某個方法的情況下引用一個方法。構(gòu)造方法引用可以在不創(chuàng)建對象的情況下引用一個構(gòu)造方法。方法引用是另外一種實現(xiàn)函數(shù)式接口的方法。在某些情況下,方法引用可以進一步簡化代碼。比如下面的代碼中,第一個forEach方法調(diào)用使用的是lambda表達式,第二個使用的是方法引用。兩者作用相同,不過使用方法引用的做法更加簡潔。

List input = Arrays.asList(new String[] {"apple", "orange", "pear"});
input.forEach((v) -> System.out.println(v));
input.forEach(System.out::println);

構(gòu)造方法可以通過名稱“new”來進行引用,如下面的代碼所示:

List dateValues = Arrays.asList(new Long[] {0L, 1000L});
List dates = CollectionUtils.map(dateValues, Date::new);

接口的默認方法

Java開發(fā)中所推薦的實踐是面向接口而不是實現(xiàn)來編程。接口作為不同組件之間的契約,使得接口的實現(xiàn)可以不斷地演化。不過接口本身的演化則比較困難。當(dāng)接口發(fā)生變化時,該接口的所有實現(xiàn)類都需要做出相應(yīng)的修改。如果在新版本中對接口進行了修改,會導(dǎo)致早期版本的代碼無法運行。Java對于接口更新的限制過于嚴格。在代碼演化的過程中,一般所遵循的原則是不刪除或修改已有的功能,而是添加新的功能作為替代。已有代碼可以繼續(xù)使用原有的功能,而新的代碼則可以使用新的功能。但是這種更新方式對于接口是不適用的,因為往一個接口中添加新的方法也會導(dǎo)致已有代碼無法運行。
接口的默認方法的主要目標(biāo)之一是解決接口的演化問題。當(dāng)往一個接口中添加新的方法時,可以提供該方法的默認實現(xiàn)。對于已有的接口使用者來說,代碼可以繼續(xù)運行。新的代碼則可以使用該方法,也可以覆寫默認的實現(xiàn)。
考慮下面的一個簡單的進行貨幣轉(zhuǎn)換的接口。該接口的實現(xiàn)方式可能是調(diào)用第三方提供的服務(wù)來完成實際的轉(zhuǎn)換操作。

public interface CurrencyConverter {
    BigDecimal convert(Currency from, Currency to, BigDecimal amount);
}

該接口在開發(fā)出來之后,在應(yīng)用中得到了使用。在后續(xù)的版本更新中,第三方服務(wù)提供了新的批量處理的功能,允許在一次請求中同時轉(zhuǎn)換多個數(shù)值。最直接的做法是在原有的接口中添加一個新的方法來支持批量處理,不過這樣會造成已有的代碼無法運行。而默認方法則可以很好的解決這個問題。使用默認方法的新接口如下所示。

public interface CurrencyConverter {
    BigDecimal convert(Currency from, Currency to, BigDecimal amount);

    default List convert(Currency from, Currency to, List amounts) {
        List result = new ArrayList();
            for (BigDecimal amount : amounts) {
                result.add(convert(from, to, amount));
            }
            return result;
    }
}

新添加的方法使用default關(guān)鍵詞來修飾,并可以有自己的方法體。
默認方法的另外一個作用是實現(xiàn)行為的多繼承。Java語言只允許類之間的單繼承關(guān)系,但是一個類可以實現(xiàn)多個接口。在默認方法引入之后,接口中不僅可以包含變量和方法聲明,還可以包含方法體,也就是行為。通過實現(xiàn)多個接口,一個Java類實際上可以獲得來自不同接口的行為。這種功能類似于JavaScript等其他語言中可見的“混入類”(mixin)。實際上,Java中一直存在“常量接口(Constant Interface)”的用法。常量接口中只包含常量的聲明。通過實現(xiàn)這樣的接口,就可以直接引用這些常量。通過默認方法,可以創(chuàng)建出類似的幫助接口,即接口中包含的都是通過默認方法實現(xiàn)的幫助方法。比如創(chuàng)建一個StringUtils接口包含各種與字符串操作相關(guān)的默認方法。通過繼承該接口就可以直接使用這些方法。
Java SE 8標(biāo)準(zhǔn)庫已經(jīng)使用默認方法來對集合類中的接口進行更新。比如java.util.Collection接口中新增的默認方法removeIf可以刪除集合中滿足某些條件的元素。還有java.lang.Iterable接口中新增的默認方法forEach可以遍歷集合中的元素,并執(zhí)行一些操作。這些新增的默認方法大多使用了java.util.function包中的函數(shù)式接口,因此可以使用lambda表達式來非常簡潔的進行操作。
Lambda表達式是Java SE 8在提高開發(fā)人員生產(chǎn)效率上的一個重大改進。通過語法上的改進,可以減少開發(fā)人員需要編寫和維護的代碼數(shù)量。

列舉

// Java 8之前:
new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("test");
    }
}).start();

//Java 8方式:
new Thread(() -> System.out.println("test0") ).start();
// Java 8之前:
JButton show =  new JButton("Show");
show.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
    System.out.println("test1");
    }
});


// Java 8方式:
show.addActionListener((e) -> {
    System.out.println("test1");
});
// Java 8之前:
List features = Arrays.asList("0", "1", "2", "3");
for (String feature : features) {
    System.out.println(feature);
}


// Java 8之后:
List features = Arrays.asList("0", "1", "2", "3");
features.forEach(n -> System.out.println(n));
 
// 使用Java 8的方法引用更方便,方法引用由::雙冒號操作符標(biāo)示,
// 看起來像C++的作用域解析運算符
features.forEach(System.out::println);

// Java 8之前:
public static void filter(List names, Predicate condition) {
    for(String name: names)  {
        if(condition.test(name)) {
            System.out.println(name + " ");
        }
    }
}


// Java 8之后:
public static void filter(List names, Predicate condition) {
    names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
        System.out.println(name + " ");
    });
}
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
// Java 8之前:
for (Integer cost : costBeforeTax) {
    double price = cost * 2;
    System.out.println(price);
}
 
// Java 8之后:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
costBeforeTax.stream().map((cost) -> cost * 2).forEach(System.out::println);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容