文章目錄
- 1.注解 java.Annotation
-
- 1.1 什麼是注解
- 1.2 内置注解
- 1.3 元注解
- 1.4 自定義注解
- 2. 反射 java.Reflection
-
- 2.1 反射
- 2.2 得到Class類的方式
- 2.3 所有類型的Class對象
- 2.4 擴充:類的加載和ClassLoader
-
- 2.4.1 類初始化
- 2.5 建立運作時類的對象
-
- 2.5.1 擷取運作時類的完整結構
- 2.5.2 動态建立對象、執行方法
-
- 2.5.2.1 建立類的對象
- 2.5.2.2 執行方法
- 2.6 性能對比分析
- 2.7 擴充:反射操作泛型
- 2.8 反射操作注解
1.注解 java.Annotation
1.1 什麼是注解
- 注解的作用:
- 不是程式本身,可以對程式作出解釋
- 可以被其他程式(如編譯器)讀取
- 注解的格式:“@注解名”,還可以添加一些參數值。如@Scheduled(cron=” “)
- 注解可以附加在包、類、方法等上面,相當于給他們添加了額外的輔助資訊
1.2 内置注解
- @Override:表示打算重寫父類中的一個方法
- @Deprecated:表示不推薦使用,但可以使用,通常是因為很危險或者存在更好的選擇
- @SuppressWarnings:抑制編譯器的警告資訊,需要加參數。
@SuppressWarnings("all") @SuppressWarnings("unchecked")
1.3 元注解
- 元注解的作用是負責注釋其他注解
- 四個元注解:
注解 作用 @Target 描述注解的使用範圍 @Retention 表示需要在什麼級别儲存該注釋資訊,描述注解的生命周期(source<class<runtime) @Documented 表示該注解将被包含在javadoc中 @Inherited 子類可以繼承父類中的該注解 - @Target:
這裡使用@Target(value= ElementType.METHOD)表示自己定義的注解MyAnnotation僅在方法上生效,此時若把注解放在類前面就會報錯。public class Test1{ @MyAnnotation public void test(){ } } // value的值也可以是一個數組 @Target(value= ElementType.METHOD) @interface MyAnnotation{ }
- @Retention
@Retention(value = RetentionPolicy.RUNTIME) // 表示該自定義的注解運作時有效 @interface MyAnnotation{ }
- @Target:
1.4 自定義注解
- 首先使用@interface自定義注解
@interface MyAnnotation2{ }
- 使用元注解定義自定義注解的使用範圍和生命周期
@Target(value = {ElementType.METHOD,ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME)
- 給注解定義一些參數
@interface MyAnnotation2{ // 注解的參數:參數的類型 + 參數名 + () String name() default " "; // default為預設值 int age() default 0; int id() default -1; // 預設值為-1,代表不存在 String[] schools() default {"NJU"}; }
- 在方法上使用注解
public class Test2 { // 給注解顯示指派 @MyAnnotation2(name="java",schools = {"南京大學"}) public void test(){ } }
- 注意如果注解僅有一個參數,且參數的名字是value時,那麼使用注解時不用顯示的去指派,可以類似這樣去指派:@SuppressWarnings(“all”)
2. 反射 java.Reflection
2.1 反射
- 反射機制允許程式在執行期間借助Reflection API取得任何類的内部資訊,并能直接操作任意對象的内部屬性和方法。
- 反射可以通過getClass方法和一個類的執行個體化對象得到完整的類的資訊,簡單來說就是可以通過一個對象得到類
- 反射機制提供的功能
- 在運作時判斷任意一個對象所屬的類
- 在運作時構造任意一個類的對象
- 在運作時判斷任意一個類所具有的成員變量和方法
- 在運作時擷取泛型資訊
- 在運作時調用任意一個對象的成員變量和方法
- 在運作時處理注解
- 生成動态代理
- ……
- 對性能有一定的影響
- 通過代碼
列印出來class reflection.user,其中user是自定義的實體類,含有id和name兩個屬性// 通過反射擷取類的class對象 Class c = Class.forName("reflection.user"); System.out.println(c);
- 通過代碼
列印出來的三個數值都是一樣的,表示一個類在記憶體中隻有一個class對象。另外,一個類被加載後,類的整個結構都會被封裝在class對象中。Class c2 = Class.forName("reflection.user"); Class c3 = Class.forName("reflection.user"); Class c4 = Class.forName("reflection.user"); System.out.println(c2.hashCode()); System.out.println(c3.hashCode()); System.out.println(c4.hashCode());
2.2 得到Class類的方式
- 若已知具體的類(User),通過類的class屬性擷取
- 已知某個類的執行個體(user),調用該執行個體的getClass()方法擷取Class對象
- 已知一個類的全類名,且該類在類路徑下,可通過Class類的靜态方法forName()擷取
- 内置基本資料類型可以直接用類名.Type
- 利用ClassLoader
實際例子:
package reflection;
public class Test extends Object{
public static void main(String[] args) throws ClassNotFoundException {
User user=new Students();
System.out.println(user.getName());
// 1:通過對象執行個體獲得
Class c1=user.getClass();
System.out.println(c1.hashCode());
// 2:forName獲得
Class c2 = Class.forName("reflection.Students");
System.out.println(c2.hashCode());
// 3:通過類名.class獲得
Class c3=Students.class;
System.out.println(c3.hashCode());
}
}
class User {
private String name;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
}
public User(String name) {
this.name = name;
}
}
class Students extends User{
public Students(){
setName("學生");
}
}
class Teachers extends User{
public Teachers(){
setName("老師");
}
}
main方法的輸出是:
學生
1554874502
1554874502
1554874502
可見一個類在記憶體中隻有一個class對象
2.3 所有類型的Class對象
哪些類型可以有Class對象:(隻要元素類型和次元是一樣的,就是同一個Class)
- 所有類型的類
- interface接口
- 數組(注意一個類隻有一個記憶體對象,不同長度的數組的hashcode依舊是一樣的。隻要元素類型和次元是一樣的,就是同一個Class)
- enum枚舉
- 注解
- 基本資料類型
- void
實際例子:
Class c1 = Object.class; //類
Class c2 = Input.class; //接口
Class c3 = int[].class; // 一維數組
Class c4 = int[][].class; // 二維數組
Class c5 = Override.class; //注解
Class c6 = ElementType.class; //枚舉
Class c7 = void.class; //void
Class c8 = Integer.class; //基本資料類型
Class c9 = Class.class; //Class
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
輸出為:
class java.lang.Object
interface com.sun.corba.se.spi.orbutil.fsm.Input
class [I
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
void
class java.lang.Integer
class java.lang.Class
2.4 擴充:類的加載和ClassLoader
三個階段:
- 加載: 将class檔案位元組碼内容加載到記憶體裡,并将這些靜态資料轉換成方法區的運作時資料結構,然後生成一個代表這個類的java.lang.Class對象
- 連結: 将Java類的二進制代碼合并到JVM的運作狀态之中
- 初始化:
- 執行類構造器clinit
- 當初始化一個類的時候,如果發現其父類沒有進行初始化,則先初始化父類
2.4.1 類初始化
什麼時候類會初始化:
- 類的主動引用(一定會發生類的初始化)
- 虛拟機啟動時,先初始化main方法所在的類
- new一個類的對象
- 調用類的靜态成員(除了final常量)和靜态方法
- 使用java.lang.reflect包的方法對類進行反射調用
- 當初始化一個類的時候,如果發現其父類沒有進行初始化,則先初始化父類
- 類的被動引用(不會發生類的初始化)
- 通路一個靜态域時,隻有真正聲明這個域的類才會被初始化。(通過子類引用父類的靜态變量,不會導緻子類初始化,隻會初始化父類)如調用Son.a,不會觸發Son類的初始化,因為a是Father類的靜态變量
- 通過數組定義類引用,不會觸發此類的初始化,如:User[] array=new User[5],不會觸發User類的初始化
- 引用常量不會觸發此類的初始化,常量在連結階段就存入調用類的常量池中了
2.5 建立運作時類的對象
本節内容用到的User類為
class User {
private String name;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
}
public User(String name) {
this.name = name;
}
}
2.5.1 擷取運作時類的完整結構
- 實作的全部接口
- 繼承的父類
- 全部的構造器
- 全部方法
- 全部Field
- 注解
- ……
執行個體:
- 獲得類的名字:包名+類名
- 獲得類的簡單名字:類名
- 獲得類的所有屬性
Field[] fieldsPublic=c1.getFields(); //找到所有public的屬性 Field[] fieldsAll=c1.getDeclaredFields(); // 找到所有屬性 for (int i = fieldsAll.length - 1; i >= 0; i--) { System.out.println(fieldsAll[i]); }
- 獲得指定屬性
// 獲得指定屬性,所有類型的都可以獲得 Field name=c1.getDeclaredField("name"); // 隻能獲得public的指定屬性 Field name=c1.getField("name");
- 獲得類的方法
// 獲得本類及其父類的所有public方法 Method[] methods=c1.getMethods(); for (Method method : methods) { System.out.println(method); } // 獲得本類的所有方法 methods=c1.getDeclaredMethods(); for (Method method : methods) { System.out.println(method); }
- 獲得指定方法,第二個參數代表該方法有沒有參數,有參數的話要填入參數類型(之是以需要填入參數類型,是因為java中包含重載機制,加入參數類型才可以區分)
Method method=c1.getMethod("getName",null); System.out.println(method); Method method1=c1.getMethod("setName",String.class); System.out.println(method1);
- 獲得構造器
// 獲得所有的public構造方法 Constructor[] constructors=c1.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } System.out.println(); // 獲得所有的構造方法 constructors=c1.getDeclaredConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); }
- 獲得指定的構造器
Constructor constructor=c1.getDeclaredConstructor(String.class); System.out.println(constructor);
2.5.2 動态建立對象、執行方法
2.5.2.1 建立類的對象
調用Class對象的newInstance方法
- 類必須有一個無參構造器
- 類的構造器通路權限要夠
- eg:
// 獲得Class對象 Class c1 = Class.forName("reflection.User"); // 構造一個對象,本質上調用了類的無參構造器 User user=(User)c1.newInstance(); System.out.println(user);
- 如果沒有無參構造器,則可以通過有參構造器建立對象
//通過構造器建立對象 Constructor constructor = c1.getDeclaredConstructor(String.class); User user=(User)constructor.newInstance("南京"); System.out.println(user);
2.5.2.2 執行方法
- 通過反射調用普通方法,首先需要建立一個對象
- 通過反射擷取方法
- 執行該方法,第二個參數為該方法需要的參數
- 類似的也可以通過反射操作屬性(不能直接操作私有屬性),這裡需要使用name.setAccessible(true)關閉權限檢測,使得可以通路到User類的私有變量并進行修改。這樣做破壞了類的封裝性
//通過反射操作屬性 User user=(User)c1.newInstance(); Field name=c1.getDeclaredField("name"); // 關閉權限檢測,使得可以通路到User類的私有變量并進行修改 name.setAccessible(true); name.set(user,"南京"); System.out.println(user.getName());
2.6 性能對比分析
- 普通方式調用執行的時間比反射方式調用的時間少得多
- 關閉權限檢測的反射方式比普通的反射方式快
~~可以自己寫代碼測試,這裡就不贅述啦
2.7 擴充:反射操作泛型
- 首先寫兩個test方法
public void test1(Map<String, User> map, List<User> list) { System.out.println("test1"); } public Map<String, User> test2() { System.out.println("test2"); return null; }
- 擷取test1方法參數的泛型的參數類型
列印出來的結果是Method method = Test10.class.getMethod("test1", Map.class, List.class); // 獲得泛型的參數類型 Type[] genericParameterTypes = method.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { // 列印泛型參數類型 System.out.println(genericParameterType); // 判斷是否是一種參數化類型 if(genericParameterType instanceof ParameterizedType){ // 獲得真實泛型參數資訊 Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument); } } System.out.println(); }
java.util.Map<java.lang.String, reflection.User> class java.lang.String class reflection.User java.util.List<reflection.User> class reflection.User
- 獲得test2方法傳回的泛型的參數類型
列印出來的結果是Method method = Test10.class.getMethod("test2", null); // 獲得傳回值的泛型的參數類型 Type genericReturnType = method.getGenericReturnType(); // 判斷是否是一種參數化類型 if (genericReturnType instanceof ParameterizedType) { // 獲得真實泛型參數資訊 Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument); } }
class java.lang.String class reflection.User
2.8 反射操作注解
- getAnnotations
- getAnnotation
這裡以ORM(對象關系映射)為例,利用注解和反射完成類和表結構的映射關系
- 首先建立一個實體類S,含有id、age、name三個屬性,對應資料庫表中的三列。
- 為資料庫的表名稱建立注解
@Target(value = ElementType.TYPE) @Retention(value = RetentionPolicy.RUNTIME @interface TableS { String value(); }
- 為資料庫中每一列的具體資訊建立注解,包括列名、資料類型、資料長度
@Target(value = ElementType.FIELD) @Retention(value = RetentionPolicy.RUNTIME) @interface FieldS { String columnName(); String type(); int length(); }
- 為實體類S和類的每一個屬性加上注解
@TableS("db_s") class S { @FieldS(columnName = "db_id", type = "int", length = 10) private int id; @FieldS(columnName = "db_age", type = "int", length = 10) private int age; @FieldS(columnName = "db_name", type = "varchar", length = 3) private String name; }
- 通過反射getAnnotations()獲得類S的注解
輸出為@reflection.TableS(value=db_s)Class c1 = Class.forName("reflection.S"); // 通過反射獲得注解 Annotation[] annotations = c1.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); }
- 通過getAnnotation()獲得指定注解的value值
輸出為db_sTableS tableS = (TableS) c1.getAnnotation(TableS.class); System.out.println(tableS.value());
- 獲得類S指定的屬性的注解的資訊
輸出為Field field=c1.getDeclaredField("name"); FieldS annotation = field.getAnnotation(FieldS.class); System.out.println(annotation.columnName()); System.out.println(annotation.type()); System.out.println(annotation.length());
所有的資訊都和自己賦給注解的值一一對應db_name varchar 3