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

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

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

前言

本文將重拾 Java 基礎(chǔ)中的反射知識,由于筆者是做移動端 Android 開發(fā)的,日常工作中反射用的少的可以拿手指頭數(shù)過來?,F(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");

通過反射獲取目標(biāo)的類的成員變量

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 類也為我們準(zhǔn)備了相應(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 萬一給后人給自己挖了個大坑埋了呢。

通過反射獲取目標(biāo)類的成員方法

獲取目標(biāo)類的成員方法

除了通過 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)用目標(biā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();
   }

通過反射獲取目標(biāo)類的構(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)該接口的對象有哪些呢?其實它包含了上述我們所說的 Class、Constructor、 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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容