Java Lambda表達式

函數式接口

函數式接口(Functional Interface)就是一個只有一個抽象方法(能夠包含多個默認方法或多個static方法)的普通接口,能夠被隱式轉換爲lambda表達式,能夠現有的函數友好地支持 lambda。java

函數式接口:算法

  • java.lang.Runnable數組

  • java.util.concurrent.Callableapp

  • java.security.PrivilegedAction函數

  • java.util.Comparatorcode

  • java.io.FileFilter對象

  • java.nio.file.PathMatcher繼承

  • java.lang.reflect.InvocationHandler索引

  • java.beans.PropertyChangeListener接口

  • java.awt.event.ActionListener

  • javax.swing.event.ChangeListener

  • java.util.function

Lambda表達式入門

匿名內部類:

public class CommandTest2 
{
    public static void main(String[] args) 
    {
        ProcessArray pa = new ProcessArray();
        int[] target = {3, -4, 6, 4};
        //處理數組,具體處理行爲取決於匿名內部類
        pa.process(target, new Command() 
        {
            public void process(int[] target) 
            {
                int sum = 0;
                for(int tmp :target)
                {
                    sum += tmp;
                }
                System.out.println("數組元素的總和是:"+ sum);
            }
        });
    }
}

Lambda表達式:

public class CommandTest3 
{
    public static void main(String[] args) 
    {
        ProcessArray pa = new ProcessArray();
        int[] array = {3, -4, 6, 4};
        //處理數組,具體處理行爲取決於匿名內部類
        pa.process(array, (int[] target)->{
            int sum = 0;
            for(int tmp : target)
            {
                sum += tmp;
            }
            System.out.println("數組元素的總和是:"+ sum);
        });
    }
}

當使用Lambda表達式代替匿名內部類建立對象時,Lambda表達式的代碼塊將會代替實現抽象方法的方法體,Lambda表達式就至關一個匿名方法。

Lambda表達式的主要做用就是代替匿名內部類的煩瑣語法。它由三部分組成。

  • 形參列表。形參列表容許省略形參類型。若是形參列表只有一個參數,甚至連形參列表的圓括號也能夠省略。

  • 箭頭(->)。必須經過英文中畫線號和大於符號組成。

  • 代碼塊。若是代碼塊只包含一條語句,Lambda表達式容許省略代碼塊的花括號,那麼這條語句就不用用花括號表達語句結束。Lambda代碼塊只有一條return語句,甚至能夠省略return關鍵字。Lambda表達式須要返回值,而它的代碼塊中僅有一條省略了return的語句,Lambda表達式會自動返回這條語句的值。

interface Eatable
{
    void test();
}

interface Flyable
{
    void fly(String weather);
}
interface Addable
{
    int add(int a, int b);
}
public class LambdaQs 
{
    //調用該方法須要Eatable對象
    public void eat(Eatable e) 
    {
        System.out.println(e);
        e.test();
    }
    //調用該方法須要Flyable對象
    public void drive(Flyable f) 
    {
        System.out.println("老司機正在開:"+f);
        f.fly("亮瞎眼");
    }
    //調用該方法須要Addable對象
    public void test(Addable add) 
    {
        System.out.println("34與59的和爲:"+add.add(34, 59));
    }
    public static void main(String[] args) 
    {
        LambdaQs lq = new LambdaQs();
        //Lamba表達式的代碼塊只有一條語句,能夠省略花括號
        lq.eat(()->System.out.println("蘋果很贊哦!"));
        //Lamba表達式的形參列表只有一個形參,能夠省略圓括號
        lq.drive(weather ->
        {
            System.out.println("今每天氣是:"+weather);
            System.out.println("直升機飛行平穩");
        });
        //Lambda表達式的代碼塊只有一條語句,能夠省略花括號
        //代碼塊中只有一條語句,即便該表達式須要返回值,也能夠省略return關鍵字
        lq.test((a, b) -> a + b);
    }
}

Lambda表達式與函數式接口

Lambda表達式的類型,也被稱爲「目標類型」,Lambda表達式的目標類型必須是「函數式接口」。函數式接口表明只包含一個抽象方法的接口。函數式接口能夠包含多個默認方法、類方法,但只能聲明一個抽象方法

若是採用匿名內部類語法來建立函數式接口的實例,則只須要實現一個抽象方法,在這種狀況下便可採用Lambda表達式來建立對象,該表達式建立處理的對象的目標類型就是這個函數式接口。查詢Java8的API文檔,能夠發現大量的函數式接口,例如:Runnable、ActionListener等接口都是函數式接口

@FunctionalInterface註解,該註解一般放在接口定義前面,該註解對程序功能沒有任何做用,它用於告訴編譯器執行更嚴格檢查——檢查該接口必須是函數式接口,不然編譯器就會報錯

Lambda表達式實現的是匿名方法——所以它只能實現特定函數式接口中的惟一方法。這意味着Lambda表達式有以下兩個限制:

  • Lambda表達式的目標類型必須是明確的函數式接口

  • Lambda表達式只能爲函數式接口建立對象。Lambda表達式只能實現一個方法,所以它只能爲只有一個抽象方法的接口(函數式接口)建立對象

爲了保證Lambda表達式的目標類型是一個明確的函數式接口,能夠有以下三種常見方式:

  • 將Lambda表達式賦值給函數式接口類型的變量

  • 將Lambda表達式做爲函數式接口類型的參數傳給某個方法

  • 使用函數式接口對Lambda表達式進行強制類型轉換

一樣的Lambda表達式的目標類型徹底多是變化的——惟一的要求是,Lambda表達式實現的匿名方法與目標類型(函數式接口)中惟一的抽象方法有相同的形參列表。

Java8在java.util.function包下預約義了大量函數式接口,典型地包含以下4類接口。

  • XxxFunction:這類接口中一般包含一個apply()抽象方法,該方法對參數進行處理、轉換(apply()方法的處理邏輯由Lambda表達式來實現),而後返回一個新的值。該函數式接口一般用於對指定數據進行轉換處理。

  • XxxConsumer:這類接口中一般包含一個accept()抽象方法,該方法與XxxFunction接口中的apply()方法基本類似,也負責對參數進行處理,只是該方法不會返回處理結果。

  • XxxxPredicate:這類接口中一般包含一個test()抽象方法,該方法一般用來對參數進行某種判斷(test()方法的判斷邏輯由Lambda表達式來實現),而後返回一個boolean值。該接口一般用於判斷參數是否知足特定條件,常常用於進行篩濾數據。

  • XxxSupplier:這類接口中一般包含一個getAsXxx()抽象方法,該方法不須要輸入參數,該方法會按某種邏輯算法(getAsXxx()方法的邏輯算法由Lambda表達式來實現)返回一個數據。

方法引用與構造器引用

若是Lambda表達式的代碼塊只有一條代碼,還能夠在代碼塊中使用方法引用和構造器引用。

方法引用和構造器引用可讓Lambda表達式的代碼塊更加簡潔。方法引用和構造器引用都須要使用兩個英文冒號。

種類 示例 說明 對應的Lambda表達式
引用類方法 類名::類方法 函數式接口中被實現方法的所有參數傳給該類方法做爲參數 (a,b,...)->類名.類方法(a,b,...)
引用特定對象的實例方法 特定對象::實例方法 函數式接口中被實現方法的所有參數傳給該類方法做爲參數 (a,b,...)->特定對象.實例方法(a,b,...)
引用某類對象的實例方法 類名::實例方法 函數式接口中被實現方法的第一個參數做爲調用者,後面的參數所有傳給該方法做爲參數 (a,b,...)->a.實例方法(b,...)
引用構造器 類名::new 函數式接口中被實現方法的所有參數傳給該類方法做爲參數 (a,b,...)->new 類名(a,b,...)

引用類方法

@FunctionalInterface
interface Converter{
    Integer convert(String from);
}

該函數式接口包含一個convert()抽象方法,該方法負責將String參數轉換成Integer。下面代碼使用Lambda表達式來建立Converter對象。

//下面代碼使用Lambda表達式建立Converter對象
Converter converter1 = from -> Integer.valueOf(from);

上面Lambda表達式的代碼塊只有一條語句,所以程序省略了該代碼塊的花括號;並且因爲表達式所實現的convert()方法須要返回值,所以Lambda表達式將會把這條代碼的值做爲返回值
調用convert1對象的convert()方法將字符串轉換爲整數了,例如以下代碼

Integer val = converter1.convert("99");
System.out.println(val); // 輸出整數99

上面Lambda表達式的代碼塊只有一行調用類方法的代碼,所以可使用以下方法引用進行替換

// 方法引用代替Lambda表達式:引用類方法。
// 函數式接口中被實現方法的所有參數傳給該類方法做爲參數。
Converter converter1 = Integer::valueOf;

對於上面的類方法引用,也就是調用Integer類的valueOf()類方法來實現Converter函數式接口中惟一的抽象方法,當調用Converter接口中惟一的抽象方法時,調用參數將會傳給Integer類的valueOf()類方法。

引用特定對象的實例方法

// 下面代碼使用Lambda表達式建立Converter對象
Converter converter2 = from -> "fkit.org".indexOf(from);

Integer value = converter2.convert("it");
System.out.println(value); // 輸出2
// 方法引用代替Lambda表達式:引用特定對象的實例方法。
// 函數式接口中被實現方法的所有參數傳給該方法做爲參數。
Converter converter2 = "fkit.org"::indexOf;

對於上面的實例方法引用,也就是調用"fkit.org"對象的indexOf()實例方法來實現Converter函數式接口中惟一的抽象方法,當調用Converter接口中惟一的抽象方法時,調用參數將會傳給"fkit.org"對象的indexOf()實例方法。

引用某類對象的實例方法

定義以下函數式接口

@FunctionalInterface
interface MyTest
{
    String test(String a , int b , int c);
}

使用Lambda表達式來建立一個MyTest對象

MyTest mt = (a , b , c) -> a.substring(b , c);
String str = mt.test("Java I Love you" , 2 , 9);
System.out.println(str); // 輸出:va I Lo
// 方法引用代替Lambda表達式:引用某類對象的實例方法。
// 函數式接口中被實現方法的第一個參數做爲調用者,
// 後面的參數所有傳給該方法做爲參數。
MyTest mt = String::substring;

引用構造器

@FunctionalInterface
interface YourTest
{
    JFrame win(String title);
}

下面代碼使用Lambda表達式建立YourTest對象

YourTest yt = (String a) -> new JFrame(a);
JFrame jf = yt.win("個人窗口");
System.out.println(jf);
// 構造器引用代替Lambda表達式。
// 函數式接口中被實現方法的所有參數傳給該構造器做爲參數。
YourTest yt = JFrame::new;

對於上面的構造器引用,也就是調用某個JFrame類的構造器來實現YourTest函數式接口中惟一的抽象方法,當調用YourTest接口中的惟一的抽象方法時,調用參數將會傳給JFrame構造器。調用YourTest對象的win()抽象方法時,實際只傳入了一個String類型的參數,這個String類型的參數會被傳給JFrame構造器——這就肯定了調用JFrame類的、帶一個String參數的構造器。

Lambda表達式與匿名內部類的聯繫和區別

Lambda表達式是匿名內部類的一種簡化,存在以下相同點

  • Lambda表達式與匿名內部類同樣,均可以直接訪問「effectively final」的局部變量,以及外部類的成員變量(包括實例變量和類變量)。

  • Lambda表達式建立的對象與匿名內部類生成的對象同樣,均可以直接調用從接口中繼承的默認方法。

@FunctionalInterface
interface Displayable
{
    //定義一個抽象方法和默認方法
    void display();
    default int add(int a, int b)
    {
        return a + b;
    }
}
public class LambdaAndInner 
{
    private int age = 24;
    private static String name = "簡單點,說話的方式簡單點" ;
    public void test() 
    {
        String sing = "演員";
        Displayable dis = () -> {
            //訪問「effectively final」的局部變量
            System.out.println("sing局部變量爲:"+ sing);
            //訪問外部類的實例變量和類變量
            System.out.println("外部類的age實例變量爲:"+ age);
            System.out.println("外部類的name類變量爲:"+ name);
        };
        dis.display();
        //調用dis對象從接口中繼承的add()方法
        System.out.println(dis.add(34, 59));
    }
    public static void main(String[] args) 
    {
        LambdaAndInner lambdaAndInner = new LambdaAndInner();
        lambdaAndInner.test();
    }
}

上面Lambda表達式建立了一個Display的對象,Lambda表達式的代碼塊中的三行粗體字代碼分別示範了訪問「effectively fianl」的局部變量、外部類的實例變量和類變量。從這點來看,Lambda表達式的代碼塊與匿名內部類的方法體是相同的。
與匿名內部類類似的是,因爲Lambda表達式訪問了sing局部變量,該局部變量至關於與一個隱式的final修飾,所以不容許對sing局部變量從新賦值。

Lambda表達式與匿名內部類主要存在以下區別

  • 匿名內部類能夠爲任意接口建立實例——無論接口包含多少個抽象方法,只有匿名內部類實現全部的抽象方法便可;但Lambda表達式只能爲函數式接口建立實例。

  • 匿名內部類能夠爲抽象類甚至普通類建立實例;但Lambda表達式只能爲函數式接口建立實例。

  • 匿名內部類實現的抽象方法的方法體容許調用接口中定義的默認方法;但Lambda表達式的代碼塊不容許調用默認方法。

使用Lambda表達式調用Arrays的類方法

Arrays類的有些方法須要Comparator、XxxOperator、XxxFunction等接口的實例,這些接口都是函數式接口,所以可使用Lambda表達式來調用Arrays的方法

import java.util.Arrays;

import javax.management.openmbean.OpenDataException;

public class LambdaArrays 
{
    public static void main(String[] args) 
    {
        String arr1[] = new String[]{"皇家馬德里", "巴塞羅那", "巴黎聖日耳曼","尤文圖斯","切爾西"};
        Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
        System.out.println(Arrays.toString(arr1));
        int[] arr2 = new int[]{4, 2, 1, 3, 5};
        //left表明數組中前一個索引處的元素,計算第一個元素時,left爲1
        //right表明數組中當前索引處的元素
        Arrays.parallelPrefix(arr2, (left, right) -> left * right);
        System.out.println(Arrays.toString(arr2));
        long[] arr3 = new long[5];
        //operand表明正在計算的元素索引
        Arrays.parallelSetAll(arr3, operand -> operand * 5);
        System.out.println(Arrays.toString(arr3));
    }
}
  • (o1, o2) -> o1.length() - o2.length():目標類型是Comparator指定了判斷字符串大小的標準:字符串越長,便可認爲該字符串越大

  • (left, right) -> left * right:目標類型是IntBinaryOperator,該對象將會根據先後兩個元素來計算當前元素的值

  • operand -> operand * 5::目標類型是IntToLongFunction,該對象將會根據元素的索引來計算當前元素的值

Lambda表達式可讓程序更加簡潔。

相關文章
相關標籤/搜索