Java反射及注解


一、反射

動態語言:是指程序在運行是可以改變其結構:新的函數可以引進,已有的函數可以被刪除等結構上的變化。比如常見的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 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM