一、反射
動態語言:是指程序在運行是可以改變其結構:新的函數可以引進,已有的函數可以被刪除等結構上的變化。比如常見的JavaScript就是動態語言,除此以外Python等也屬於動態語言,而C、C++則不屬於動態語言。從反射角度說Java屬於半動態語言。
反射機制:指在運行狀態中,對於任意一個類都能夠知道這個類所有的屬性和方法;並且對於任意一個對象,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能稱為Java語言的反射機制。在Java中,只要給定類的名字,那么就可以通過反射機制來獲得類的所有信息。
反射機制主要提供了以下功能:
- 在運行時判定任意一個對象所屬的類;
- 在運行時創建對象;
- 在運行時判定任意一個類所具有的成員變量和方法;
- 在運行時調用任意一個對象的方法;
- 生成動態代理;
哪里用到反射機制?
jdbc中有一行代碼:Class.forName('com.mysql.jdbc.Driver.class'); 加載MySQL的驅動類。這就是反射,現在很多框架都用到反射機制,hibernate,struts都是用反射機制實現的。
反射的應用場合
編譯時類型和運行時類型
在Java程序中許多對象在運行時都會出現兩種類型:編譯時類型和運行時類型。編譯時的類型由聲明對象時使用的類型來決定;運行時的類型由實際賦值給對象的類型決定。如:
Person p = new Student(); 其中編譯時類型為Person,運行時類型為Student。
編譯時類型無法獲取具體方法
程序在運行時還可能接收到外部傳入的對象,該對象的編譯時類型為Object,但是程序有需要調用該對象的運行時類型的方法。為了解決這些問題,程序需要在運行時發現對象和類的真實信息。然而,如果編譯時根本無法預知該對象和類屬於哪些類,程序只能依靠運行時信息來發現該對象和類的真實信息,此時就必須使用到反射了。
Java反射API
反射API 用來生成JVM中的類、接口或對象的信息。
1)Class類:反射的核心類,它表示正在運行的Java應用程序中的類和接口。可以獲取類的屬性、方法等信息。
2)Field類:Java.lang.reflect 包中的類,表示類的成員變量,可以用來獲取和設置類或接口中的屬性值。
3)Method類:Java.lang.refect 包中的類,表示類的方法,可以用來獲取類或接口中的某個方法信息或執行方法。
4)Constructor類:Java.lang.reflect 包中的類,表示類的構造方法。
反射使用步驟(獲取class對象、調用對象的方法)
1)獲取想要操作的類的Class對象,它是反射的核心,通過Class對象我們可以任意調用類的方法。
2)調用Class類中的方法,即就是反射的使用階段。
3)使用反射API來操作這些信息。
獲取Class對象的4種方法
調用某個對象的getClass()方法(即對象名.getClass)
Person p = new Person();
Class clazz = p.getClass();
調用某個類的class屬性來獲取該類對應的Class對象(即類名.class)
Class clazz = Person.class;
使用Class 類中的forName()靜態方法(最安全/性能最好)
Class clazz = Class.forName("類的全路徑");
如果是基本類型的包裝類,則可以通過調用包裝類的Type屬性來獲得改包裝類的Class對象。
例:Class<?> claszz = Integer.TYPE;
1 當我們獲得了想要操作的類的 Class 對象后,可以通過 Class 類中的方法獲取並查看該類中的方法和屬性。 2 //獲取 Person 類的 Class 對象
3 Class clazz=Class.forName("reflection.Person"); 4 //獲取 Person 類的所有方法信息
5 Method[] method=clazz.getDeclaredMethods(); 6 for(Method m:method){ 7 System.out.println(m.toString()); 8 } 9 //獲取 Person 類的所有成員屬性信息
10 Field[] field=clazz.getDeclaredFields(); 11 for(Field f:field){ 12 System.out.println(f.toString()); 13 } 14 //獲取 Person 類的所有構造方法信息
15 Constructor[] constructor=clazz.getDeclaredConstructors(); 16 for(Constructor c:constructor){ 17 System.out.println(c.toString()); 18 }
創建對象的兩種方法
Class 對象的newInstance()
1)使用Class對象的newInstance()方法來創建該Class對象對應類的實例,但是這種方法要求該Class對象對應的類有默認的空構造器。
調用Constructor對象的newInstance()
2)先使用Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建Class對象對應類的實例,通過這種方法可以選定構造方法創建實例。
1 //獲取 Person 類的 Class 對象
2 Class clazz=Class.forName("reflection.Person"); 3 //使用.newInstane 方法創建對象
4 Person p=(Person) clazz.newInstance(); 5 //獲取構造方法並創建對象
6 Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class); 7 //創建對象並設置屬性
8 Person p1=(Person) c.newInstance("李四","男",20);
JDK動態加載
Java反射機制允許程序在運行時加載、探知、使用編譯期間完全未知的classes。通過Java的反射機制,可以獲得程序內部或第三方JAR包的Class、Method、屬性、參數等信息。
動態加載:程序在運行時調用相應方法,即使其他方法是錯誤的,程序依舊會執行。
靜態加載:程序在編譯時執行,在執行過程中加載所有可能執行到的程序。在這種加載方式下,只要加載中一個方法出錯,程序就不能運行。
反射機制的優缺點?
優點:
1)能夠運行時動態獲取類的實例,大大提高程序的靈活性。
2)與Java動態編譯相結合,可以實現無比強大的功能。
缺點:
1)使用反射的性能較低。Java反射是要解析字節碼,將內存中的對象進行解析。
解決方案:
a. 由於JDK的安全檢查耗時較多,所以通過setAccessible(true)的方式關閉安全檢查來(取消對訪問控制修飾符的檢查)提升反射速度。
b. 需要多次動態創建一個類的實例的時候,有緩存的寫法會比沒有緩存要快很多。
c. ReflectASM工具類,通過字節碼生成的方式加快反射速度。
2)使用反射相對來說不安全,破壞了類的封裝性,可以通過反射獲取這個類的私有方法和屬性。
二、Java注解
1、概念
Annotation(注解)是Java提供的一種對源程序中元素關聯信息和元數據(metadata)的途徑和方法。Annotation(注解)是一個接口,程序可以通過反射來獲取制定程序中元素的Annotation對象,然后通過該Annotation對象來獲取注解中的元數據信息。
2、四種標准元注解
元注解作用是負責注解其他注解。Java5.0定義了4個標准的meta-annotation類型,它們被用來提供對其他annotation類型作說明。
@Target 修飾的對象范圍
@Target說明了annotation所修飾的對象范圍:annotation可被用於packages、types(類、接口、枚舉、annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在annotation類型的聲明中使用了target可更加明晰其修飾的目標。
@Retention 定義被保留的時間長短
Retention定義了該annotation被保留的時間長短:表示需要在什么級別保存注解信息,用於描述注解的生命周期(即:被描述的注解在什么范圍內有效),取值(RetentionPoicy)有:
SOURCE:在源文件中有效(即源文件保留)
CLASS:在class文件中有效(即class保留)
RUNTIME:在運行時有效(即運行時保留)
@Documented 描述-javadoc
@Documented用於描述其他類型的annotation應該被作為被標注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。
@Inherited 闡述了某個被標注的類型是被繼承的
@Inherited元注解是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。
3、注解處理器
如果沒有用來讀取注解的方法和工作,那么注解也就不會比注釋更有用處了。使用注解的過程中,很重要的一部分就是創建與使用注解處理器。Java SE5擴展了反射機制的API,以幫助程序員快速的構造自定義注解處理器。下面實現一個注解處理器。
1 /1:*** 定義注解*/
2 @Target(ElementType.FIELD) 3 @Retention(RetentionPolicy.RUNTIME) 4 @Documented 5 public @interface FruitProvider { 6 /**供應商編號*/
7 public int id() default -1; 8 /*** 供應商名稱*/
9 public String name() default ""; 10 /** * 供應商地址*/
11 public String address() default ""; 12 } 13 //2:注解使用
14 public class Apple { 15 @FruitProvider(id = 1, name = "陝西紅富士集團", address = "陝西省西安市延安路") 16 private String appleProvider; 17 public void setAppleProvider(String appleProvider) { 18 this.appleProvider = appleProvider; 19 } 20 public String getAppleProvider() { 21 return appleProvider; 22
23 } 24 /3:*********** 注解處理器 ***************/
25 public class FruitInfoUtil { 26 public static void getFruitInfo(Class<?> clazz) { 27 String strFruitProvicer = "供應商信息:"; 28 Field[] fields = clazz.getDeclaredFields();//通過反射獲取處理注解
29 for (Field field : fields) { 30 if (field.isAnnotationPresent(FruitProvider.class)) { 31 FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class);//注解信息的處理地方
32 strFruitProvicer = " 供應商編號:" + fruitProvider.id() + " 供應商名稱:"
33 + fruitProvider.name() + " 供應商地址:"+ fruitProvider.address(); 34 System.out.println(strFruitProvicer); 35 } 36 } 37 } 38 } 39 public class FruitRun { 40 public static void main(String[] args) { 41 FruitInfoUtil.getFruitInfo(Apple.class); 42 /***********輸出結果***************/
43 // 供應商編號:1 供應商名稱:陝西紅富士集團 供應商地址:陝西省西安市延
44 } 45 }
