夯實 Java 基礎(chǔ) - 反射

夯實 Java 基礎(chǔ) - 反射

自嵌套 Fragment 懶加載文章至今已經(jīng)已經(jīng)一個星期過去了浇垦,說實話最近對于學(xué)習(xí)的熱情有點衰減依疼,也可能是自己有點飄了高蜂,也有可能是現(xiàn)實中的誘惑多了點椎眯,但是這是個不好的狀態(tài)神帅,必須調(diào)整自己向著目標繼續(xù)前進再姑。

前言

本文將重拾 Java 基礎(chǔ)中的反射知識,由于筆者是做移動端 Android 開發(fā)的找御,日常工作中反射用的少的可以拿手指頭數(shù)過來≡疲現(xiàn)在我所記得的上次使用它應(yīng)該是在修改 TabLayout 的下劃線寬度的時候绍填。這次重拾反射這部分知識的主要原因其實是在注解和動態(tài)代理。相比反射來說這兩者在我們?nèi)粘J褂玫目蚣苤斜容^常見栖疑,比如 EventBus 原理讨永,ButterKnife 原理,Retrofit原理遇革,甚至 AOP(面向切面編程) 在 Android 中的應(yīng)用卿闹,都有這兩者的身影。

本文其實沒有什么營養(yǎng)價值萝快,和其他相關(guān)文章一樣锻霎,本文更多的是在記錄反射的 API,這么做更多可能是為了以后遺忘了方便查閱杠巡。本文將從以下幾個方面來記錄:

  1. 反射是什么量窘? 反射的意義?
  2. 反射的入口 - Class 類
  3. 反射的成員變量獲取 - Field 類
  4. 反射的方法獲取 - Method 類
  5. 反射的構(gòu)造方法獲取 - Constructor 類
  6. 反射的獲取注解相關(guān)信息

反射機制

試著解釋清楚為什么需要反射是并不簡單氢拥,這里涉及了一些 jvm 類加載機制蚌铜,那么從先前說的修改 TabLayout 的下劃線寬度說起,首先 TabLayout 是存在 SDK 中并不是我們定義的一個類嫩海,但是在使用中我們遇到了要修改其內(nèi)容的需求冬殃,這時候我們通過反射在程序運行時獲取了其內(nèi)部私有變量 mTabStrip ,并修改了他的 LayoutParams叁怪。我們知道我們通過 tablayout.mTabStrip 是無法訪問的审葬,因為變量是私有的。

Field tabStrip = tablayout.getDeclaredField("mTabStrip");
tabStrip.setAccessible(true);
...
do reset padding LayoutParams operation

當(dāng)然這只是我遇到的一個使用場景奕谭,做 JavaWeb 的朋友應(yīng)該對此有更深的了解涣觉。

那么這里記錄下什么是反射的官方說法:

通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息血柳。Java 反射機制可以動態(tài)地創(chuàng)建對象并調(diào)用其屬性官册,即使這個對象的類型在編譯期是未知的。

通過 Java 的反射機制我們可以:

  1. 在運行時判斷任意一個對象所屬的類难捌;
  2. 在運行時構(gòu)造任意一個類的對象膝宁;
  3. 在運行時獲取任意一個類所具有的成員變量和方法,即使它是私有的根吁;
  4. 在運行時調(diào)用任意一個對象的方法员淫;

注意我們所提到的前提和重點是運行時。

反射的入口 - Class 類

如何獲取 Class 類

想要通過反射操作一個類的成員或者方法击敌,Java 為我們提供了一套 API 而這套 API 屬于 Class 類介返,作為一些列反射操作的入口,Java 提供了三個方法或獲得某個對象或者類的 Class 類對象:

  • 使用 Object 的 getClass 方法愚争,如我們已知一個類的對象我們可以使用超類的 getClass 方法獲扔辰浴:
TabLayout tabLayout = findViewById(R.id.tabLayout);
Class<?> tabLayoutClass = tabLayout.getClass();
  • 我們也可以通過 XXX.class 的方式直接獲取某個類的 Class 對象而無需創(chuàng)建該類的對象挤聘。
//對于普通引用數(shù)據(jù)類型的類我們可以如下調(diào)用
Class<?> tabLayoutClass = TabLayout.class;

//而對于基本數(shù)據(jù)類型我們可以使用 XXX.TYPE 的方式調(diào)用
Class<?> classInt = Integer.TYPE;
  • 通過 Class 的靜態(tài)方法 Class.forName(String name) 方法傳入一個類的全量限定名獲得創(chuàng)建。該方法會要求拋出或者捕獲ClassNotFoundException異常捅彻。
Class c3 = Class.forName("android.support.design.widget.TabLayout");

通過反射獲取目標的類的成員變量

Class 類可以幫助我們在只知道一個類的名字的時候组去,獲取該類的成員變量,及時某些成員變量是私有的步淹。我們假設(shè)只知道一個類的名字从隆,并不知道其內(nèi)部構(gòu)成,也就是說內(nèi)部沒有 API 列表提供給我們缭裆。Java Class 類提供了4種方法键闺,來獲取這些成員變量。

方法名稱 返回值類型 是否包含獲得繼承的屬性 是否可以獲得私有屬性
getField() Field YES NO
getDeclaredField() Field NO YES
getFields() Field[] YES NO
getDeclaredFields() Field[] NO YES
  • 測試 getField() / getDeclaredField()
  Class<TopStudent> topStudentClass = TopStudent.class;
  
  //id public 屬性定義在父類 Student 中
  Field id = topStudentClass.getField("id");
  //grade public 屬性定義在 TopStudent 中
  Field grade = topStudentClass.getField("grade");
  //isReal private 屬性定義在 TopStudent 中 無法獲得私有屬性將拋出NoSuchFieldException: isReal
  Field isReal = topStudentClass.getField("isReal");

  //id public 屬性定義在父類 Student 中 無法獲得 public 父類屬性 java.lang.NoSuchFieldException: id
  Field  declaredId = topStudentClass.getDeclaredField("id");
  //grade public 屬性定義在 TopStudent 中
  Field declaredGrade = topStudentClass.getDeclaredField("grade");
  //isReal private 屬性定義在 TopStudent 中
  Field declaredIsReal = topStudentClass.getDeclaredField("isReal");
  • 測試 getFields() / getDeclaredFields()
  Field[] fields = topStudentClass.getFields();
  Field[] declaredFields = topStudentClass.getDeclaredFields();

  //fields = [public int Reflection.TopStudent.grade, public int Reflection.Student.id]
  System.out.println("fields = " + Arrays.toString(fields));

  // grade  id
  for (Field field : fields) {
      System.out.println("" + field.getName());
  }

  //declaredFields = [private boolean Reflection.TopStudent.isReal, public int Reflection.TopStudent.grade]
  System.out.println("declaredFields = " + Arrays.toString(declaredFields));
  //isReal grade
  for (Field field : declaredFields) {
      System.out.println("" + field.getName());
  }

事實上我們通過反射獲取到屬性以后澈驼,下一步可能是要獲取或者修改該屬性的值辛燥,F(xiàn)ield 類也為我們準備了相應(yīng)的 set 和 get 方法。同時 set 方法作用于私有屬性的時候?qū)伋?IllegalAccessException異常缝其。此時我們需要通過挎塌。

同時如果我們預(yù)先不知道該屬性的類型的時候我們也可以通過 getType/getGenericType 來獲取該屬性的類型,后者在屬性為泛型表示的屬性時獲取泛型的通用符號如果不是則返回值與 getType 內(nèi)容相同。

  TopStudent topStudent = topStudentClass.newInstance();

  Field grade = topStudentClass.getDeclaredField("grade");
  
  grade.set(topStudent, 4);
  //Can not set int field Reflection.TopStudent.grade to java.lang.Float
 // grade.set(topStudent,4.0f);
  System.out.println("grade = " + grade.get(topStudent));

  Class<?> type = grade.getType();
  Type genericType = grade.getGenericType();
  System.out.println("type = " + type);
  System.out.println("genericType = " + genericType);
  //如果我們知道對應(yīng)的變量為基本類型變量的某個類型可以使用 setXXX 的等價方法
   grade.setInt(topStudent,4);
  //Can not set int field Reflection.TopStudent.grade to (float)4.0
  //grade.setFloat(topStudent,4);


 //再給私有屬性設(shè)置值的時候要記得設(shè)置 isAccessible 為 true
  Field isReal = topStudentClass.getDeclaredField("isReal");
  isReal.setAccessible(true);
  // 如果不設(shè)置isReal.setAccessible(true);
  // 將會拋出 can not access a member of class Reflection.TopStudent with modifiers "private"異常
  isReal.set(topStudent, true);
  boolean isRealValue = (boolean) isReal.get(topStudent);
  System.out.println("isRealValue = " + isRealValue);

  int gradeValue = grade.getInt(topStudent);
  System.out.println("gradeValue  " + gradeValue);

自動裝箱導(dǎo)致的 IllegalArgumentException 異常

值得注意的是我們當(dāng)我反射的類某個屬性為基本數(shù)據(jù)類型的包裝類的時候内边,我們無法使用 setXXX 直接設(shè)置該數(shù)值榴都,將拋出java.lang.IllegalArgumentException ,使用 set(Object obj,Object val) 則可以直接運行漠其,原因在于 setInt 等方法無法為我們做自動裝箱的操作嘴高,而后者則可以:

 // 測試 自動拆箱裝箱
  Field code = topStudentClass.getField("code");
  //裝箱成功
  code.set(topStudent,100);
  //無法自動裝箱 Can not set java.lang.Integer field Reflection.Student.code to (int)200
  code.setInt(topStudent,200);
  int codeVal = (int) code.get(topStudent);
  System.out.println("codeVal = " + codeVal);

對于 final 修飾的變量改如何修改

從代碼編寫角度來看,如果我們將一個成員變量定義為 final 代表我們不希望有人可以修改它的值和屎,但是實際的需求誰又能考慮到這里多呢拴驮?好在通過反射我們也可以修改某個 final 成員變量。當(dāng)然需要注意的地方比較多柴信。

對于 Java 基本數(shù)據(jù)類型 以及用使用 String str = "111" 賦值的成員變量莹汤,在編譯期 JVM 對其做了內(nèi)聯(lián)優(yōu)化,可以簡單的理解為編譯后就寫死在.class 文件中了颠印,我們并不能修改成功 final 的成員變量。

對于非上述兩種情況是可以修改成功的

//測試 set final

public class Student {
    public final int id  = 30;
    public final Integer cod  = 90;
    public static final int ID = 1000;
    ...
}

Field id = topStudentClass.getField("id");
// 如果不設(shè)置 setAccessible(true) 將拋出 IllegalAccessException
// 設(shè)置 settAccessible 將繞過檢查
id.setAccessible(true);
id.set(topStudent,100);
int idVal = (int) id.get(topStudent);
System.out.println("idVal = " + idVal);//100
System.out.println("idVal = " + topStudent.id);//30 修改失敗

Field code = topStudentClass.getField("code");
code.setAccessible(true);
code.set(topStudent,100);
int codeVal = (int) code.get(topStudent);
System.out.println("codeVal = " + codeVal);//100
System.out.println("codeVal = " + topStudent.code);//100 修改成功


Field ID = topStudentClass.getField("ID");

//即使設(shè)置了setAccessible(true) 也會拋出 IllegalAccessException
//ID.setAccessible(true);

//通過反射將對應(yīng)成員變量的 final 修飾去掉
Field modifiersField = Field.class.getDeclaredField("modifiers"); 
modifiersField.setAccessible(true);
modifiersField.setInt(ID, ID.getModifiers() & ~Modifier.FINAL); 

ID.set(topStudent,100);
int IDVal = (int) id.get(topStudent);
System.out.println("IDVal = " + IDVal);//100
System.out.println("IDVal = " + TopStudent.ID);//1000 修改失敗

結(jié)論抹竹,沒事不要亂改 final 萬一給后人給自己挖了個大坑埋了呢线罕。

通過反射獲取目標類的成員方法

獲取目標類的成員方法

除了通過 Class 獲取成員變量,通過反射也可以獲取一個類的所有成員方法窃判。與后去成員變量一樣钞楼,獲取
成員方法也有 4 個方法:

方法名稱 返回值類型 是否包含獲得父類方法 是否可以獲得私有方法
getMethod() Method YES NO
getDeclaredMethod() Method NO YES
getMethods() Method[] YES NO
getDeclaredMethods() Method[] NO YES

假設(shè)我們設(shè)置兩個類它們?nèi)缦拢?/p>

public class Student {
    .....
    private String name;
    .....
    
    public String getName() {
        System.out.println("我是 Student 的  public 方法");
        return name;
    }

    private void testPrivate(){
        System.out.println("我是 Student 的 private 方法");
    }
}

public class TopStudent extends Student {
    private boolean isReal;
    public int grade;

    public boolean isReal() {
        System.out.println("我是 TopStudent 的 public 方法");
        return isReal;
    }

    private void testSelfPrivate(){
        System.out.println("我是 TopStudent 的 private 方法");
    }
}

我們嘗試用 getMethods()/getDeclaredMethods() 獲取 TopStudent 類所包含的方法:

private void testGetMethod() {
   Class<TopStudent> topStudentClass = TopStudent.class;
   Method[] methods = topStudentClass.getMethods();
   Method[] declaredMethods = topStudentClass.getDeclaredMethods();

   for (Method method: methods) {
       System.out.println(method.getName());
   }

   System.out.println("---------");

   for (Method method: declaredMethods) {
       System.out.println(method.getName());
   }
}

從打印結(jié)果可以看出 getMethods() 方法獲取的 method 包含所有父類和當(dāng)前類的所有 Public 方法,而 getDeclaredMethods() 獲取的 method 僅包含當(dāng)前類的所有方法袄琳。咦好像沒有辦法獲取父類的 private 方法的途徑询件,什么燃乍? 子類根本就無法繼承父類的私有方法好伐。

/*topStudentClass.getMethods 獲取的 method 包含所有父類和當(dāng)前類的所有 Public 方法*/

public boolean Reflection.TopStudent.isReal()
public java.lang.String Reflection.Student.getName()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

/*topStudentClass.getDeclaredMethods 獲取的 method 僅包含當(dāng)前類的所有方法*/

boolean Reflection.TopStudent.isReal()
private void Reflection.TopStudent.testSelfPrivate()方法*/

同樣我們還可以獲取某個類的單個方法通過 Class 提供給我們的 getMethod 和 getDeclaredMethod 這兩個方法都帶有兩個參數(shù),第一個參數(shù)為方法名 "name",第二個參數(shù)為對應(yīng)方法需要 傳入的參數(shù)的Class 對象 即 "Class<?>... parameterTypes"宛琅。當(dāng)我們嘗試獲取一個并不存在的方法時刻蟹,將會拋出NoSuchMethodException 異常。

我們?yōu)?TopStudent 添加兩個方法用于測試

public void testParams(String p1,int p2){}

public void testParams(double p){}
   try {
       // getMethod 可以正常獲取自己以及父類的公有方法
       Method isRealMethod = topStudentClass.getMethod("isReal");
       Method getNameMethod = topStudentClass.getMethod("getName");

       // 嘗試獲取私有方法將拋出 java.lang.NoSuchMethodException 異常
       Method testSelfPrivateMethod = topStudentClass.getMethod("testSelfPrivate");
       Method testPrivateMethod = topStudentClass.getMethod("testPrivate");


       //嘗試獲取父類的方法 將拋出 NoSuchMethodException 異常
       Method getNameDeclareMethod = topStudentClass.getDeclaredMethod("getName");

       // getDeclaredMethod 可以獲取私有方法將拋出 以及公有方法
       Method isRealDeclareMethod = topStudentClass.getDeclaredMethod("isReal");
       Method testSelfPrivateDeclareMethod = topStudentClass.getDeclaredMethod("testSelfPrivate");
        
        //重載方法的測試
        Method testParams1 = topStudentClass.getMethod("testParams", double.class);
       Method testParams2 = topStudentClass.getMethod("testParams", String.class, int.class);
       //獲取并不存在的重載方法 將拋出 java.lang.NoSuchMethodException
       Method testParams3 = topStudentClass.getMethod("testParams");

   } catch (NoSuchMethodException e) {
       e.printStackTrace();
   }

調(diào)用目標類的成員方法

由于我們上文說過了 getMethod 和 getDeclaredMethod 方法的區(qū)別了嘿辟,為了我們正常獲取對應(yīng)的方法去掉用舆瘪,我們需要使用對應(yīng)的方法。

我們獲取到了指定了 Class 的成員方法后可以通過 Method 的

Object invoke(Object obj, Object... args)

方法來調(diào)用指定類的對象的方法红伦。第一個參數(shù)為該類的對象英古,第二個可變參數(shù)為該方法的參數(shù),而返回值即所調(diào)用的方法的返回值昙读,通常需要我們強轉(zhuǎn)為指定參數(shù)類型召调。而我們還可以通過 Method 的 getReturnType 方法來獲取返回值類型。

另外還需要注意的是蛮浑,私有成員方法和私有變量一樣唠叛,獲取可以,但是當(dāng)我們需要訪問修改的時候陵吸,必須要繞過權(quán)限檢查即設(shè)置:method.setAccessible(true)

下面我們來一個例子:


//為 TopStudent 添加 testParams 測重載方法
public String testParams(int p) {
   System.out.println("我是 TopStudent 的 testParams(int p) 方法 ," + " p = " + p);
   return String.valueOf(p * 100);
}

try {
       Class<TopStudent> topStudentClass = TopStudent.class;
       TopStudent topStudent = topStudentClass.newInstance();
       
       //調(diào)用 public 方法
       Method isRealDeclareMethod = topStudentClass.getDeclaredMethod("isReal");
       isRealDeclareMethod.invoke(topStudent);

       //調(diào)用私有方法必須繞過權(quán)限檢查 即需要設(shè)置對應(yīng)的 Method 對象的 setAccessible 屬性為 true
       Method testSelfPrivateDeclareMethod = topStudentClass.getDeclaredMethod("testSelfPrivate");
       testSelfPrivateDeclareMethod.setAccessible(true);
       testSelfPrivateDeclareMethod.invoke(topStudent);
       
       Method testParams1 = topStudentClass.getMethod("testParams", double.class);

       //傳入錯誤的參數(shù)類型將會拋出 IllegalArgumentException 異常
       //testParams1.invoke(topStudent,"200");
       testParams1.invoke(topStudent, 200);

       Method testParams2 = topStudentClass.getMethod("testParams", String.class, int.class);
       testParams2.invoke(topStudent, "測試", 200);

       Method testParams3 = topStudentClass.getMethod("testParams", int.class);
       Class<?> returnType = testParams3.getReturnType();
        //returnType = class java.lang.String
       System.out.println("returnType = " + returnType);
       String result = (String) testParams3.invoke(topStudent, 200);//result = 20000

       System.out.println("result = " + result);
       
  } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
       e.printStackTrace();
   }

通過反射獲取目標類的構(gòu)造函數(shù)

通過反射獲取構(gòu)造函數(shù)的方法同樣有4個玻墅,分別為

| 方法名稱 | 返回值類型 | 是否可以獲得私有方法 |
| --- | --- | --: | --: |
| getConstructor() | Constructor<?> | NO |
| getDeclaredConstructor() | Constructor<?> | YES |
| getConstructors() | Constructor<?>[] | NO |
| getDeclaredConstructors() | Constructor<?>[] | YES |

對于構(gòu)造方法的獲取這里沒有指出是否可以獲得父類的構(gòu)造方法,因為 java 規(guī)定壮虫,子類無法繼承父類的構(gòu)造方法澳厢。而對于訪問修飾符的限制,這里跟上文的普通成員函數(shù)沒有什么區(qū)別囚似。

如:

   Class<TopStudent> topStudentClass = TopStudent.class;

   Constructor<?>[] constructors = topStudentClass.getConstructors();
   for (Constructor constructor: constructors) {
       System.out.println("constructor = " + constructor);
   }

   Constructor<?>[] declaredConstructors = topStudentClass.getDeclaredConstructors();
   for (Constructor constructor: declaredConstructors) {
       System.out.println("constructor = " + constructor);
   }
   
   
   try {
       Constructor<TopStudent> isRealConstructor = topStudentClass.getConstructor(boolean.class);
       System.out.println("isRealConstructor = " + isRealConstructor);

       Constructor<TopStudent> gradeConstructor = topStudentClass.getDeclaredConstructor(int.class);
       System.out.println("gradeConstructor = " + gradeConstructor);

       TopStudent topStudent = isRealConstructor.newInstance(false);
       System.out.println("topStudent.isReal = " + topStudent.isReal()); 
    }catch (NoSuchMethodException) {
        e.printStackTrace();
    }
       

運行結(jié)果

constructor = public Reflection.TopStudent(boolean,int)
constructor = public Reflection.TopStudent(boolean)

constructor = public Reflection.TopStudent(boolean,int)
constructor = private Reflection.TopStudent(int)
constructor = public Reflection.TopStudent(boolean)

isRealConstructor = public Reflection.TopStudent(boolean)
gradeConstructor = private Reflection.TopStudent(int)

而我們之前說過通過 Class.newInstance() 可以創(chuàng)建一個類的對象剩拢,但是如果一個類并沒有提供空參數(shù)的構(gòu)造方法,那么這個方法將拋出 InstantiationException 異常饶唤。此時我們就可以通過獲取其他參數(shù)構(gòu)造函數(shù)的方法來獲得對應(yīng)的 Constructor 對象來調(diào)用 Constructor.newInstance(Object... obj)

此方法接受對應(yīng)的構(gòu)造函數(shù)的參數(shù)類型的對象徐伐,如果傳遞的參數(shù)個數(shù)以及類型錯誤將拋出IllegalArgumentException潘拨,類似于 invoke 方法所拋出的異常瞧毙。

try {

        // 如果沒有空構(gòu)造函數(shù),將拋出 InstantiationException 異常
        //  TopStudent topStudent = topStudentClass.newInstance();
       TopStudent topStudent = isRealConstructor.newInstance(false);
       System.out.println("topStudent.isReal = " + topStudent.isReal());

       //調(diào)用私有構(gòu)造函數(shù)的時候必須把對應(yīng)的 Constructor 設(shè)置為  setAccessible(true)
       gradeConstructor.setAccessible(true);
       TopStudent topStudent1 = gradeConstructor.newInstance(1000);
       System.out.println("topStudent.grade = " + topStudent1.grade);
       //傳入錯誤的參數(shù)的個數(shù)的時候?qū)伋?java.lang.IllegalArgumentException
       TopStudent errorInstance = isRealConstructor.newInstance(false, 100);


   } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
       e.printStackTrace();
   }

通過 Class 做類型判斷

在不使用反射的時候欣尼,我們會用 instanceof 關(guān)鍵字來判斷是否為某個類的實例祸穷。當(dāng)我們通過上面的方法獲取了一個對象的 Class 對象性穿,也可以Class對象的 isInstance() 方法來判斷是否為某個類的實例,它是一個 Native方法,使用方法如下:

try {
       Class<Student> studentClass = Student.class;
       //java 中內(nèi)部類的全路徑命名是 OuterClassName$InnerClassName
       Class<?> tearcherClass = Class.forName("ReflectionTest$Teacher");

       Teacher teacher = new Teacher();
       //tearcherClass.isInstance(teacher) true 
       System.out.println("tearcherClass.isInstance(teacher)" + tearcherClass.isInstance(teacher));
   } catch (ClassNotFoundException e) {
       e.printStackTrace();
   }

通過反射獲取注解相關(guān)信息

開頭提到雷滚,學(xué)習(xí)反射可能更大目的不是應(yīng)用于日常開發(fā)需曾,而是為了學(xué)習(xí)一些三方庫的源碼,而這些源碼中,反射往往都是伴隨著注解一塊使用的呆万,這篇文章我們暫時不拿注解展開說商源,只簡單的說下Annotation 相關(guān)的反射 API:

首先在 java.lang.reflect 包下有一個跟提取注解非常相關(guān)的接口,它就是 AnnotatedElement 接口谋减,那么實現(xiàn)該接口的對象有哪些呢牡彻?其實它包含了上述我們所說的 ClassConstructor逃顶、 Field讨便、Method幾個類。其實了解注解可以修飾哪些成員的朋友對此并不難理解以政,注解可以修飾一個類霸褒,一個方法,一個成員盈蛮,所以當(dāng)我們需要自定義注解的時候废菱,如果拿到對應(yīng)的成員或者類的注解便是關(guān)鍵。

AnnotatedElement 接口定義了一下幾個方法:

方法名 參數(shù) 返回值 作用
isAnnotationPresent 注解修飾的元素的 Class boolean 檢測該元素是否被參數(shù)對應(yīng)注解修飾
getAnnotation 注解修飾的元素的 Class Annotation 返回注解對象
getAnnotations Annotation[] 返回該程序元素上存在的所有注解(如果沒有注解存在于此元素上抖誉,則返回長度為零的一個數(shù)組殊轴。)
getDeclaredAnnotations Annotation[] 返回直接存在于此元素上的所有注解。與此接口中的其他方法不同袒炉,該方法將忽略繼承的注解旁理。(如果沒有注解直接存在于此元素上,則返回長度為零的一個數(shù)組我磁。)

下面我們通過一個簡單的例子來了解下如何通過反射獲取注解:

假設(shè)我們有一個這樣的自定義注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
protected @interface FruitName {
   String value();
}

且我們定義了一個 Apple:

public class Apple {
   @FruitName(value = "Apple")
   public String name;
}

并有下列方法用來查看注解信息:

public static void getFruitInfo(Class<?> clazz) {
   try {
       String fruitName = "水果名稱:";

       Field field = clazz.getDeclaredField("name");
       java.lang.annotation.Annotation[] annotations = field.getAnnotations();
       System.out.println("annotations = " + Arrays.toString(annotations));
       if (field.isAnnotationPresent(FruitName.class)) {
           FruitName fruitNameAnno = field.getAnnotation(FruitName.class);
           fruitName = fruitName + fruitNameAnno.value();
           System.out.println(fruitName);
       }
   } catch (NoSuchFieldException e) {
       e.printStackTrace();
   }
}

得到打印結(jié)果為

annotations = [@Annotation$FruitName(value=Apple)]
水果名稱:Apple

總結(jié)

本文分析了一些常見的反射 API 的使用孽文。這些并不是全部的 API。網(wǎng)上也有很多其他的反射的講解也都不錯夺艰。本文出發(fā)點想要與眾不同芋哭,但是寫著寫著就"同流合污"了。 本來想說明反射中的泛型擦除郁副,也想加上動態(tài)代理减牺,雖然這兩個知識點和反射有著很大的聯(lián)系,但是兩個都可獨立成文存谎。所以僅當(dāng)此篇是一份學(xué)習(xí)記錄拔疚,方便以后查閱吧。最后放出我所查看過一些不錯的反射文章既荚。

一些不錯的反射介紹文章:

深入解析Java反射(1) - 基礎(chǔ)
Java 反射由淺入深 | 進階必備
JAVA反射與注解
反射技術(shù)在android中的應(yīng)用
細說反射草雕,Java 和 Android 開發(fā)者必須跨越的坎
Thinking in Java
Java 核心技術(shù)卷 I

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市固以,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖憨琳,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诫钓,死亡現(xiàn)場離奇詭異,居然都是意外死亡篙螟,警方通過查閱死者的電腦和手機菌湃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遍略,“玉大人惧所,你說我怎么就攤上這事⌒餍樱” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵蕾久,是天一觀的道長势似。 經(jīng)常有香客問我,道長僧著,這世上最難降的妖魔是什么履因? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮盹愚,結(jié)果婚禮上栅迄,老公的妹妹穿的比我還像新娘。我一直安慰自己皆怕,他們只是感情好毅舆,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著端逼,像睡著了一般朗兵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上顶滩,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天余掖,我揣著相機與錄音,去河邊找鬼礁鲁。 笑死盐欺,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仅醇。 我是一名探鬼主播冗美,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼析二!你這毒婦竟也來了粉洼?” 一聲冷哼從身側(cè)響起节预,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎属韧,沒想到半個月后安拟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡宵喂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年糠赦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锅棕。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡拙泽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出裸燎,到底是詐尸還是另有隱情顾瞻,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布顺少,位于F島的核電站朋其,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脆炎。R本人自食惡果不足惜梅猿,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秒裕。 院中可真熱鬧袱蚓,春花似錦、人聲如沸几蜻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梭稚。三九已至颖低,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弧烤,已是汗流浹背忱屑。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留暇昂,地道東北人莺戒。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像急波,于是被迫代替她去往敵國和親从铲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容