注解與反射
注解(Java.Annotation)
注解入門
什么是注解
- Annotation是從JDK5.0引入的技術
- Annotation的作用:
- 對程式作出解釋
- 可以被其他程式(如:編譯器等)讀取(使用反射機制)
- Annotation的格式
- 以“
@注解名”在代碼中存在,還可以添加一些引數值
- 以“
- Annotation的使用
- 注解有檢查和約束的作用
- 可以附加在package,class,method,field等上面,相當于給它們添加了額外的輔助資訊,可以通過反射機制來實作對這些元資料的訪問
內置注解
java中最常見的三種注解
@Overide:定義在java.lang.Override中,此注解只使用于修飾方法,表示一個方法宣告打算重寫超類中的另一個方法宣告@Deprecated:定義在java.lang.Deprecated中,此注解可以用于修飾方法,屬性,類,表示不推薦使用這樣的元素,因為它很危險或者存在更好的選擇@SuppressWarnings:定義在java.lang.SuppressWarmings中,用來抑制編譯時的警告資訊,與前兩個注解有所不同,你需要添加一個引數才能正確的使用,這些引數是已經定義好了的,可以選擇性的使用@SuppressWarnings("all")@SuppressWarnings("unchecked")@SuppressWarnings(value=https://www.cnblogs.com/whitezeroxz/archive/2020/11/06/{"unchecked", "deprecation"})- ......
元注解
元注解就是負責注解其他注解的注解,Java定義了4個標準的meta-annotation型別,被用來提供對其他Annotation型別做說明,這些型別和它們所支持的類在java.lang.annotation包中
@Target:用于描述注解的使用范圍(即:該注解可以用在哪些地方)@Target(ElementType.TYPE)——介面、類、列舉、注解@Target(ElementType.FIELD)——欄位、列舉的常量@Target(ElementType.METHOD)——方法@Target(ElementType.PARAMETER)——方法引數@Target(ElementType.CONSTRUCTOR)——建構式@Target(ElementType.LOCAL_VARIABLE)——區域變數@Target(ElementType.ANNOTATION_TYPE)——注解@Target(ElementType.PACKAGE)——包
@Retention:表示需要在什么級別保存該注解資訊,用于描述注解的生命周期@Retention(RetentionPolicy.SOURCE)@Retention(RetentionPolicy.CLASS)@Retention(RetentionPolicy.RUNTIME)- 生命周期長度:SOURCE < CLASS < RUNTIME,前者能作用的地方后者一定也能作用,
- SOURCE:這種型別的注解只在源代碼級別保留,編譯時就會被忽略,在class位元組碼檔案中不包含,如果只是做一些檢查性的操作,比如
@Override和@SuppressWarnings,則可選用 SOURCE 注解 - CLASS:這種型別的注解編譯時被保留,默認的保留策略,在class位元組碼檔案中存在,但
JVM將會忽略,運行時無法獲得,如果要在編譯時進行一些預處理操作,比如生成一些輔助代碼(為欄位添加setter方法),就用 CLASS注解 - RUNTIME:這種型別的注解將被
JVM保留,所以他們能在運行時被JVM或其他使用反射機制的代碼所讀取和使用,一般如果需要在運行時去動態獲取注解資訊,那只能用 RUNTIME 注解
- SOURCE:這種型別的注解只在源代碼級別保留,編譯時就會被忽略,在class位元組碼檔案中不包含,如果只是做一些檢查性的操作,比如
@Documented:說明該注解將被包含在Javadoc中@Documented用于描述注解應該被作為被標注的程式成員的公共API,因此可以被Javadoc此類的工具檔案化,@Documented是一個標記注解,沒有引數成員
@Inherited:說明子類可以繼承父類中的該注解- 與
@Documented一樣是一個標記注解,@Inherited闡述了某個被標注的注解是被繼承的,如果一個使用了@Inherited修飾的注解被用于一個class,則這個注解將被用于該class的子類
- 與
自定義注解
使用@interface自定義注解,自動繼承了java.lang.annotation.Annotation介面
@interface用來宣告一個注釋,格式:public @interface 注釋名{定義內容}- 只能用public或者默認(default)這兩個訪問修飾符來修飾
- 注解引數的可支持資料型別:
- 基本資料型別(int,float,boolean,byte,double, char,long,short)
- String型別
- Class型別
- enum型別
- Annotation型別
- 以上所有型別的陣列
- 可以通過default來宣告引數的默認值
- 如果只有一個引數成員,引數名通常為value
- 注解元素必須有確定的值,要么在定義注解的默認值中指定,要么在使用注解時指定,非基本型別的注解元素的值不可為null,使用空字串或0作為默認值是一種常用的做法,可以用來表現這個元素的存在或缺失狀態
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Documented;
/**
* 用戶名注解
* @author zerocode
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserName
{
String value() default "";
}
反射(Java.Reflection)
Reflection(反射)允許程式在執行期間借助Reflection API取得任何類的內部資訊(包括private權限修飾符修飾的方法和屬性),并能直接操作任意類的內部屬性及方法
在加載完類之后,在堆記憶體的方法區中就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的類的結構資訊,我們可以通過這個物件看到類的結構,
class位元組碼檔案 -> Class物件
Class物件的由來是將class位元組碼檔案讀入記憶體,并為之創建一個Class物件,
什么是反射
借助類加載產生的Class物件(其中有類的資訊)來獲知類的結構(成員變數,方法,構造方法,等資訊)各個部分,映射成一個個的物件,拿到這些物件后可以做一些事情,
Java反射的優點和缺點
- 優點
- 在運行時獲得類的各種資訊,能夠讓我們很方便的創建靈活的代碼,
- 缺點
- 反射會消耗一定的系統資源,如果不用動態的獲取一個物件就不用反射,
- 反射呼叫方法是可以忽略權限檢查,導致安全問題,
反射的用途
-
反編譯:.class -> .java
-
通過反射機制訪問物件java物件的屬性,方法,構造方法等
-
一般來說反射是用來做框架的,或者說可以做一些抽象度比較高的底層代碼,反射在日常的開發中用到的不多,但是咱們還必須搞懂它,因為搞懂了反射以后,可以幫助理解框架的一些原理,所以說有一句很經典的話:反射是框架設計的靈魂,為了保證框架的通用性,他們可能需要配置加載不同的類或者物件,呼叫不同的方法,這個時候就必須使用到反射,通過運行時動態加載需要加載的物件,
例如:在使用Strut2框架的開發程序中,一般使用struts.xml里去配置Action,比如
<action name="login" method="execute"> <result>/shop/shop-index.jsp</result> <result name="error">login.jsp</result> </action>
反射機制中常用的類
Java.lang.Class;Java.lang.reflect.Constructor;Java.lang.reflect.Field;Java.lang.reflect.Method;Java.lang.reflect.Modifier;
Class類
Java運行時系統時鐘為所有物件維護一個RTTI(運行時型別標識,Run-Time Type Identification),會跟蹤每個物件所屬的類,虛擬機利用該資訊選擇要執行的正確方法,
每個類的運行時的型別資訊就是用Class物件表示的,它包含了與類有關的資訊,其實我們的實體物件就通過Class物件來創建的,
每一個類都有一個Class物件,程式運行時每當加載一個新類就產生一個Class物件,基本型別 (boolean, byte, char, short, int, long, float, and double)有Class物件,陣列有Class物件,就連關鍵字void也有Class物件(void.class),
Employee e;
...
Class c = e.getClass();
就像Employee物件e描述一個特定員工的屬性一樣,一個Class物件c1描述一個特定類的屬性(特定類的類名,特定類包含的欄位,特定類包含的方法等),
從Java記憶體來理解Class類
Java記憶體
由于Java程式是交由JVM執行的,所以我們在談Java記憶體區域劃分的時候事實上是指JVM記憶體區域劃分,
Java程式執行流程:

如圖所示,Java原始碼檔案被Java編譯器編譯為位元組碼檔案,然后由JVM中的類加載器加載各個類的位元組碼檔案,加載后交由JVM執行引擎執行,執行程序中,JVM用一段空間來存盤程式執行期間需要用到的資料和相關資訊,這段空間被稱為運行時資料區,也就是常說的Java記憶體,因此,在Java中我們常常說的記憶體管理就是針對這段空間進行管理(如何分配和回收記憶體空間),
Java記憶體分為幾個部分
根據 JVM 規范,JVM 記憶體共分為虛擬機堆疊、堆、方法區、程式計數器、本地方法堆疊五個部分,

| 名稱 | 特征 | 作用 |
|---|---|---|
| 程式計數器 | 占用記憶體小,執行緒私有,生命周期與執行緒相同 | 大致為位元組碼行號指示器 |
| 虛擬機堆疊 | 執行緒私有,生命周期與執行緒相同,使用連續的記憶體空間 | Java 方法執行的記憶體模型,存盤區域變數表、操作堆疊、動態鏈接、方法出口等資訊 |
| 堆 | 執行緒共享,生命周期與虛擬機相同,可以不使用連續的記憶體地址 | 保存物件實體(包括Class物件),所有物件實體(包括陣列)都要在堆上分配 |
| 方法區 | 執行緒共享,生命周期與虛擬機相同,可以不使用連續的記憶體地址 | 存盤已被虛擬機加載的類資訊、常量、靜態變數、即時編譯器編譯后的代碼等資料,包含了所有的Class資訊和static變數 |
| 運行時常量池 | 方法區的一部分,具有動態性 | 存放字面量及符號參考 |
類加載的程序
當程式主動使用某個類,如果類未被加載到記憶體中,系統會通過三個步驟來對類進行初始化,
-
類的裝載(Load)
將class位元組碼檔案內容(類的描述資訊)加載到方法區中,并將這些靜態資料轉換為方法區運行時資料結構,然后在堆中生成一個代表這個類的Class物件
-
類的鏈接(Link)
將類的二進制代碼合并到JVM的運行狀態中的程序
- 驗證:確保加載的類資訊符合JVM規范,有沒有安全方面的問題
- 準備:正式為類變數(static)分配記憶體并設定默認初始值(并沒有開始賦值,僅設定為默認值)的階段,這些空間都將在方法區中進行分配,另外如果是final 修飾的常量,此時一并直接賦值
- 決議:運行時常量池內的符號參考(常量)替換為直接參考(地址)的程序
-
類的初始化(Initialize)
- 執行類構造器(類構造器:構造類資訊,不是構造類物件的構造器)
<clinit>()方法的程序,<clinit>()方法是有編譯器自動收集類中所有類變數(static)的賦值動作和靜態初始化塊中的陳述句合并產生的, - 初始化一個類的時候,如果發現父類還沒有初始化,則需要先觸發其父類的初始化,
- 虛擬機會保證一個類的
<clinit>()方法在多執行緒環境中被正確的加鎖和同步 <clinit>()對于類或者介面并不是必須的,如果一個類沒有靜態陳述句塊也沒有對變數的賦值操作編譯器可以不為這個類生成<clinit>()方法
- 執行類構造器(類構造器:構造類資訊,不是構造類物件的構造器)
類加載的時機
什么時候類加載 :第一次需要使用類資訊時加載,
類加載的原則:延遲加載,能不加載就不加載,
觸發類加載的幾種情況
-
虛擬機啟動,先加載main方法所在的類
-
當第一次new一個類的物件時
-
呼叫這個類的靜態成員(final常量除外)和靜態方法
通過子類呼叫父類的靜態成員時,只會加載父類而不會加載子類
-
呼叫java.lang.reflect包的方法對類進行反射呼叫
-
加載一個類,其父類沒有被加載,優先加載其父類
package com.zerocode.reflection;
public class Test
{
static
{
System.out.println("加載Test類");
}
public static void main(String[] args)
{
System.out.println("進入main方法");
System.out.println("通過Cat呼叫SuperCat類的靜態成員");
var test = Cat.superCatStaticField1;
System.out.println("開始new一個Dog類的物件");
new Dog();
System.out.println("開始獲取Cat的Class物件");
try
{
Class.forName("com.zerocode.reflection.Cat");
}
catch (ClassNotFoundException e)
{
System.out.println("沒有找到Cat類");
}
System.out.println("結束main方法");
}
}
class SuperCat {
public static int superCatStaticField1 = 1;
static
{
System.out.println("加載SuperCat類");
}
}
class Cat extends SuperCat {
static
{
System.out.println("加載Cat類");
}
}
class SuperDog {
static
{
System.out.println("加載SuperDog類");
}
}
class Dog extends SuperDog {
static
{
System.out.println("加載Dog類");
}
}
/* Output:
加載Test類
進入main方法
通過Cat呼叫SuperCat類的靜態成員
加載SuperCat類
開始new一個Dog類的物件
加載SuperDog類
加載Dog類
開始獲取Cat的Class物件
加載Cat類
結束main方法
*/
從輸出中可以看到,Class物件僅在需要的時候才被加載,static初始化是在類加載的時候進行,
Class.forName的好處就在于,不需要為了獲得Class參考而持有該型別的物件,只要通過全限定名就可以回傳該型別的一個Class參考
類加載的演示:
package com.zerocode.reflection;
public class Test
{
static
{
testStaticField1 = 1;
}
static int testStaticField1 = 2;
public static void main(String[] args)
{
Cat cat = new Cat();
System.out.println("輸出Test類的靜態成員的值:" + Test.testStaticField1);
}
}
class Cat {
static int catStaticField1 = 1;
public int catField1 = 1;
public Cat()
{
catField1 = 2;
}
}









可以看到在類加載的程序中也有Class類的參與,而使用反射的程序就是通過Class物件反向獲取類的資訊,若這個類的資訊之前沒有加載,就會執行該類的類加載,

獲取Class類物件
- Object -> getClass
- 任何資料型別(包括基本的資料型別)都有一個靜態class屬性
- 通過Class類的靜態方法:
forName(String className) - 基本型別的包裝類有一個TYPE屬性
package com.zerocode.reflection;
public class Test
{
public static void main(String[] args)
{
// 第一種獲取Class物件的方法
Cat cat = new Cat();
Class catClass1 = cat.getClass();
// 第二種獲取Class物件的方法
Class catClass2 = Cat.class;
// 第三種獲取Class物件的方法
try
{
Class catClass3 = Class.forName("com.zerocode.reflection.Cat");
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
// 第四種獲取Class物件的方法
Class intClass1 = Integer.TYPE;
}
}
class Cat{}
四種方式中最常用的就是第三種,第一種:不常用,一般用在想要去獲取該物件的類的名字等資訊,例如:System.out.println("The class of " + obj + " is " + obj.getClass().getName());,第二種:需要匯入類包,第三種:通過帶包名的類路徑一個字串可以傳入,也可以寫在組態檔中等多種方式,第四種與第二種方式類似,不過是基本型別中特有的,
Class類的常用方法
-
static Class forNmae(String className);回傳與給定字串名稱的類或介面相關聯的Class物件,
-
Object newInstance();創建由此Class物件表示的類的新實體(使用無參構造器)
-
Class getSuperClass();獲取由此Class物件表示的類的父類
-
Class[] getInterface();獲取由此Class物件表示的類實作的介面
-
ClassLoader getClassLoader();獲取加載由此Class物件表示的類的類加載器
-
String getName();獲取Class物件所表示的物體(類,介面,陣列類或void)的名稱
-
int getModifiers();回傳此類或介面的修飾符,以整數編碼,可以利用
Modifier.toString()方法將修飾符列印出來 -
Field[] getFields()、Mehtod[] getMethods()和Constructors[] getConstructors()方法將分別回傳這個類支持的公共欄位、方法和構造器陣列,其中包括超類的公共成員, -
Field[] getDeclareFileds()、Mehtod[] getDeclareMethods()和Constructors[] getDeclareConstructors()方法將回傳類中宣告的全部欄位、方法和構造器的陣列,其中包括公共成員(public)、私有成員(private)、包成員(default)和受保護成員(protected),但不包括超類的成員,
反射的基本使用
使用Class物件創建對應類的實體
-
使用Class物件的newInstance()方法來創建Class物件對應類的實體,使用的是無參構造器來創建物件
Class<?> c = String.class; object str = c.newInstance(); -
先通過Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來創建物件,這種方法可以指定構造器來構造類的實體
Class<?> str = String.class; // 獲取String的Class物件 Constructor constructor = str.getConstructor(String.class);// 指定形參為String的構造器 Object obj = constructor.newInstance("hello reflection");// 通過構造器創建實體
使用反射分析類的結構
列印一個一個類的全部資訊,這個程式根據輸入的類名,然后輸出類中所有的方法和構造器的簽名,以及全部實體欄位名,
package com.zerocode.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;
public class Test{
public static void main(String[] args)
throws ClassNotFoundException
{
String className;
if (args.length > 0)
{
className = args[0];
}
else
{
var in = new Scanner(System.in);
System.out.print("輸入類名(例子:java.util.Date):");
className = in.next();
}
// 獲取類的Class物件
Class cl = Class.forName(className);
// 列印修飾符部分
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0)
{
System.out.print(modifiers + " ");
}
// 列印類的名字
String name = cl.getName();
System.out.print("class " + name);
// 列印類的繼承關系,如果列印的類是Object或者列印的類的父類為Object則不列印繼承關系
Class superCl = cl.getSuperclass();
if (superCl != null && superCl != Object.class)
{
System.out.print(" extends " + superCl.getName());
}
System.out.println();
System.out.println("{");
// 分別列印構造器,類方法,欄位
printConstructors(cl);
System.out.println();
printMethods(cl);
System.out.println();
printField(cl);
System.out.println("}");
}
/**
* 列印Class物件對應的類的構造器方法
* @param cl 需要列印構造器方法的CLass物件
*/
public static void printConstructors(Class cl)
{
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor c: constructors)
{
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if(modifiers.length() > 0)
{
System.out.print(modifiers + " ");
}
String name = c.getName();
System.out.print(name + "(");
Class[] paramTypes = c.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++)
{
if (i > 0)
{
System.out.print(", ");
}
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
/**
* 列印Class物件對應的類的方法
* @param cl 需要列印方法的CLass物件
*/
public static void printMethods(Class cl)
{
Method[] methods = cl.getDeclaredMethods();
for (Method m: methods)
{
System.out.print(" ");
String modifiers = Modifier.toString(m.getModifiers());
if (modifiers.length() > 0)
{
System.out.print(modifiers + " ");
}
// 獲取回傳值的型別
Class returnType = m.getReturnType();
System.out.print(returnType.getName() + " ");
String name = m.getName();
System.out.print(name + "(");
Class[] paramTypes = m.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++)
{
if (i > 0)
{
System.out.print(", ");
}
System.out.print(paramTypes[i].getName());
}
System.out.println(");");
}
}
/**
* 列印Class物件對應的類的欄位
* @param cl 需要列印欄位的CLass物件
*/
public static void printField(Class cl)
{
Field[] fields = cl.getDeclaredFields();
for (Field f: fields)
{
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
if (modifiers.length() > 0)
{
System.out.print(modifiers + " ");
}
Class type = f.getType();
System.out.print(type.getName() + " ");
String name = f.getName();
System.out.println(name + ";");
}
}
}
/* Output:
輸入類名(例子:java.util.Date):java.lang.Integer
public final class java.lang.Integer extends java.lang.Number
{
public java.lang.Integer(java.lang.String);
public java.lang.Integer(int);
public static int numberOfLeadingZeros(int);
public static int numberOfTrailingZeros(int);
public static int bitCount(int);
public boolean equals(java.lang.Object);
public static java.lang.String toString(int);
public static java.lang.String toString(int, int);
public java.lang.String toString();
public static int hashCode(int);
public int hashCode();
public static int min(int, int);
public static int max(int, int);
public static int reverseBytes(int);
static int getChars(int, int, [B);
public volatile int compareTo(java.lang.Object);
public int compareTo(java.lang.Integer);
public byte byteValue();
public short shortValue();
public int intValue();
public long longValue();
public float floatValue();
public double doubleValue();
public static java.lang.Integer valueOf(int);
public static java.lang.Integer valueOf(java.lang.String, int);
public static java.lang.Integer valueOf(java.lang.String);
public static java.lang.String toHexString(int);
public static java.lang.Integer decode(java.lang.String);
public static int compare(int, int);
public static int reverse(int);
static int stringSize(int);
public static long toUnsignedLong(int);
public static int parseInt(java.lang.String);
public static int parseInt(java.lang.String, int);
public static int parseInt(java.lang.CharSequence, int, int, int);
public static int sum(int, int);
public static int compareUnsigned(int, int);
private static java.lang.String toStringUTF16(int, int);
public static java.lang.String toUnsignedString(int, int);
public static java.lang.String toUnsignedString(int);
public static java.lang.String toOctalString(int);
public static java.lang.String toBinaryString(int);
private static java.lang.String toUnsignedString0(int, int);
static void formatUnsignedInt(int, int, [C, int, int);
static void formatUnsignedInt(int, int, [B, int, int);
private static void formatUnsignedIntUTF16(int, int, [B, int, int);
public static int parseUnsignedInt(java.lang.String, int);
public static int parseUnsignedInt(java.lang.CharSequence, int, int, int);
public static int parseUnsignedInt(java.lang.String);
public static java.lang.Integer getInteger(java.lang.String, java.lang.Integer);
public static java.lang.Integer getInteger(java.lang.String, int);
public static java.lang.Integer getInteger(java.lang.String);
public static int divideUnsigned(int, int);
public static int remainderUnsigned(int, int);
public static int highestOneBit(int);
public static int lowestOneBit(int);
public static int rotateLeft(int, int);
public static int rotateRight(int, int);
public static int signum(int);
public static final int MIN_VALUE;
public static final int MAX_VALUE;
public static final java.lang.Class TYPE;
static final [C digits;
static final [B DigitTens;
static final [B DigitOnes;
static final [I sizeTable;
private final int value;
public static final int SIZE;
public static final int BYTES;
private static final long serialVersionUID;
}
*/
使用反射在運行時分析物件
同一個型別的幾個型別中最大的不同就是其中欄位的值的不同
要做到這一點,關鍵方法是Field類中的get方法,如果f是一個Field類的物件,f.get(obj)將回傳一個物件,為物件obj的中f所表示的欄位的欄位值
package com.zerocode.reflection;
import java.lang.reflect.Field;
public class Test{
public static void main(String[] args)
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
{
// new一個Cat物件
Cat cat = new Cat("Tom", 12);
// 獲取Cat類的Class物件
Class catClass = Class.forName("com.zerocode.reflection.Cat");
// 從Cat類的Class物件中獲取name欄位的抽象
Field name = catClass.getDeclaredField("name");
// name欄位的抽象中有一個get方法可以獲取傳入的Cat物件中的name欄位的值
Object catName = name.get(cat);
}
}
class Cat{
public String name;
public int age;
public Cat(String name, int age)
{
this.name = name;
this.age = age;
}
}
不僅可以獲得值,也可以設定值,呼叫f.set(obj, value)將把物件obj中的f所表示的欄位設定為新值
但是如果f是一個私有欄位,那么get和set方法就會拋出一個IlleaglAccessException,Java安全機制允許查看一個物件有哪些欄位,但是除非擁有訪問權限,否則不允許讀寫那些欄位的值,
反射機制默認是受限于Java的訪問控制的,不過,可以呼叫Field、Method或Constructor物件的setAccessible方法覆寫Java的訪問控制,
package com.zerocode.reflection;
import java.lang.reflect.Field;
public class Test{
public static void main(String[] args)
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
{
Cat cat = new Cat("Tom", 12);
Class catClass = Class.forName("com.zerocode.reflection.Cat");
Field name = catClass.getDeclaredField("name");
// 在呼叫get方法前呼叫setAccessible方法覆寫Java的訪問控制
name.setAccessible(true);
Object catName = name.get(cat);
}
}
class Cat
{
private String name;
private int age;
public Cat(String name, int age)
{
this.name = name;
this.age = age;
}
}
使用反射呼叫任意方法
java.lang.reflection.Method有一個invoke方法,允許你呼叫包裝在當前Method物件中的方法,invoke方法的簽名是:
Object invoke(Object obj, Object... args)
假設用Method類的ml物件,包裝Cat類的setName方法,
Cat cat = new Cat(...);
ml.invoke(cat, "Tom");
如何得到Method物件
-
呼叫Class物件的
getDeclaredMethods方法,然后搜索回傳的Method物件陣列,直到發現想要的方法為止 -
呼叫Class物件的
getMethod方法,提供想要方法的名字,有鑒于可能存在若干個同名的方法,可以提供想要方法的引數型別,簽名:
Method getMethod(String name, Class... parameterTypes);Method m1 = Cat.class.getMethod("getName");Method m2 = Cat.class.getMethod("setName", String.class);
使用反射動態拓展陣列
當一個陣列的空間滿了之后可以使用下面的代碼進行擴展
int[] array = {1, 2, 3};
int[] newArray = new int[array.length * 2];
// 將array的值復制給擴充的陣列
System.arraycopy(array, 0, newArray, 0, array.length);
那么有沒有通用的辦法來擴展任意型別的陣列,而不僅只能根據背景關系來撰寫擴展陣列的代碼
java.lang.reflection.Array類允許動態地創建陣列,java.util.Arrays類中的copyOf方法實作就使用了這個類,
package com.zerocode.reflection;
import java.lang.reflect.Array;
import java.util.Arrays;
public class Test{
public static void main(String[] args)
{
String[] names = {"Tom", "Jerry", "Spike"};
String[] newNames = (String[]) goodCopyOf(names, 5);
System.out.println(Arrays.toString(newNames));
int[] ages = {1, 2, 3};
int[] newAges = (int[]) goodCopyOf(ages, 5);
System.out.println(Arrays.toString(newAges));
// 不能強制轉型,如果使用強制轉型產生例外
Object[] newObjects = badCopyOf(names, 5);
}
// 僅能擴展物件陣列
public static Object[] badCopyOf(Object[] original, int newLength)
{
Object[] newArray = new Object[newLength];
System.arraycopy(original, 0, newArray, 0, Math.min(original.length, newLength));
return newArray;
}
// 可以擴展任意型別的陣列,而不僅是物件陣列
public static Object goodCopyOf(Object original, int newLength)
{
Class cl = original.getClass();
//由于引數宣告為Object,所以需要判斷original是否為陣列型別,
if (!cl.isArray())
{
return null;
}
Class componentType = cl.getComponentType();
int length = Array.getLength(original);
// java.lang.reflection.Array允許動態的創建陣列,
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(original, 0, newArray, 0, Math.min(newLength, length));
return newArray;
}
}
使用反射獲取注解資訊
反射和泛型
反射允許在運行時分析任意物件,如果物件是泛型類的實體,關于泛型型別引數得不到太多資訊,因為它們已經被擦除了,
Class類是泛型的,例如:String.class獲取的就是Class<String>類的物件,
引數型別十分有用,這是因為它允許Class<T>的方法的回傳型別更具有特定性,這樣就免除了型別轉換,
使用Class<T>引數創建Pair<T>物件
public static <T> Pair<T> makePair(Class<T> cl) throws
IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
return new Pair<>(cl.newInstance(), cl.newInstance());
}
如果呼叫makePair(Cat.class),Cat.class將是一個Class<T>物件,makePair的型別變數T同Cat匹配,編譯器可以根據背景關系推斷出這個方法將回傳Pair<Cat>,
使用反射獲取泛型資訊
Java泛型的突出特性之一就是型別擦除,Java泛型僅僅是給編譯器javac使用的,當編譯完成后,所有和泛型有關的型別全部擦除,
為了通過反射操作泛型型別,可以通過java.lang.reflection包中的介面Type包含了如下子型別,
-
Class類:描述具體型別 -
TypeVariable介面:描述型別變數(如:T extends Comparable<? super T>) -
WildcardType介面:描述通配符(如?,? extends Number,或? super Integer) -
ParameterizedType介面:描述泛型類或泛型介面(如:Comparable<? super T>) -
GenericArrayType介面:描述泛型陣列(如:T[])
參考視頻、博客以及書籍
- 【狂神說Java】注解和反射:https://www.bilibili.com/video/BV1p4411P7V3
- 《Java核心技術——卷1》
- Java基礎篇:反射機制詳解:https://blog.csdn.net/a745233700/article/details/82893076
- 用最直接的大白話來聊一聊Java中的反射機制:https://blog.csdn.net/ju_362204801/article/details/90578678
- Class類 和 class物件(運行時的型別資訊):https://blog.csdn.net/laomo_bible/article/details/83067810
- JVM的Class物件的存盤位置和作用:https://blog.csdn.net/joker_cc/article/details/91948058
- java 虛擬機記憶體劃分,類加載程序以及物件的初始化:https://www.cnblogs.com/noteless/p/9262200.html
- JVM基礎(三)一個物件的創建程序:https://zhuanlan.zhihu.com/p/142614439
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/204731.html
標籤:其他
