Java中的反射,枚舉及lambda表達式的使用詳解

一、反射

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 性能要高;不容易進行調試;

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!    

推薦閱讀: