Java總結分享之反射、列舉、Lambda表示式

2022-11-22 18:01:16
本篇文章給大家帶來了關於的相關知識,其中主要介紹了關於反射、列舉、lambda表示式的相關內容,包括了反射的概述、使用以及優缺點、自定義構造列舉物件等等內容,下面一起來看一下,希望對大家有幫助。

程式設計師必備介面測試偵錯工具:

推薦學習:《》

一. 反射

1. 反射的概述

  • 什麼是反射

Java的反射(reflection)機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意方法和屬性,既然能拿到那麼,我們就可以修改部分型別資訊;這種動態獲取資訊以及動態呼叫物件方法的功能稱為java語言的反射(reflection)機制。

  • 反射的基本資訊

Java程式中許多物件在執行時會出現兩種型別:執行時型別(RTTI)和編譯時型別,例如Person p = new Student();這句程式碼中p在編譯時型別為Person,執行時型別為Student。程式需要在執行時發現物件和類的真實 信心。而通過使用反射程式就能判斷出該物件和類屬於哪些類。

  • 反射的用途
  1. 在日常的第三方應用開發過程中,經常會遇到某個類的某個成員變數、方法或是屬性是私有的或是隻對系統應用開放,這時候就可以利用Java的反射機制通過反射來獲取所需的私有成員或是方法 。
  2. 反射最重要的用途就是開發各種通用框架,比如在spring中,我們將所有的類Bean交給spring容器管理,無論是XML設定Bean還是註解設定,當我們從容器中獲取Bean來依賴注入時,容器會讀取設定,而設定中給的就是類的資訊,spring根據這些資訊,需要建立那些Bean,spring就動態的建立這些類。

2. 反射的使用

2.1 反射常用的類

類名用途
Class類代表類的實體,在執行的Java應用程式中表示類和介面
Field類代表類的成員變數/類的屬性
Method類代表類的方法
Constructor類代表類的構造方法

Class代表類的實體,在執行的Java應用程式中表示類和介面 ,Java檔案被編譯後,生成了.class檔案,JVM此時就要去載入.class檔案 ,被編譯後的Java檔案,也就是.class檔案會被JVM解析為一個物件,這個物件就是 java.lang.Class 。這樣當程式在執行時,每個java檔案就最終變成了Class類的一個範例。我們通過Java的反射機制應用到這個範例,就可以去獲得甚至去新增改變Class物件所對應類的屬性和動作, 使得這個類成 為一個動態的類 .

2.2 通過反射獲取Class物件

反射獲取物件一共有三種方式:

  • 通過Class類中的通過forName方法。
  • 通過類名.class獲取。
  • 通過使用範例物件呼叫getclass方法獲取。

下面我們演示使用三種方式得到的物件是否是同一個物件,我們來獲取相關Student類的類資訊物件。

Student類定義:

class Student{
    //私有屬性name
    private String name = "rong";
    //公有屬性age
    public int age = 18;
    //不帶引數的構造方法
    public Student(){
        System.out.println("Student()");
    }
    //帶兩個引數的構造方法
    private Student(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Student(String,name)");
    }

    private void eat(){
        System.out.println("i am eating");
    }

    public void sleep(){
        System.out.println("i am sleeping");
    }

    private void function(String str) {
        System.out.println("私有方法function被呼叫:"+str);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }}
登入後複製

獲取對應類的Class物件:

public static void main(String[] args) {
    //有3種方式可以獲取Class物件
    //1.通過物件的getClass()方法
    Student student1 = new Student();
    Class<?> c1 = student1.getClass();
    //2、通過類名.class獲取
    Class<?> c2 = Student.class;
    //3. forName(「路徑」)
    Class<?> c3 = null;
    try {
        c3 = Class.forName("Student");
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
    System.out.println(c1.equals(c2));
    System.out.println(c1.equals(c3));
    System.out.println(c2.equals(c3));}
登入後複製

執行結果:

通過結果發現, 三種方式獲取到的物件是同一個.

img

2.3 獲得Class類相關的方法

方法用途
getClassLoader()獲得類的載入器
getDeclaredClasses()返回一個陣列,陣列中包含該類中所有類和介面類的物件(包括私有的)
forName(String className)根據類名返回類的物件
newInstance()建立類的範例
getName()獲得類的完整路徑名字

2.4 使用反射建立範例物件

首先獲取到Class物件,然後通過Class物件中的newInstance()方法建立範例物件 .

需要注意的是newInstance()方法的返回值的是一個泛型,在編譯階段會被擦除為Object,所以我們在接收的時候需要強制型別轉換 .

public static void main(String[] args) {
    //獲取相關類的Class物件
    Class<?> c = Student.class;
    //使用newInstance方法建立範例
    try {
        //需要進行強轉
        Student student = (Student) c.newInstance();
        System.out.println(student);
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }}
登入後複製

執行結果:

通過反射成功建立了Student類的範例。

img

2.5 使用反射獲取範例物件中的構造方法

方法用途
getConstructor(Class…<?> parameterTypes)獲得該類中與引數型別匹配的公有構造方法
getConstructors()獲得該類的所有公有構造方法
getDeclaredConstructor(Class…<?> parameterTypes)獲得該類中與引數型別匹配的構造方法
getDeclaredConstructors()獲得該類所有構造方法

使用反射獲取範例物件中構造方法然後建立範例物件:

  • 獲取Class物件。

  • 通過上述的方法獲取構造器。

  • 如果獲取的是私有的構造方法,則需要記得通過構造器的setAccessible方法將存取許可權開啟。

  • 呼叫構造器中的newInstance方法獲取物件。

public static void main(String[] args) throws ClassNotFoundException {
    //1.獲取Clas物件
    Class<?> c = Class.forName("Student");
    //2.獲取指定參數列的構造器,演示獲取Student中的一個私有構造器,引數傳形參列表型別
    try {
        Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
        //獲取的私有構造方法,需要開啟存取許可權,預設關閉
        constructor.setAccessible(true);
        //3.根據獲取到的構造器獲取範例物件,使用newInstance方法,需要傳入構造器需要的引數
        Student student = (Student) constructor.newInstance("張三", 20);
        System.out.println(student);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }}
登入後複製

執行結果:

獲取到了私有的構造器,按照所傳引數建立範例物件。

img

2.6 通過反射獲取範例物件的屬性

方法用途
getField(String name)獲得某個公有的屬性物件
getFields()獲得所有公有的屬性物件
getDeclaredField(String name)獲得某個屬性物件
getDeclaredFields()獲得所有屬性物件

通過如下過程修改一個物件的私有屬性:

  • 獲取Class物件。

  • 建立或通過反射範例化一個需要修改其私有欄位的類。

  • 通過屬性名,呼叫上述getDeclaredField方法獲取對應的屬性物件。

  • 通過setAccessible方法設定為存取私有屬性開許可權。

  • 通過Field物件的set方法,修改傳入物件中的對應屬性。

public static void main(String[] args) {
    //1.獲取Class物件
    Class<?> c = Student.class;
    try {
        //2.通過反射建立範例物件
        Student student = (Student) c.newInstance();
        //3.獲取私有屬性name
        Field field =  c.getDeclaredField("name");
        //4.給該私有屬性開許可權
        field.setAccessible(true);
        //5.修改該私有屬性
        field.set(student, "被反射修改的私有屬性");
        System.out.println(student);
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }}
登入後複製

執行結果:

範例物件裡面的私有屬性name被修改了。

img

2.7 通過反射獲取範例物件的方法

方法用途
getMethod(String name, Class…<?> parameterTypes)獲得該類某個公有的方法
getMethods()獲得該類所有公有的方法
getDeclaredMethod(String name, Class…<?> parameterTypes)獲得該類某個方法
getDeclaredMethods()獲得該類所有方法

通過如下過程獲取Student物件中的私有方法function:

  • 獲取相關Student類的Class物件。

  • 建立或通過反射範例化一個Student。

  • 通過class物件獲取到範例物件中的方法物件,引數為方法名,形參型別列表。

  • 為獲取的私有方法開存取許可權。

  • 通過invork方法呼叫方法。

public static void main(String[] args) {
    try {
        //1.獲取Class物件
        Class<?> c = Class.forName("Student");
        //2.獲取Student的一個範例物件
        Student student = (Student) c.newInstance();
        //3.通過class物件獲取範例的方法物件,引數為方法名,以及形參列表
        Method method =  c.getDeclaredMethod("function", String.class);
        //4.為私有方法開存取許可權
        method.setAccessible(true);
        //5.通過invork方法呼叫方法
        method.invoke(student, "傳入私有方法引數");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }}
登入後複製

執行結果:

通過反射可以獲取到範例物件的私有方法並進行呼叫。

img

2.8 獲得類中註解相關的方法

方法用途
getAnnotation(Class annotationClass)返回該類中與引數型別匹配的公有註解物件
getAnnotations()返回該類所有的公有註解物件
getDeclaredAnnotation(Class annotationClass)返回該類中與引數型別匹配的所有註解物件
getDeclaredAnnotations()返回該類所有的註解物件

3. 反射的優缺點

優點

  • 對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法

  • 增加程式的靈活性和擴充套件性,降低耦合性,提高自適應能力

  • 反射已經運用在了很多流行框架如:Struts、Hibernate、Spring 等等。

缺點

  • 使用反射會有效率問題。會導致程式效率降低。

  • 反射技術繞過了原始碼的技術,因而會帶來維護問題。反射程式碼比相應的直接程式碼更復雜 。

二. 列舉

1. 列舉的概述

列舉是在JDK1.5以後引入的; 關鍵字enum可以將一組具名的值的有限集合建立為一種新的型別,而這些具名的值可以作為常規的程式元件使用,這個新的型別就是列舉

主要用途是:將一組常陣列織起來,在這之前表示一組常數通常使用定義常數的方式:

public static int final RED = 1;public static int final GREEN = 2;public static int final BLACK = 3;
登入後複製

但是常數舉例有不好的地方,例如:可能碰巧有個數位1,但是他有可能誤會為是RED,現在我們可以直接用列舉來進行組織,這樣一來,就擁有了型別,列舉型別。而不是普通的整形1.

下面是建立一個Color列舉型別 :

public enum Color {
    RED,BLUE,GREEN,YELLOW,BLACK;}
登入後複製

優點:將常陣列織起來統一進行管理

場景:錯誤狀態碼,訊息型別,顏色的劃分,狀態機等等…

本質是 java.lang.Enum 的子類,也就是說,自己寫的列舉類,就算沒有顯示的繼承 Enum ,但是其預設繼承了這個類

2. 列舉的使用

2.1 switch語句中使用列舉

switch語句中可以使用列舉來提高程式碼的可讀性。

其實enum關鍵字組織的是一個特殊的類,裡面包含一個或多個的列舉物件,下面定義的Color,其實裡面包含了3個列舉物件,每個物件都是Color型別。

enum Color {
    BLACK,YELLOW,GREEN;}public class Test {
    public static void main(String[] args) {
        Color color = Color.YELLOW;
        switch (color) {
            case BLACK:
                System.out.println("BLACK");
                break;
            case YELLOW:
                System.out.println("YELLOW");
                break;
            case GREEN:
                System.out.println("GREEN");
                break;
            default:
                break;
        }
    }}
登入後複製

執行結果:

img

2.2 列舉enum中的常用方法

列舉中常用的方法如下:

方法名稱描述
values()以陣列形式返回列舉型別的所有成員
ordinal()獲取列舉元的索引位置
valueOf()將普通字串轉換為列舉範例
compareTo()比較兩個列舉元在定義時的順序

關於Enum類原始碼中找不到values()方法的解釋:

values方法,在編譯前無法找到,這是因為enum宣告實際上定義了一個類,我們可以通過定義的enum呼叫一些方法,Java編譯器會自動在enum型別中插入一些方法,其中就包括values(),valueOf(),所以我們的程式在沒編譯的時候,就沒辦法檢視到values()方法以及原始碼,這也是列舉的特殊性。

  • 使用values()得到一個含有所有列舉物件的一個陣列
public enum Color {
    BLACK,YELLOW,GREEN;

    public static void main(String[] args) {
        Color[] colors = Color.values();
        for (Color c : colors) {
            System.out.println(c);
        }
    }}
登入後複製

執行結果:

img

  • 使用valueOf()通過一個字串獲取同名列舉:
public enum Color {
    BLACK,YELLOW,GREEN;
    public static void main(String[] args) {
        Color color = Color.valueOf("BLACK");
        System.out.println(color);
    }}
登入後複製

執行結果:

img

  • 使用ordinal()獲取列舉在列舉類中的位置次序,也就是索引:
public enum Color {
    BLACK,YELLOW,GREEN;
    public static void main(String[] args) {
        Color[] colors = Color.values();
        for (Color c : colors) {
            System.out.println(c + "的索引:" + c.ordinal());
        }
    }}
登入後複製

執行結果:

img

  • 使用compareTo() 比較兩個列舉元在定義時的順序:
public enum Color {
    BLACK,YELLOW,GREEN;
    public static void main(String[] args) {
        System.out.println(Color.GREEN.compareTo(Color.YELLOW));
        System.out.println(Color.BLACK.compareTo(Color.YELLOW));
    }}
登入後複製

執行結果:

img

3. 自定義構造列舉物件

上面的例子中enum本質上其實是一個特殊的類,預設繼承了抽象類java.lang.Enum,裡面包含了一個或多個列舉物件,並且這些列舉物件預設情況下都是通過無引數的構造方法構造的,

其實我們可以在列舉類中自定義屬性方法以及構造方法,實現自定義列舉物件.

看下面的寫法, 和上面的例子是一樣的 , 只不過上面的寫法是無參構造省略了 ( )

img

我們可以自己在列舉類中定義一些屬性, 然後去寫含有含有引數的構造方法, 實現自定義列舉;

注意 : 列舉中的構造方法必須(預設)是私有的, 且當我們寫了含有引數的構造方法時, 編譯器不會再提提供無參的構造方法 , 所以此時需要按照我們自己寫的構造方法傳入引數;

public enum Color {
    BLACK("BLACK", 11, 1),
    YELLOW("YELLOW", 12, 2),
    GREEN("GREEN", 13, 3);
    public String colorName;
    public int colorId;
    public int ordonal;

    Color(String colorName, int colorId, int ordonal) {
        this.colorName = colorName;
        this.colorId = colorId;
        this.ordonal = ordonal;
    }

    @Override
    public String toString() {
        return "Color{" +
                "colorName='" + colorName + '\'' +
                ", colorId=" + colorId +
                ", ordonal=" + ordonal +
                '}';
    }

    public static void main(String[] args) {
        Color[] colors = Color.values();
        for (Color c : colors) {
            System.out.println(c);
        }
    }}
登入後複製

執行結果:

img

4. 列舉的安全性

首先看下面的程式碼, 我們想要從外部通過反射獲取到列舉類:

public class Test {
    public static void main(String[] args) {
        //嘗試獲取列舉物件
        Class<?> c = Color.class;
        try {
            //獲取構造方法物件
            Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class, int.class);
            //開許可權
            constructor.setAccessible(true);
            //通過構造方法構造物件
            Color color = (Color) constructor.newInstance("藍色", 88, 2);
            System.out.println(color);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }}
登入後複製

執行結果:

img

結果中丟擲一個java.lang.NoSuchMethodException: Color.<init>(java.lang.String, int, int)異常,表示沒有找到我們給定參數列的構造方法,但是我們列舉類中是定義過這個構造方法的,那麼這裡報錯的原因是什麼呢?

上面說過列舉類是預設繼承抽象類java.lang.Enum的,所以要構造enum需要先幫助父類別完成構造,但是列舉類與一般的類相比比較特殊,它不是使用super關鍵字進行顯示地幫助父類別構造,而是在編譯後會多插入兩個引數來幫助父類別構造,也就是說,我們傳參時要在原本所定義的構造方法參數列基礎上前面再新增String和int型別的兩個引數

所以實際情況下,我們需要在反射獲取構造器時,多寫兩個引數

Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class, String.class, int.class, int.class);
登入後複製

再次執行程式結果如下:

img

可以發現結果還是會丟擲異常,但是此時拋的不是構造方法找不到的異常,而是列舉無法進行反射異常Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects;

所以列舉物件是無法通過反射得到的, 這也就保證了列舉的安全性;

其實列舉無法通過反射獲取到列舉物件是因為在**newInstance****()**中獲取列舉物件時,會過濾掉列舉型別,如果遇到的是列舉型別就會丟擲異常。

img

5. 總結

  • 列舉本身就是一個類,其構造方法預設為私有的,且都是預設繼承與 java.lang.Enum

  • 列舉可以避免反射和序列化問題

  • 列舉實現單例模式是安全的

列舉的優點:

  • 列舉常數更簡單安全
  • 列舉具有內建方法 ,程式碼更優雅

列舉的缺點:

  • 不可繼承,無法擴充套件

三. Lambda表示式

1. 函數式介面

要了解Lambda表示式,首先需要了解什麼是函數式介面,函數式介面定義:一個介面有且只有一個抽象方法 。

注意:

  • 如果一個介面只有一個抽象方法,那麼該介面就是一個函數式介面

  • 如果我們在某個介面上宣告了 @FunctionalInterface註解,那麼編譯器就會按照函數式介面的定義來要求該接 口,這樣如果有兩個抽象方法,程式編譯就會報錯的。所以,從某種意義上來說,只要你保證你的介面中只有一個抽象方法,你可以不加這個註解。加上就會自動進行檢測的。

定義方式

@FunctionalInterfaceinterface NoParameterNoReturn {
	//注意:只能有一個方法
	void test();}
登入後複製

基於jdk1.8, 還以有如下定義:

@FunctionalInterfaceinterface NoParameterNoReturn {
	void test();
	default void test2() {
		System.out.println("JDK1.8新特性,default預設方法可以有具體的實現");
	}}
登入後複製

2. 什麼是Lambda表示式?

Lambda表示式是Java SE 8中一個重要的新特性。lambda表示式允許你通過表示式來代替功能介面。 lambda表達 式就和方法一樣,它提供了一個正常的參數列和一個使用這些引數的主體(body,可以是一個表示式或一個程式碼 塊)。 Lambda 表示式(Lambda expression),基於數學中的λ演算得名,也可稱為閉包(Closure) 。

Lambda表示式的語法:

 (parameters) -> expression 或 (parameters) ->{ statements; }
登入後複製

Lambda表示式由三部分組成

  • paramaters:類似方法中的形參列表,這裡的引數是函數式介面裡的引數。這裡的引數型別可以明確的宣告也可不宣告而由JVM隱含的推斷。另外當只有一個推斷型別時可以省略掉圓括號。

  • ->:可理解為「被用於」的意思

  • 方法體:可以是表示式也可以程式碼塊,是函數式介面裡方法的實現。程式碼塊可返回一個值或者什麼都不反回,這裡的程式碼塊塊等同於方法的方法體。如果是表示式,也可以返回一個值或者什麼都不反回。

常用的lambda表示式格式:

// 1. 不需要引數,返回值為 2() -> 2
    // 2. 接收一個引數(數位型別),返回其2倍的值x -> 2 * x    
// 3. 接受2個引數(數位),並返回他們的和(x, y) -> x + y    
// 4. 接收2個int型整數,返回他們的乘積(int x, int y) -> x * y    
// 5. 接受一個 string 物件,並在控制檯列印,不返回任何值(看起來像是返回void)(String s) -> System.out.print(s)
登入後複製

3. Lambda表示式的基本使用

  • 引數型別可以省略,如果需要省略,每個引數的型別都要省略。

  • 引數的小括號裡面只有一個引數,那麼小括號可以省略

  • 如果方法體當中只有一句程式碼,那麼大括號可以省略

  • 如果方法體中只有一條語句,其是return語句,那麼大括號可以省略,且去掉return關鍵字

以下面這些介面為例:

//無返回值無引數@FunctionalInterfaceinterface NoParameterNoReturn {
    void test();}//無返回值一個引數@FunctionalInterfaceinterface OneParameterNoReturn {
    void test(int a);}//無返回值多個引數@FunctionalInterfaceinterface MoreParameterNoReturn {
    void test(int a,int b);}//有返回值無引數@FunctionalInterfaceinterface NoParameterReturn {
    int test();}//有返回值一個引數@FunctionalInterfaceinterface OneParameterReturn {
    int test(int a);}//有返回值多引數@FunctionalInterfaceinterface MoreParameterReturn {
    int test(int a,int b);}
登入後複製

實現介面最原始的方式就是定義一個類去重寫對應的方法,其次更簡便的方式就是使用匿名內部類去實現介面;

public class TestDemo {
    public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn(){
            @Override
            public void test() {
                System.out.println("hello");
            }
        };
        noParameterNoReturn.test();
    }}
登入後複製

那麼這裡使用lambda表示式, 可以進一步進行簡化;

public class TestDemo {
    public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn = ()->{
            System.out.println("無引數無返回值");
        };
        noParameterNoReturn.test();
        
        OneParameterNoReturn oneParameterNoReturn = (int a)->{
            System.out.println("一個引數無返回值:"+ a);
        };
        oneParameterNoReturn.test(10);
        
        MoreParameterNoReturn moreParameterNoReturn = (int a,int b)->{
            System.out.println("多個引數無返回值:"+a+" "+b);
        };
        moreParameterNoReturn.test(20,30);
        
        NoParameterReturn noParameterReturn = ()->{
            System.out.println("有返回值無引數!");
            return 40;
        };
        //接收函數的返回值
        int ret = noParameterReturn.test();
        System.out.println(ret);
        
        OneParameterReturn oneParameterReturn = (int a)->{System.out.println("有返回值有一個引數!");
            return a;
        };
        ret = oneParameterReturn.test(50);
        System.out.println(ret);
        
        MoreParameterReturn moreParameterReturn = (int a,int b)->{
            System.out.println("有返回值多個引數!");
            return a+b;
        };
        ret = moreParameterReturn.test(60,70);
        System.out.println(ret);
    }}
登入後複製

上面的的程式碼根據開頭的省略規則還可以進一步省略, 如下:

public class TestDemo {
    public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn                = ()->System.out.println("無引數無返回值");
        noParameterNoReturn.test();

        OneParameterNoReturn oneParameterNoReturn                = a-> System.out.println("一個引數無返回值:"+ a);
        oneParameterNoReturn.test(10);

        MoreParameterNoReturn moreParameterNoReturn                = (a,b)-> System.out.println("多個引數無返回值:"+a+" "+b);
        moreParameterNoReturn.test(20,30);

        //有返回值無引數!
        NoParameterReturn noParameterReturn = ()->40;
        int ret = noParameterReturn.test();
        System.out.println(ret);

        //有返回值有一個引數!
        OneParameterReturn oneParameterReturn = a->a;
        ret = oneParameterReturn.test(50);
        System.out.println(ret);

        //有返回值多個引數!
        MoreParameterReturn moreParameterReturn = (a,b)->a+b;
        ret = moreParameterReturn.test(60,70);
        System.out.println(ret);
    }}
登入後複製

還有一種寫法更加簡潔, 但可讀性就… , 比如:

OneParameterNoReturn oneParameterNoReturn = a-> System.out.println(a);
登入後複製

可以簡化成下面的樣子, 看不太懂了…

OneParameterNoReturn oneParameterNoReturn = System.out::println;
登入後複製

4. 變數捕獲

Lambda 表示式中存在變數捕獲 ,瞭解了變數捕獲之後,我們才能更好的理解Lambda 表示式的作用域 。

在匿名內部類中,只能捕獲到常數,或者沒有發生修改的變數,因為lambda本質也是實現函數式介面,所以lambda也滿足此變數捕獲的規則。

下面的程式碼捕獲的變數num未修改, 程式可以正常編譯和執行;

img

當捕獲的變數num是修改過的, 則會報錯;

img

5. Lambda在集合當中的使用

5.1 Collection介面中的forEach方法

注意:Collection的forEach()方 法是從介面 java.lang.Iterable 拿過來的。

forEach方法需要傳遞的引數是Consumer<? super E> action,這個引數也是一個函數式介面,需要重寫裡面的accept方法。

img

使用匿名內部類,accept中的t參數列示集合中迭代出的元素,我們可以對該元素設定操作, 這裡重寫的方法只做輸出操作;

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("欣");
    list.add("欣");
    list.add("向");
    list.add("榮");
    list.forEach(new Consumer<String>(){
        @Override
        public void accept(String str){
            //簡單遍歷集合中的元素。
            System.out.print(str+" ");
        }
    });}
登入後複製

執行結果:

img

我們可以將上面的匿名內部類使用lambda表示,它只有一個引數沒有返回值,上面的程式碼變為

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("欣");
    list.add("欣");
    list.add("向");
    list.add("榮");
    list.forEach(s -> System.out.print(s + " "));}
登入後複製

5.2 Map中forEach方法

map中的forEach方法和前面Collection中的forEach方法的使用其實都差不多,換了一個引數而已,這個引數BiConsumer<? super K, ? super V> action同樣是一個函數式介面,我們需要傳入一個實現該介面的實現類。

img

使用匿名內部類:

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "欣");
    map.put(2, "欣");
    map.put(3, "向");
    map.put(4, "榮");
    map.forEach(new BiConsumer<Integer, String>(){
        @Override
        public void accept(Integer k, String v){
            System.out.println(k + "=" + v);
        }
    });}
登入後複製

執行結果:

img

同樣的對上面程式碼可以使用lambda表示式來實現,這是一個含有兩個引數無返回值的函數式介面,上面的程式碼改為:

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "欣");
    map.put(2, "欣");
    map.put(3, "向");
    map.put(4, "榮");
    map.forEach((k,v)-> System.out.println(k + "=" + v));}
登入後複製

5.3 大部分介面中的sort方法

大部分介面中的sort方法,預設都是按照升序的方式進行排序,如果需要對自定義類進行排序或者實現自定義規則的排序,需要額外傳入一個Comparator的實現類物件(比較器) ; 這裡以List集合中的sort方法為例 .

img

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("aaaa");
    list.add("bbb");
    list.add("cc");
    list.add("d");
    list.sort(new Comparator<String>() {
        @Override
        public int compare(String str1, String str2){
            //注意這裡比較的是長度
            return str1.length()-str2.length();
        }
    });
    System.out.println(list);}
登入後複製

執行結果:

img

同樣的對上面程式碼可以使用lambda表示式來實現,這是一個含有兩個引數有返回值的函數式介面,上面的程式碼改為:

 public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("aaaa");
    list.add("bbb");
    list.add("cc");
    list.add("d");
    //呼叫帶有2個引數的方法,且返回長度的差值
    list.sort((str1,str2)-> str1.length()-str2.length());
    System.out.println(list);}
登入後複製

6. 總結

Lambda表示式的優點很明顯,在程式碼層次上來說,使程式碼變得非常的簡潔。缺點也很明顯,程式碼不易讀。

優點

  • 程式碼簡潔,開發迅速

  • 方便函數語言程式設計

  • 非常容易進行平行計算

  • Java 引入 Lambda,改善了集合操作

缺點

  • 程式碼可讀性變差

  • 在非平行計算中,很多計算未必有傳統的 for 效能要高

  • 不容易進行偵錯

推薦學習:《》

以上就是Java總結分享之反射、列舉、Lambda表示式的詳細內容,更多請關注TW511.COM其它相關文章!