首頁 > 軟體

Java中的反射,列舉及lambda表示式的使用詳解

2022-03-04 19:01:24

一、反射

1.1 定義

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

1.2 用途

1、在日常的第三方應用開發過程中,經常會遇到某個類的某個成員變數、方法或是屬性是私有的或是只對系統應用開放,這時候就可以利用Java的反射機制通過反射來獲取所需的私有成員或是方法 。

2、反射最重要的用途就是開發各種通用框架,比如在spring中,我們將所有的類Bean交給spring容器管理,無論是XML設定Bean還是註解設定,當我們從容器中獲取Bean來依賴注入時,容器會讀取設定,而設定中給的就是類的資訊,spring根據這些資訊,需要建立哪些Beanspring就動態的建立這些類。

1.3 反射基本資訊

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

1.4 與反射相關的類

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

1.5 Class類(反射機制的起源 )

Class代表類的實體,在執行的Java應用程式中表示類和介面 .

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

1.6 Class類中的相關方法

常用獲得類相關的方法:

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

常用獲得類中屬性相關的方法(以下方法返回值為Field相關)

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

獲得類中註解相關的方法

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

獲得類中構造器相關的方法(以下方法返回值為Constructor相關)

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

1.7 獲得Class物件的三種方式

在反射之前,我們需要做的第一步就是先拿到當前需要反射的類的Class物件,然後通過Class物件的核心方法,達到反射的目的,即:在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法對於任意一個物件,都能夠呼叫它的任意方法和屬性,既然能拿到,那麼我們就可以修改部分型別資訊。

第一種,使用 Class.forName("類的全路徑名"); 靜態方法。

前提:已明確類的全路徑名。

第二種,使用 .class 方法。

說明:僅適合在編譯前就已經明確要操作的 Class

第三種,使用類物件的 getClass() 方法。

程式碼範例:

本節程式碼均在一個包下面。

package reflectTest;
class Student{
    //私有屬性name
    private String name = "bit";
    //公有屬性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 eat");
    }
    public void sleep(){
        System.out.println("i am pig");
    }
    private void function(String str) {
        System.out.println(str);
    } @
            Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}
public class test01 {
    public static void main(String[] args) {
        try {
            //通過 Class 物件的 forName() 靜態方法來獲取,用的最多,
            //但可能丟擲 ClassNotFoundException 異常
            Class<?> c1 = Class.forName("reflectTest.Student");
            //直接通過 類名.class 的方式得到,該方法最為安全可靠,程式效能更高
            //這說明任何一個類都有一個隱含的靜態成員變數 class
            Class<?> c2 = Student.class;
            //通過getClass獲取Class物件
            Student student = new Student();
            Class<?> c3 = student.getClass();

            System.out.println(c1.equals(c2));
            System.out.println(c1.equals(c3));
            System.out.println(c2.equals(c3));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

輸出結果:

1.8 反射的使用

package reflectTest;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
 * 通過class類的newInstance方法獲取類的範例
 */
public class ReflectClassDemo {
   public static void reflectNewInstance(){
       try {
           //獲得Class物件
           Class<?> c1 = Class.forName("reflectTest.Student");
           //建立類的範例
           Student student = (Student) c1.newInstance();
           System.out.println(student);
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       }
   }
    /**
     * 反射私有的構造方法
     */
    public static void reflectPrivateConstructor() {
        try {
            Class<?> c1 = Class.forName("reflectTest.Student");
            //構造方法
            Constructor<?> constructor =  c1.getDeclaredConstructor(String.class,int.class);
            //設定為true後可修改存取許可權
            constructor.setAccessible(true);
            Student student = (Student) constructor.newInstance("world",18);
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 反射私有屬性
     */
    public static void reflectPrivateField() {
        try {
            Class<?> c1 = Class.forName("reflectTest.Student");
            Student student = (Student) c1.newInstance();
            Field field =  c1.getDeclaredField("name");
            field.setAccessible(true);
            field.set(student,"zhang");
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    // 反射私有方法
    public static void reflectPrivateMethod() {
        try {
            Class<?> c1 = Class.forName("reflectTest.Student");
            Student student = (Student) c1.newInstance();
           Method method = c1.getDeclaredMethod("function",String.class);
           method.setAccessible(true);
           method.invoke(student,"我是私有的方法的引數");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
//        reflectNewInstance();
//        reflectPrivateConstructor();
//        reflectPrivateField();
        reflectPrivateMethod();
    }
}

1.9 反射優點和缺點

優點

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

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

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

缺點

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

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

二、列舉

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

public static int final RED = 1;
public static int final GREEN = 2;
public static int final BLACK = 3;

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

程式碼範例:

package enumTest;
public enum test01 {
    RED,BLACK,GREEN,WHITE;
    public static void main(String[] args) {
//        System.out.println(test01.BLACK);
//        System.out.println(BLACK);
        test01 te = test01.BLACK;
        switch (te) {
            case RED:
                System.out.println("red");
                break;
            case BLACK:
                System.out.println("black");
                break;
            case WHITE:
                System.out.println("white");
                break;
            case GREEN:
                System.out.println("green");
                break;
            default:
                break;
        }
    }
}

輸出結果:

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

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

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

2.1 Enum 類的常用方法

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

values()程式碼範例 :

public enum test01 {
    RED,BLACK,GREEN,WHITE;
    public static void main(String[] args) {
        test01[] te = test01.values();
        for (int i = 0; i < te.length; i++) {
            System.out.println(te[i]);
        }
     }
  }

輸出結果:

ordinal() 程式碼範例:

public enum test01 {
    RED,BLACK,GREEN,WHITE;
    public static void main(String[] args) {
        test01[] te = test01.values();
        for (int i = 0; i < te.length; i++) {
            System.out.println(te[i] + " --> " + te[i].ordinal());
        }
     }
  }

輸出結果:

valueOf() 、compareTo() 程式碼範例

public enum test01 {
    RED,BLACK,GREEN,WHITE;

    public static void main(String[] args) {
        //把字串變成對應的列舉物件
        test01 te = test01.valueOf("RED");
        System.out.println(te);
        System.out.println(RED.compareTo(GREEN));//-2
        System.out.println(BLACK.compareTo(RED));//1
    }
  }

輸出結果:

列舉的構造方法預設是私有的

public enum test01 {
    //列舉物件
    RED("red",1),BLACK(),GREEN(),WHITE();
    public String color;
    public int ordinal;
//private 加或者不加其都是私有的
   test01(String color, int ordinal) {
        this.color = color;
        this.ordinal = ordinal;
    }
    //無參構造
    test01(){
    }
}

2.2 列舉的優點和缺點

優點

1.列舉常數更簡單安全 。

2.列舉具有內建方法 ,程式碼更優雅。

缺點

1.不可繼承,無法擴充套件。

  • 列舉非常安全,不能通過反射,拿到範例物件。
  • 列舉本身就是一個類,其構造方法預設為私有的,且都是預設繼承於 java.lang.Enum
  • 列舉可以避免反射和序列化問題

三、Lambda 表示式

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

3.1 Lambda表示式的語法及基本使用

基本語法(parameters) -> expression 或 (parameters) ->{ statements; }

Lambda表示式由三部分組成:

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

2.->:可理解為“被用於”的意思

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

// 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)

程式碼範例:

package lambdaTest;
@FunctionalInterface
//函數式介面
interface NoParameterNoReturn {
    //注意:只能有一個方法
    void test();
}
//無返回值一個引數
@FunctionalInterface
interface OneParameterNoReturn {
    void test(int a);
}
//無返回值多個引數
@FunctionalInterface
interface MoreParameterNoReturn {
    void test(int a,int b);
}
//有返回值無引數
@FunctionalInterface
interface NoParameterReturn {
    int test();
}
//有返回值一個引數
@FunctionalInterface
interface OneParameterReturn {
    int test(int a);
}
//有返回值多引數
@FunctionalInterface
interface MoreParameterReturn {
    int test(int a,int b);
}
public class test01 {
    public static void main(String[] args) {
        // {} return 可以省略
        NoParameterReturn noParameterReturn = ()->{return 10;};
        int ret = noParameterReturn.test();
        System.out.println(ret);//10
        //()可以省略
        OneParameterReturn oneParameterReturn = (a) -> a;
        System.out.println(oneParameterReturn.test(10));//10
        MoreParameterReturn moreParameterReturn = (a,b) -> a+b;
        System.out.println(moreParameterReturn.test(1,2));//3
    }
    public static void main3(String[] args) {
        //()  {}  可省略
        OneParameterNoReturn oneParameterNoReturn = (a)-> System.out.println(a);
        oneParameterNoReturn.test(10);//10
        //int型別可以省略
        MoreParameterNoReturn moreParameterNoReturn = (a,b)-> System.out.println(a+b);
        moreParameterNoReturn.test(10,20);//30
    }
    public static void main2(String[] args) {
        NoParameterNoReturn noParameterNoReturn = () -> System.out.println("重寫方法");
        noParameterNoReturn.test();
    }
    public static void main1(String[] args) {
        NoParameterNoReturn noParameterNoReturn =  new NoParameterNoReturn(){
            public void test(){
                System.out.println("重寫方法");
            }
        };
        noParameterNoReturn.test();
    }
}

3.2 函數式介面

函數式介面定義:一個介面有且只有一個抽象方法 。

注意:

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

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

程式碼範例:

@FunctionalInterface
//函數式介面
interface NoParameterNoReturn {
    //注意:只能有一個方法
    void test();
}
public static void main1(String[] args) {
        NoParameterNoReturn noParameterNoReturn =  new NoParameterNoReturn(){
            public void test(){
                System.out.println("重寫方法");
            }
        };
        noParameterNoReturn.test();
    }
}

3.3 變數捕獲

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

package lambdaTest;
class Test {
    public void func(){
        System.out.println("func()");
    }
}
        public class test02 {
    public static void main(String[] args) {
        int a = 100;
        new Test(){
            @Override
            public void func() {
                System.out.println("我是內部類,且重寫了func這個方法!");
                System.out.println("捕獲遍歷" + a);//能捕獲到的變數,要麼是常數,要麼未發生改變過。
            }
        };
    }
}

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

優點

程式碼簡潔,開發迅速;方便函數語言程式設計;非常容易進行平行計算;Java 引入 Lambda,改善了集合操作;

缺點

程式碼可讀性變差;在非平行計算中,很多計算未必有傳統的 for 效能要高;不容易進行偵錯;

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!    


IT145.com E-mail:sddin#qq.com