作用
反射的定義:在運行狀態中,能獲取任意一個類的所有方法和屬性;能調用一個對象的所有方法和屬性。這種動態獲取類信息和動態調用對象方法和屬性的功能就是Java的反射機制。
注意定義中的措辭,是所有的方法和屬性,即使是私有的也能調用。所以功能是非常強大的。但在我們日常開發中很少會用到反射機制,因為正是這種強大的機制反而會破壞我們應用代碼的封裝性。日常中不用不代表就沒用,很多框架的設計其實都用到了反射機制。如果熟悉Spring的話不難發現,Spring中有大量關於反射的應用。比如我們在配置XML時就會被要求寫對應類的全限定名,在Spring的BeanFactor中就會讀取這些配置文件,並通過反射去創建Class對象。連接數據庫時,會要求指定驅動程序,比如com.mysql.jdbc.Driver,其實也通過反射機制加載的。
原理
在此之前先要明白,在Java中萬物皆對象,某個類本身可以是個對象,類里面的結構,比如屬性、方法等也可以是個對象。在JVM進行類加載的過程中,在第一步也就是加載步驟,會在內存中生成一個代表該類的Class對象。需要注意這個Class對象和該類的實例對象不是同一個東西。Class對象的類型就是Class,大家可以看看Class的API或者直接看它的源碼。我們使用Class.forName("類的全限定名")時其實就是獲取這個類的Class對象。具體的API可以看下面。通過這個Class對象我們就可以獲取這個類的屬性、方法、注解等信息。那么是如何獲取這些信息的?首先Class這個類提供了獲取這些信息的方法,既然提供了獲取的方法,那說明這些信息肯定存在於某些地方。還是得從JVM層面來說,一個類的靜態成員,即靜態方法和屬性都是存在方法區的,這些成員在類加載時就生成了,所以它們與該類的實例對象沒有什么關系,只與這個類本身也就是Class對象相關。而那些非靜態成員會隨着實例對象的創建一起存進Java堆當中,所以我們獲取這些非靜態成員時,一定是根據某個實例對象來獲取的。這兩種情況會在下面的示例代碼中展現出來。
API
下面的API摘自:https://www.jianshu.com/p/9be58ee20dee,感興趣的可以去看看。
與Java反射相關的類如下:
類名 |
用途 |
Class類 |
代表類的實體,在運行的Java應用程序中表示類和接口 |
Field類 |
代表類的成員變量(成員變量也稱為類的屬性) |
Method類 |
代表類的方法 |
Constructor類 |
代表類的構造方法 |
Class類
Class代表類的實體,在運行的Java應用程序中表示類和接口。在這個類中提供了很多有用的方法,這里對他們簡單的分類介紹。
- 獲得類相關的方法
方法 |
用途 |
asSubclass(Class<U> clazz) |
把傳遞的類的對象轉換成代表其子類的對象 |
Cast |
把對象轉換成代表類或是接口的對象 |
getClassLoader() |
獲得類的加載器 |
getClasses() |
返回一個數組,數組中包含該類中所有公共類和接口類的對象 |
getDeclaredClasses() |
返回一個數組,數組中包含該類中所有類和接口類的對象 |
forName(String className) |
根據類名返回類的對象 |
getName() |
獲得類的完整路徑名字 |
newInstance() |
創建類的實例 |
getPackage() |
獲得類的包 |
getSimpleName() |
獲得類的名字 |
getSuperclass() |
獲得當前類繼承的父類的名字 |
getInterfaces() |
獲得當前類實現的類或是接口 |
- 獲得類中屬性相關的方法
方法 |
用途 |
getField(String name) |
獲得某個公有的屬性對象 |
getFields() |
獲得所有公有的屬性對象 |
getDeclaredField(String name) |
獲得某個屬性對象 |
getDeclaredFields() |
獲得所有屬性對象 |
- 獲得類中注解相關的方法
方法 |
用途 |
getAnnotation(Class<A> annotationClass) |
返回該類中與參數類型匹配的公有注解對象 |
getAnnotations() |
返回該類所有的公有注解對象 |
getDeclaredAnnotation(Class<A> annotationClass) |
返回該類中與參數類型匹配的所有注解對象 |
getDeclaredAnnotations() |
返回該類所有的注解對象 |
- 獲得類中構造器相關的方法
方法 |
用途 |
getConstructor(Class...<?> parameterTypes) |
獲得該類中與參數類型匹配的公有構造方法 |
getConstructors() |
獲得該類的所有公有構造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) |
獲得該類中與參數類型匹配的構造方法 |
getDeclaredConstructors() |
獲得該類所有構造方法 |
- 獲得類中方法相關的方法
方法 |
用途 |
getMethod(String name, Class...<?> parameterTypes) |
獲得該類某個公有的方法 |
getMethods() |
獲得該類所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) |
獲得該類某個方法 |
getDeclaredMethods() |
獲得該類所有方法 |
- 類中其他重要的方法
方法 |
用途 |
isAnnotation() |
如果是注解類型則返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) |
如果是指定類型注解類型則返回true |
isAnonymousClass() |
如果是匿名類則返回true |
isArray() |
如果是一個數組類則返回true |
isEnum() |
如果是枚舉類則返回true |
isInstance(Object obj) |
如果obj是該類的實例則返回true |
isInterface() |
如果是接口類則返回true |
isLocalClass() |
如果是局部類則返回true |
isMemberClass() |
如果是內部類則返回true |
Field類
Field代表類的成員變量(成員變量也稱為類的屬性)。
方法 |
用途 |
equals(Object obj) |
屬性與obj相等則返回true |
get(Object obj) |
獲得obj中對應的屬性值 |
set(Object obj, Object value) |
設置obj中對應屬性值 |
Method類
Method代表類的方法。
方法 |
用途 |
invoke(Object obj, Object... args) |
傳遞object對象及參數調用該對象對應的方法 |
Constructor類
Constructor代表類的構造方法。
方法 |
用途 |
newInstance(Object... initargs) |
根據傳遞的參數創建類的對象 |
示例
首先創建一個被反射的類:
public class People { //靜態屬性 public static String Version="1.1.1"; private String Name; private Integer Age; private String Habit; //公共構造函數 public People(String name, Integer age, String habit) { Name = name; Age = age; Habit = habit; } //私有構造函數 private People(String name){ Name=name; } public People(){ } //私有方法 private void isPrivate(String data){ System.out.println("這是個私有方法,傳入的參數為:"+data); } //公有方法 public String getName() { return Name; } //靜態方法 public static void StaticVoid(){ System.out.println("這是一個靜態方法"); } }
接下來通過Java提供的反射方法獲取這個類的構造函數、方法、屬性:
public class reflectionTest { public static void main(String[] args) { try { System.out.println("----------------------獲取Class對象-----------------------"); //因為我這里People和reflectionTest在同一包下,如果不在同一包下需要傳入完整的類路徑 Class clazz = Class.forName("People"); System.out.println(clazz); /** * 還有兩種獲取Class對象的方法,這幾種方法獲取的Class對象都是同一個對象: * 一般都會用foeName的方式,因為下面第一種方法需要導包 * 第二種方法,對象都創建出來了,除非是要調用私有的東西,那我還反射個毛。 * 1.Class clazz=People.class; * 2.People p=new People(); * Class clazz=p.getClass(); * */ System.out.println("----------------------獲取構造函數對象-----------------------"); //獲取所有構造函數,不包括私有的,如果想包括私有就用getDeclaredConstructors Constructor[] constructors = clazz.getConstructors(); for (Constructor c : constructors) { System.out.println(c); } //獲取私有構造函數並調用(String.class為構造函數參數類型) Constructor constructor = clazz.getDeclaredConstructor(String.class); //忽略訪問修飾符,如果是共有的就不需要這一步 constructor.setAccessible(true); //根據這個構造函數創建一個People實例對象 People people = (People) constructor.newInstance("張三"); //最后通過對象輸出剛剛設置的姓名 System.out.println(people.getName()); System.out.println("----------------------獲取方法對象-----------------------"); //獲取所有公共方法,包括父類的方法,比如Object的equals,如果想包含私有方法就用getDeclaredMethods,但只有本類的方法 Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println(method); } //獲取一個私有方法並調用 Method method = clazz.getDeclaredMethod("isPrivate", String.class); //忽略訪問修飾符 method.setAccessible(true); //訪問私有方法,如果這個私有方法有返回值用一個變量接收就行了。 //通過invoke調用該私有方法,除了要傳入這個方法本來的參數,比如這里的耶low,還需要一個該方法對應類的對象,我們就用上面反射獲取的people對象 method.invoke(people, "耶low"); //獲取靜態方法並調用,發現調用靜態方法不需要實例對象,這也符合原理中所說的。 Method method2=clazz.getMethod("StaticVoid"); method2.invoke(null); System.out.println("----------------------獲取屬性對象-----------------------"); //獲取本類的所有屬性,包括私有的 Field[] fields=clazz.getDeclaredFields(); for(Field field:fields){ System.out.println(field); } //獲取私有屬性並調用 Field field=clazz.getDeclaredField("Name"); field.setAccessible(true); //設置這個私有屬性 field.set(people,"李四"); //然后獲取一下Name,看看設置成功沒 System.out.println(people.getName()); //獲取靜態屬性 Field field2=clazz.getField("Version"); //輸出靜態屬性的值 System.out.println(field2.get(clazz)); } catch (Exception e) { e.printStackTrace(); } } }