Java中的反射到底是個啥?

Radish蘿蔔發表於2020-10-08

前言

最近看面試題的時候,看到有關反射的面試,由於上課學的時候老師壓根沒講反射的內容,所以今天又來補漏洞啦

一、反射是什麼

反射到底是個啥?《Java核心技術》書中給出的解釋是:能夠分析類能力的程式稱為反射。反射機制可以用來:

  • 在執行時分析類的能力
  • 在執行時檢視物件,例如,編寫一個toString
  • 實現通用的陣列操作程式碼
  • 利用Method物件,這個物件很像C++中的函式指標

通俗來說,反射就是在執行時才知道要操作的類是什麼,並且可以在執行時獲取類的完整構造,並呼叫對應的方法。

因此,反射是一種功能強大且複雜的機制。使用它的主要人員是工具構造者,而不是應用程式設計師。

二、類物件

  • 類的物件:基於某個類new出來的物件,也稱為例項物件。
  • 類物件:類載入的產物,封裝了一個類的所有資訊(類名、父類、介面、屬性、方法、構造方法)

也就是說,每個類載入到記憶體都對應一個Class物件,每個類有且只有一個Class物件
在這裡插入圖片描述
顯示當前程式所載入的類:JVM引數-verbose:class
在這裡插入圖片描述
在這裡插入圖片描述

三、獲取類物件

3.1 通過類的物件,獲取類物件

/**
 * @author: Radish
 * @date: 2020-10-07 19:10
 */
public class TestPerson {
    public static void main(String[] args) {
        Person person = new Person("張三");
        Class<?> aClass = person.getClass();
        System.out.println(aClass);
    }
}

在這裡插入圖片描述

3.2 通過類名獲取類物件

/**
 * @author: Radish
 * @date: 2020-10-07 19:10
 */
public class TestPerson {
    public static void main(String[] args) {
        System.out.println(Person.class);
    }
}

在這裡插入圖片描述

3.3 通過靜態方法獲取類物件(推薦)

/**
 * @author: Radish
 * @date: 2020-10-07 19:10
 */
public class TestPerson {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("com.reflect.Person");
    }
}

在這裡插入圖片描述
若類名不存在,則會丟擲異常
在這裡插入圖片描述

3.4 常用方法

使用反射獲取類的構造方法,建立物件

//使用反射獲取類的構造方法,建立物件
    public static void reflectOpe2() throws Exception {
        //(1)獲取類物件
        Class<?> aClass = Class.forName("com.reflect.Person");
        //(2)獲取類的構造方法Constructor
        System.out.println("===========獲取類的構造方法============");
        Constructor<?>[] cons = aClass.getConstructors();
        for (Constructor<?> con : cons) {
            System.out.println(con);
        }
        //(3)獲取類中的無參構造
        System.out.println("===========獲取類的無參構造============");
        Constructor<?> con = aClass.getConstructor();
        System.out.println(aClass);
        Person lisi = (Person) con.newInstance();
        System.out.println(lisi);
        //(4)獲取類中帶參構造方法
        System.out.println("===========獲取類的帶參構造============");
        Constructor<?> con1 = aClass.getConstructor(String.class, int.class);
        Person p2 = (Person) con1.newInstance("李四", 23);
        System.out.println(p2);
    }

在這裡插入圖片描述
使用反射獲取類中的方法,並呼叫方法

//3使用反射獲取類中的方法,並呼叫方法
    public static void reflectOpe3() throws Exception {
        //(1)獲取類物件
        Class<?> aClass = Class.forName("com.reflect.Person");
        //(2)獲取方法Method物件
        System.out.println("========獲取方法Method物件========");
        //Method[] methods = aClass.getMethods();  獲取公開的方法,包括從父類繼承的方法
        Method[] methods = aClass.getDeclaredMethods(); //獲取類中的所有方法,包括私有、預設、保護的、不包含繼承的方法
        for (Method method : methods) {
            System.out.println(method);
        }
        //(3)獲取單個方法
        System.out.println("===========獲取單個方法===========");
        System.out.println("----------不帶參----------");
        Method eatMethod = aClass.getMethod("eat");
        //呼叫方法
        //正常呼叫方法 Person person = new Person(); person.eat;
        Person zhangsan = (Person) aClass.newInstance();
        eatMethod.invoke(zhangsan);
        //toString
        Method toStringMethod = aClass.getMethod("toString");
        Object result = toStringMethod.invoke(zhangsan);
        System.out.println(result);
        System.out.println("----------帶參----------");
        Method eatMethod2 = aClass.getMethod("eat", String.class);
        eatMethod2.invoke(zhangsan, "雞腿");
        //(4)獲取私有方法
        System.out.println("===========獲取私有方法===========");
        Method privateMethod = aClass.getDeclaredMethod("privateMethod");
        //設定訪問許可權無效
        privateMethod.setAccessible(true);
        privateMethod.invoke(zhangsan);
        //(5)獲取靜態方法
        System.out.println("===========獲取靜態方法===========");
        Method staticMethod = aClass.getMethod("staticMethod");
        staticMethod.invoke(null);
    }

這裡需要注意幾點:①getMethods方法是獲取公開的方法,包括從父類繼承的方法;getDeclaredMethods方法是獲取類中的所有方法,包括私有、預設、保護的、不包含繼承的方法。②在獲取私有方法時,需設定訪問許可權無效Method setAccessible(true)

使用反射實現一個可以呼叫任何物件方法的通用方法

    //4使用反射實現一個可以呼叫任何物件方法的通用方法
    public static Object invokeAny(Object obj, String methodName, Class<?>[] types,Object...args) throws Exception {
        //1獲取類物件
        Class<?> aClass = obj.getClass();
        //2獲取方法
        Method method = aClass.getMethod(methodName, types);
        //3呼叫
        return method.invoke(obj,args);
    }
    public static void main(String[] args) throws Exception {
        //reflectOpe2();
        //reflectOpe3();
        Properties properties = new Properties();
        invokeAny(properties, "setProperty", new Class[]{String.class,String.class}, "username", "張三");
        System.out.println(properties);
    }

在這裡插入圖片描述

使用反射獲取類的屬性

	//5使用反射獲取類的屬性
    public static void reflectOpe4() throws Exception{
        //(1)獲取類物件
        Class<?> aClass = Class.forName("com.reflect.Person");
        //(2)獲取屬性(欄位) 公開的欄位,父類繼承的欄位
//        Field[] fields = aClass.getFields();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        //(3)獲取name屬性
        Field name = aClass.getDeclaredField("name");
        name.setAccessible(true);
        System.out.println(name);
        //(4)賦值
        Person zhangsan = (Person) aClass.newInstance();
        name.set(zhangsan, "張三");
        //(5)獲取值
        System.out.println(name.get(zhangsan));
    }

在這裡插入圖片描述

四、設計模式

4.1 什麼是設計模式

一套被反覆呼叫、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。

  • 好處:使用設計模式為了可重用程式碼、讓程式碼更容易被他人理、保證程式碼可靠性、重用性。

4.2 工廠設計模式

  • 工廠設計模式主要負責物件建立的問題
  • 開發中有一個非常重要的原則“開閉原則”,對擴充套件開放、對修改關閉。
  • 可通過反射進行工廠模式的設計,完成動態的物件建立。

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
新增實現類時只需修改配置檔案
在這裡插入圖片描述

4.3 單例模式

  • 單例:只允許建立一個該類的物件。

方式1:餓漢式(類載入時建立,天生執行緒安全)

class Singleton {
	private static final Singleton = new Singleton();
	private Singleton();
	public static Singleton getInstance(){
		return instance;
	};
}

方式2:懶漢式(使用時建立,執行緒不安全,加同步,效率低)

class Singleton {
	private static final Singleton = null;
	private Singleton();
	public static synchronized Singleton getInstance(){
		if(instance==null){
			instance = new Singleton();
		}
		return instance;
	};
}

方式3:懶漢式(使用時建立,執行緒安全)

class Singleton {
	private Singleton();
	private static class Holder() {
		static Singleton s = new Singleton();
	}
	public static Singleton instance(){
		return Holder.s;
	};
}

五、列舉

5.1 什麼是列舉

列舉是一個引用型別,列舉是一個規定了取值範圍的資料型別。

  • 列舉變數不能使用其他的資料,只能使用列舉中常量賦值,提高程式安全性。
  • 定義列舉使用enum關鍵字。
  • 列舉的本質:
    • 列舉是一個終止類,並整合enum抽象類
    • 列舉中常量是當前型別的靜態常量

六、註解

6.1 什麼是註解

註解是程式碼裡的特殊標記,程式可以讀取註解,一般用於替代配置檔案。

  • 開發人員可以通過註解告訴類如何執行。

    • 在Java技術裡註解的典型應用是:可以通過反射技術去得到類裡面的註解,以決定怎麼去執行類。
  • 常見註解:@Override、@Deprecated

  • 定義註解使用@interface關鍵字,註解中只能包含屬性

定義一個註解:
在這裡插入圖片描述
使用
在這裡插入圖片描述

6.2 註解屬性型別

  • String型別
  • 基本資料型別
  • Class型別
  • 列舉型別
  • 註解型別
  • 以上型別的一堆陣列

6.3 元註解

  • 元註解:用來描述註解的註解

  • @Retention:用於指定註解可以保留的域。

    • RetentionPolicy.CLASS:註解記錄在class檔案中,執行java程式時,JVM不會保留
    • RetentionPolicy.RUNTIME:註解記錄在class檔案中,執行java程式時,JVM會保留,程式可以通過反射獲取該註釋
    • RetentionPolicy.SOURCE:編譯時直接丟棄這種策略的註釋。
  • @Target:指定註解用於修飾類的哪個成員

相關文章