一、反射 1. 反射機制 反射機制的相關類除了一個java.lang.Class,其餘都在java.lang.reflect包下。反射機制用於讀取class位元組碼文件,需要註意,JVM載入位元組碼到記憶體中時都只會保存一份,多次讀取class文件時不用擔心也會載入多次。反射機制相關的常用類: java. ...
一、反射
1. 反射機制
反射機制的相關類除了一個java.lang.Class,其餘都在java.lang.reflect包下。
反射機制用於讀取class位元組碼文件,需要註意,JVM載入位元組碼到記憶體中時都只會保存一份,多次讀取class文件時不用擔心也會載入多次。
反射機制相關的常用類:
- java.lang.Class:代表整個類的位元組碼,表示一個類型。
- java.lang.reflect.Method:代表位元組碼中的方法位元組碼,表示一個方法。
- java.lang.reflect.Constructor:代表位元組碼中的構造方法位元組碼,表示一個構造方法。
- java.lang.reflect.Field:代表位元組碼中的屬性位元組碼,表示一個屬性。
2. 反射類位元組碼Class(類/類型)
- 第一種方式:通過Class類的靜態方法forName,例如
Class c1 = Class.forName("java.lang.String");
就表示獲取到了String這個類的class位元組碼,註意,這是Class類下的一個靜態方法,參數需要是完整的包名。另外,Class.forName方法的使用會導致類的載入,也就是說如果希望只是執行一個類的靜態代碼塊,並不執行其他的代碼,就可以使用這個方法來進行類的載入,此時,自然就會去執行對應的靜態代碼了。 - 第二種方式:通過Object類的getClass方法,例如
String s = "abc"; Class c2 = s.getClass();
,即通過任何類對象的getClass方法就可以拿到對應了類位元組碼了,並且因為JVM只會在記憶體中載入一份相同類的位元組碼,所以這個例子的c2和第一種方式的c1使用雙等號判斷返回結果是true。 - 第三種方式:通過類的class屬性,例如
Class c3 = String.class;
Class中常用的方法:
- String getName():返回類的完整類名(包含包路徑)。
- String getsimpleName():返回類的簡類名(類定義名稱)。
- Field[] getFields():獲取Class中所有public類型的Field對象(屬性)。
- Field[] getDeclaredFields():獲取Class中所有的Field對象(屬性)。
- Field getDeclaredField(String name):獲取指定名稱的Field對象(屬性)。
- Method[] getDeclaredMethods():獲取所有的Method對象(方法)。
- Method getDeclaredMethod(String name, Class<?>... parameterTypes):根據方法名稱和參數類型列表獲取Method對象(方法),例如“userClass.getDeclaredMethod("login", String.class, int.class);”。
- Constructor<?>[] getDeclaredConstructors():獲取所有的Constructor對象(構造方法)。
- Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):獲取指定參數類型列表的Constructor對象(構造方法),例如“userClass.getDeclaredConstructor(String.class, int.class);”。
- Class<? super T> getSuperclass():獲取類的父類。
- Class<?>[] getInterfaces():獲取所有類實現的介面。
3. 反射屬性位元組碼Field(欄位/屬性)
獲取Field需要先獲取到對應的類位元組碼Class,然後才能從類中獲取到對應的Field(java.lang.reflect.Field)。
Field常用方法:
- Class<?> getType():返回屬性的數據類型。
- String getName():返回屬性的名稱。
- int getModifiers():返回修飾符列表的代號,此代號可以使用java.lang.reflect.Modifier的靜態方法toString方法傳入代號獲取到具體的修飾符名稱列表。
- void set(Object obj, Object value):給指定對象的Field對象賦予值value。
- Object get(Object obj):獲取指定對象的Field值(屬性值)。
- void setAccessible(boolean flag):設置為true時表示打破封裝,如果不調用這個方法設置為true的話就沒辦法獲取對象的私有屬性了,調用這個方法之後就可以獲取到所有的屬性了,包括私有屬性。
4. 反射方法位元組碼Method(方法)
獲取Method需要先獲取到對應的類位元組碼Class,然後才能從類中獲取到對應的Method(java.lang.reflect.Method)。
Method中的常用方法:
- Class<?> getReturnType():獲取返回值的數據類型。
- Class<?>[] getParameterTypes():獲取方法的參數類型列表。
- int getModifiers():返回修飾符列表的代號,此代號可以使用java.lang.reflect.Modifier的靜態方法toString方法傳入代號獲取到具體的修飾符名稱列表。
- Object invoke(Object obj, Object... args):調用指定對象的方法。
5. 反射構造方法Constructor(構造方法)
獲取Constructor需要先獲取到對應的類位元組碼Class,然後才能從類中獲取到對應的Method(java.lang.reflect.Constructor)。
Constructor中的常用方法:
- int getModifiers():返回修飾符列表的代號,此代號可以使用java.lang.reflect.Modifier的靜態方法toString方法傳入代號獲取到具體的修飾符名稱列表。
- T newInstance(Object... initargs):使用newInstance方法創建一個實例對象。
二、註解
1. 定義註解(Annotation)
註解,或者稱之為註釋,也是一種引用數據類型,編譯之後也會生成class文件,具體用法見示例:
[修飾符列表] @interface 註解類型名{ // 屬性定義 }
註解定義示例:
// 無屬性的註解定義 public @interface MyAnnotation{ // 這裡面什麼都不寫,表示沒有屬性 } // 有屬性的註解定義 public @interface MyAnnotation2{ // 定義一個沒有預設值的屬性,在使用這個註解的時候就必須給這個屬性傳值 // 註意,註解的屬性定義是有小括弧的,但它不是方法,就只是屬性 String name(); // 使用default給屬性指定預設值,有預設值的屬性在使用時就可以不用給這個屬性傳值了 int id() default 2333; } // 屬性只有一個,且為value時,使用時可以不用指定屬性名稱 public @interface MyAnnotation3{ String value(); }
註解使用示例:
// 使用:直接在類、方法、屬性、形參、註解等上面使用形如”@註解類型名“的格式即可。 // 註解的使用其實就像修飾符一樣在定義的前面加上就可以,但是通常的使用習慣是在定義上一行進行添加 public class AnnotationTest { public static void main(String[] args) { } // 相當於:@MyAnnotation private int id; @MyAnnotation private int id; // 註解只有一個屬性,且屬性名為value時,可以不用指定屬性名 @MyAnnotation3("hello") public AnnotationTest() { } // 定義了沒有預設值的屬性的註解,就必須給這個屬性傳值,有預設值的屬性可以傳,也可以不傳 @MyAnnotation2(name = "zhangsan") public static void func() { @MyAnnotation2(name = "lisi", id = 666) int i = 10; } // 相當於:@MyAnnotation public void func2(@MyAnnotation String name) @MyAnnotation public void func2(@MyAnnotation String name) { System.out.println(name); } }
註解屬性類型:定義註解的屬性時,屬性的類型可以是byte、short、int、long、float、double、boolean、char、String、Class、枚舉類型,以及這幾種類型的數組形式,不能是其他的類型。有一個小技巧,屬性如果是數組,並且使用時傳入的數組元素只有一個的話,定義數組的大括弧是可以不寫的。
2. Java內置註解
內置註解在java.lang包下,常用的有:
- @Override:這個註解只能註解方法,並且只是給編譯器在編譯階段做參考用的,和運行階段的代碼沒有關係,編譯器在編譯時會檢查這個方法是否是重寫的父類方法,如果不是則會報錯。
- @Deprecated:表示被標註的類、方法等元素已經過時了,不建議使用。在IDEA中,被標註的方法等會出現一條橫線,提示你這個方法已過時。
3. 元註解
用來標註“註解類型”的註解,即註解的註解,稱之為元註解,在java.lang.annotation包下。常用的元註解有:
- @Target(ANNOTATION_TYPE):用來指定被標註的註解可以出現在哪些位置上,參數為枚舉類型ElementType的數組,具體有哪些枚舉值可以參考幫助文檔,如“@Target(ElementType.METHOD)”表示被標註的註解只能出現在方法上。
- @Retention(RUNTIME):用來指定被標註的註解最終保存在哪裡,參數是一個枚舉類型RetentionPolicy,有三個枚舉值,“@Retention(RetentionPolicy.SOURCE)”表示被標註的註解保存在java源文件中,並不會出現在編譯之後的class文件中,“@Retention(RetentionPolicy.CLASS)”表示被標註的註解保存在class文件中,“@Retention(RetentionPolicy.RUNTIME)”表示被標註的註解保存在class文件中,並且可以被反射機制讀取出來。
4. 反射註解
以類的註解為例,首先獲取到類的位元組碼對象後,使用Class對象的方法進行反射,常用的方法有:
- boolean isAnnotationPresent(MyAnnotation.class):判斷一個類是否有指定的註解,這裡需要傳入一個指定註解的類位元組碼對象。
- getAnnotation(MyAnnotation.class):獲取類的指定註解對象,這裡需要傳入一個指定註解的類位元組碼對象。
屬性獲取:通過反射拿到註解對象之後,就可以通過調用方法(其實是屬性)的形式獲取屬性值,因為註解定義屬性時,本身就自帶小括弧,所以看起來就是在調用方法了,如“String name = annotationObj.name();”
註:方法、屬性等的註解獲取也是和上面的方法一樣,而且調用的方法等大多也都是一樣的。
5. 註解的作用
註解通常是通過反射機制去檢查被註解的類、方法等是否滿足要求,比如@Override就是檢查被註解的方法是否是重寫父類的方法,如果不是就會編譯報錯。