# Annotation(註解與反射) ###### tags: `Master of JAVA` ## 1.註解入門 ### 概念 註解與註釋是不太一樣的東西,註釋主要是給人看的,而註解除了給人看以外,主要是給程式看的,這也是包括mybatis、spring等許多框架的底層實現。 ### 什麼是註解 ![](https://i.imgur.com/iGXnDUh.png) 這裡要注意的是,註解可被其他程序讀取,可以實現動態性,依靠反射來實現。 ```java= package com.ykma.annotation; //什麼是註解 public class Test01 extends Object{ //Override 重寫的註解 @Override public String toString() { return super.toString(); } } ``` ### 內置註解 ![](https://i.imgur.com/zi0oyas.png) ```java= package com.ykma.annotation; import java.util.ArrayList; import java.util.List; //什麼是註解 public class Test01 extends Object{ //Override 重寫的註解 @Override public String toString() { return super.toString(); } //Deprecated 不推薦工程師使用,但還是可以使用,或著存在更好的方式 @Deprecated public static void test(){ System.out.println("Deprecated"); } //鎮壓警告,有參數,把沒使用到的灰色警告強制鎮壓下去 @SuppressWarnings("all") public void test02(){ List list = new ArrayList(); } public static void main(String[] args) { test(); } } ``` ### 元註解 用來註解註解的註解,JAVA定義了4個meta-annotation: ![](https://i.imgur.com/FLnMXeX.png) ```java= package com.ykma.annotation; import java.lang.annotation.*; //測試元註解 @MyAnnotation public class Test02 { @MyAnnotation public void test(){ } } //定義一個註解 //Target 表示我們的註解可以用在哪些地方 @Target(value = {ElementType.METHOD,ElementType.TYPE}) //Retention 表示我們的註解在什麼地方還有效 //runtime>class>sources @Retention(value = RetentionPolicy.RUNTIME) //Documented 表示是否將我們的註解生成在JAVAdoc中 @Documented //Inherited 子類可以繼承父類的註解 @Inherited @interface MyAnnotation{ } ``` ### 自訂義註解 ![](https://i.imgur.com/Q7LN4zs.png) ```java= package com.ykma.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //自訂義註解 public class Test03 { //註解可已顯示賦值,如果沒有默認值,我們就必須給註解賦值 @MyAnnotation2(name = "ykma",schools = {"台大"}) public void test(){} //如果只有一個參數,可設定為value,這樣寫的時候不需血參數名 @MyAnnotation3("ykma") public void test2(){ } } @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation2{ //註解的參數 : 參數類型 + 參數名(); String name() default ""; int age() default 0; int id() default -1; // 如果默認值為-1,代表不存在,indexof,如果找不到就返回-1 String[] schools() default {"興大"}; } @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation3{ String value(); } ``` ## 2.反射(Reflaction) JAVA透過反射將靜態語言變成動態語言,在程序執行的時候依然可以注入程式,改變其結構。 ### 靜態語言&動態語言 ![](https://i.imgur.com/jGpxbkF.png) JS就是一個動態語言,沒有固定的資料型別,可以在執行的時候改變某些結構: ```javascript= function f(){ var x = "var a=3;var b=5;alert(a+b)"; eval(x) } ``` ### Reflaction 反射是讓JAVA變成動態語言的關鍵所在,透過reflaction API可以在程序執行時獲得任何類的內部訊息,其原理跟一般創建物件相反,故有反射之義,一般是透過類來new物件,反射則是透過reflaction建立物件,透過該物件可以反向獲得類的訊息: ![](https://i.imgur.com/5TVldfB.png) 反射的機制與應用,動態代理在AOP相當重要,框架也使用了大量AOP: ![](https://i.imgur.com/6jCePM4.png) 反射的優點與缺點: ![](https://i.imgur.com/lE4psQ4.png) 反射相關的主要API,都是根據Class這個類來操作: ![](https://i.imgur.com/kMIv2gZ.png) ```java= package com.ykma.reflaction; //甚麼叫反射 public class Test02 { public static void main(String[] args) throws ClassNotFoundException { //透過反射獲取類的class物件 Class c1 = Class.forName("com.ykma.reflaction.User"); System.out.println(c1); Class c2 = Class.forName("com.ykma.reflaction.User"); Class c3 = Class.forName("com.ykma.reflaction.User"); Class c4 = Class.forName("com.ykma.reflaction.User"); //一個類在記憶體中只有一個class物件 //一個類被加載之後,類的整個結構都會被封裝在class物件中 System.out.println(c2.hashCode()); System.out.println(c3.hashCode()); System.out.println(c4.hashCode()); } } //實體類:pojo , entitiy class User{ private String name; private int id; private int age; public User() { } public User(String name, int id, int age) { this.name = name; this.id = id; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", id=" + id + ", age=" + age + '}'; } } ``` Class類: ![](https://i.imgur.com/FpEpc8s.png) 正常是透過由上往下,由Class往下new出物件,反射則是由下往上,由物件去get Class資訊,即為反射之義。 ### Class的特性與常用方法 Class的特性: ![](https://i.imgur.com/P55ana8.png) Class類的常用方法: ![](https://i.imgur.com/zxW9BDs.png) 如何獲取Class類的實體: ![](https://i.imgur.com/vVYgT8F.png) ```java= package com.ykma.reflaction; //測試Class類的創建方式有哪些 public class Test03 { public static void main(String[] args) throws ClassNotFoundException { Person person = new Student(); System.out.println("這個人是:"+person.name); //方式一:透過物件獲得 Class c1 = person.getClass(); System.out.println(c1.hashCode()); //方式二:透過forname獲得 Class c2 = Class.forName("com.ykma.reflaction.Student"); System.out.println(c2.hashCode()); //方式三:透過類名.class獲得 Class c3 = Student.class; System.out.println(c3.hashCode()); //方式四:基本內置類型的包裝類都有一個Type屬性 Class<Integer> c4 = Integer.TYPE; System.out.println(c4); //獲得父類類型 Class c5 = c1.getSuperclass(); System.out.println(c5); } } class Person{ public String name; public Person() { } public Person(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } } class Student extends Person{ public Student(){ this.name = "學生"; } } class Teacher extends Person{ public Teacher(){ this.name = "老師"; } } ``` ### 哪些類型可以有Class物件 ![](https://i.imgur.com/secOtdb.png) ```java= package com.ykma.reflaction; import java.lang.annotation.ElementType; //所有類型的class public class Test04 { public static void main(String[] args) { Class c1 = Object.class; //類 Class c2 = Comparable.class; //介面(接口) Class c3 = String[].class; //一維陣列 Class c4 = int[][].class; //二維陣列 Class c5 = Override.class; //註解 Class c6 = ElementType.class; //枚舉 Class c7 = Integer.class; //基本數據類型 Class c8 = void.class; //void 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 int[] a = new int[10]; int[] b = new int[100]; System.out.println(a.getClass().hashCode()); System.out.println(b.getClass().hashCode()); } } ``` ### 從JAVA記憶體分析 ![](https://i.imgur.com/x1ioTeB.png) 了解類的加載過程: ![](https://i.imgur.com/gfH7sVF.png) ![](https://i.imgur.com/bVZnSQD.png) ```java= package com.ykma.reflaction; public class Test05 { public static void main(String[] args) { A a = new A(); System.out.println(a.m); /* 1.加載到記憶體,會產生一個類對應Class物件 2.鏈結,鏈結結束後 m = 0 3.初始化 <clinit>(){ System.out.println("A類靜態程式碼區塊初始化"); m = 300; m = 100; } m = 100; */ } } class A{ static { System.out.println("A類靜態程式碼區塊初始化"); m = 300; } static int m = 100; public A(){ System.out.println("A類的無參建構初始化"); } } ``` ![](https://i.imgur.com/Ou4sJho.png) ### 分析類初始化 甚麼時候會發生類的初始化?: ![](https://i.imgur.com/QBaXMbE.png) ```java= package com.ykma.reflaction; //測試類甚麼時候會初始化 public class Test06 { static { System.out.println("main類被加載"); } public static void main(String[] args) throws ClassNotFoundException { //1.主動引用 Son son = new Son(); //反射也會產生主動引用 Class.forName("com.ykma.reflaction.Son"); //不會產生類的引用的方法 System.out.println(Son.b); Son[] array = new Son[5]; System.out.println(Son.M); } } class Father{ static int b = 2; static { System.out.println("父類被加載"); } } class Son extends Father{ static { System.out.println("子類被加載"); m = 300; } static int m = 100; static final int M = 1; } ``` ### 類加載器 ![](https://i.imgur.com/OS9Uk3y.png) ![](https://i.imgur.com/VuVmnpC.png) ```java= package com.ykma.reflaction; public class Test07 { public static void main(String[] args) throws ClassNotFoundException { //獲取系統類的加載器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); //獲取系統類加載器的父類加載器-->擴展類加載器 ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent); //獲取擴展類加載器的父類加載器-->根加載器(c/c++) ClassLoader parent1 = parent.getParent(); System.out.println(parent1); //測試當前類是哪個加載器加載的 ClassLoader classLoader = Class.forName("com.ykma.reflaction.Test07").getClassLoader(); System.out.println(classLoader); //測試JDK內置的類是誰加載的 classLoader = Class.forName("java.lang.Object").getClassLoader(); System.out.println(classLoader); //如何獲得系統類加載器可以加載的路徑 System.out.println(System.getProperty("java.class.path")); //雙親委派機制 } } ``` ### 建構運行時類的物件 經過以上的內容,接下來我們要創建運行時類的物件,首先創建必須要先獲取: ![](https://i.imgur.com/FMjHEqD.png) ```java= package com.ykma.reflaction; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; //獲得類的訊息 public class Test08 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { Class c1 = Class.forName("com.ykma.reflaction.User"); User user = new User(); c1 = user.getClass(); //獲得類的名字 System.out.println(c1.getName()); //獲得包名 + 類名 System.out.println(c1.getSimpleName()); //獲得類名 //獲得類的屬性 System.out.println("============================="); Field[] fields = c1.getFields(); //只能找到public屬性 fields = c1.getDeclaredFields(); //可以找到全部的屬性 for (Field field : fields) { System.out.println(field); } //獲得指定屬性的值 Field name = c1.getDeclaredField("name"); System.out.println(name); //獲得類的方法 System.out.println("============================="); Method[] methods = c1.getMethods(); //獲得本類及其父類的全部public方法 for (Method method : methods) { System.out.println("正常的:"+method); } methods = c1.getDeclaredMethods(); //獲得本類的所有方法 for (Method method : methods) { System.out.println("getDeclaredMethods:"+method); } //獲得指定方法 //為何要參數? --> 重載overload Method getName = c1.getMethod("getName", null); Method setName = c1.getMethod("setName", String.class); System.out.println(getName); System.out.println(setName); //獲得構造器(建構子) System.out.println("============================="); Constructor[] constructors = c1.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } constructors = c1.getDeclaredConstructors(); for (Constructor constructor : constructors) { System.out.println("#"+constructor); } //獲得指定的構造器(建構子) Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class); System.out.println("指定:"+declaredConstructor); } } ``` 小結: ![](https://i.imgur.com/cuA5toF.png) ### 有了Class物件後,如何使用 ![](https://i.imgur.com/w79npHZ.png) 如何調用指定的方法: ![](https://i.imgur.com/PtskO01.png) Invoke: ![](https://i.imgur.com/nASSOTd.png) setAccessible: ![](https://i.imgur.com/BWQ3uVW.png) ```java= package com.ykma.reflaction; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; //透過反射動態的建構物件 public class Test09 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { //獲得Class物件 Class c1 = Class.forName("com.ykma.reflaction.User"); //建構一個物件 // User user = (User)c1.newInstance(); //本質是調用了類的無參構造器(建構子) // System.out.println(user); //透過構造器(建構子)創建物件 // Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class); // User user2 = (User)constructor.newInstance("ykma", 001, 18); // System.out.println(user2); //透過反射調用普通方法 User user3 = (User)c1.newInstance(); //透過反射獲取一個方法 Method setName = c1.getDeclaredMethod("setName", String.class); //invoke:激活的意思 //(物件,"方法的值") setName.invoke(user3,"馬凱"); System.out.println(user3.getName()); //透過反射操作屬性 System.out.println("=========================================================="); User user4 = (User)c1.newInstance(); Field name = c1.getDeclaredField("name"); //不能直接操作私有屬性,我們需要關閉程序的安全檢測,屬性或方法的setAccessible(true) name.setAccessible(true); name.set(user4,"馬凱2"); System.out.println(user4.getName()); } } ``` ### 性能對比分析 ```java= package com.ykma.reflaction; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; //分析性能問題 public class Test10 { //普通方式調用 public static void test01(){ User user = new User(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { user.getName(); } long endTime = System.currentTimeMillis(); System.out.println("普通方式執行10億次:"+(endTime-startTime)+"ms"); } //反射方式調用 public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class c1 = user.getClass(); Method getName = c1.getDeclaredMethod("getName", null); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user,null); } long endTime = System.currentTimeMillis(); System.out.println("反射方式執行10億次:"+(endTime-startTime)+"ms"); } //反射方式調用,關閉檢測 public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class c1 = user.getClass(); Method getName = c1.getDeclaredMethod("getName", null); getName.setAccessible(true); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user,null); } long endTime = System.currentTimeMillis(); System.out.println("關閉檢測執行10億次:"+(endTime-startTime)+"ms"); } public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { test01(); test02(); test03(); } } ``` ### 使用反射來操作泛型 ![](https://i.imgur.com/MRrU13Y.png) 透過反射來讀取泛型: ```java= package com.ykma.reflaction; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import java.util.Map; //透過反射獲取泛型 public class Test11 { public void test01(Map<String,User> map, List<User> list){ System.out.println("test01"); } public Map<String,User> test02(){ System.out.println("test02"); return null; } public static void main(String[] args) throws NoSuchMethodException { Method method = Test11.class.getMethod("test01", 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); } } } method = Test11.class.getMethod("test02", null); Type genericReturnType = method.getGenericReturnType(); if (genericReturnType instanceof ParameterizedType){ Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument); } } } } ``` ### ==**(重要)透過反射來操作註解**== 1. getAnnotations 2. getAnnotation 甚麼是**ORM(Object Relationship Mapping)**: ORM其實就是透過類跟資料庫中的表做映射: ![](https://i.imgur.com/0Fp0Zdz.png) ```java= package com.ykma.reflaction; import java.lang.annotation.*; import java.lang.reflect.Field; //練習反射操作註解 public class Test12 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { Class c1 = Class.forName("com.ykma.reflaction.Student2"); //透過反射獲得註解 Annotation[] annotations = c1.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); } //獲得註解的value的值 Tableykma tableykma = (Tableykma)c1.getAnnotation(Tableykma.class); String value = tableykma.value(); System.out.println(value); //獲得類指定的註解 Field f = c1.getDeclaredField("id"); Fieldykma annotation = f.getAnnotation(Fieldykma.class); System.out.println(annotation.columnName()); System.out.println(annotation.type()); System.out.println(annotation.length()); } } @Tableykma("db_student") class Student2{ @Fieldykma(columnName = "db_id",type = "int",length = 10) private int id; @Fieldykma(columnName = "db_age",type = "int",length = 10) private int age; @Fieldykma(columnName = "db_name",type = "varchar",length = 3) private String name; public Student2() { } public Student2(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student2{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } } //類名的註解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Tableykma{ String value(); } //屬性的註解 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface Fieldykma{ String columnName(); String type(); int length(); } ```