文章首發(fā)我的博客怠褐,歡迎訪問:https://blog.itzhouq.cn/annotation-reflection
最近又回顧了一下 Java 中的注解和反射知識點较剃,注解在日常開發(fā)中使用很多,但是反射比較少嚣潜。值得注意的是 Java 的各種框架底層源碼中大量使用了注解和反射,閱讀源碼,這些是基本功赂毯,面試中這部分內(nèi)容也經(jīng)常問到。這里面概念不多,內(nèi)容略微有些枯燥党涕,但是通過一些簡單的例子烦感,能讓我們明白一些基本概念和 API 的使用。所以膛堤,說到底手趣,這篇博客只能算是一個簡單的筆記,希望對你有幫助肥荔。 以前也寫過枚舉類和注解的相關(guān)筆記绿渣,可以看看歷史文章 Java枚舉類和注解梳理。
1燕耿、什么是注解
注解 Annotation 是從JDK1.5 開始引入的新技術(shù)中符。
注解的作用:不是程序本身,可以對程序作出解釋誉帅,能被其他程序讀取到淀散。
注解使用的位置:package、class蚜锨、method吧凉、field 等上面,相當(dāng)于給他們添加了額外的輔助信息踏志。我們可以通過反射機制實現(xiàn)對這些元數(shù)據(jù)的訪問阀捅。
2、元注解
元注解的作用就是負(fù)責(zé)注解其他的注解针余,Java 定義了 4 個標(biāo)準(zhǔn)的 meta-Annotation類型饲鄙,他們被用來提供對其他 Annotation 類型做說明。
-
Target
:用于描述注解的使用范圍圆雁,注解可以用在什么地方忍级。隨便點擊一個注解,查看源碼可以看到這個位置使用ElementType
枚舉類表示伪朽,主要可以放在類上轴咱,方法上,屬性上等烈涮,我這就不細(xì)說了朴肺。 -
Retention
:表示在什么級別保存該注解的信息,用于描述注解的生命周期坚洽。SOURCE < CLASS <RUNTIME
戈稿。同樣看源碼,使用RetentionPolicy
枚舉類表示讶舰,有SOURCE
鞍盗,CLASS
需了,RUNTIME
“慵祝基本見名知意肋乍。 -
Document
:說明該注解會被包含在 Javadoc中。 -
Inherited
:說明子類可以繼承父類的該注解敷存。
3住拭、自定義注解
使用 @interface
自定義注解時,自動繼承了 java.lang.annotation.Annotation
接口历帚。
分析:
-
@interface
:用來聲明一個注解滔岳,格式:public @interface 注解名{定義內(nèi)容}
; - 其中的每一個方法實際上就是一個配置參數(shù)挽牢;
- 方法的名稱就是參數(shù)的名稱谱煤;
- 返回的類型就是參數(shù)的類型(返回值只能是基本類型、Class禽拔、String刘离、enum);
- 可以通過 default 來聲明參數(shù)的默認(rèn)值睹栖;
- 如果只有一個參數(shù)成員硫惕,一般參數(shù)名為 value;
- 注解元素必須要有值野来,我們定義注解元素時恼除,經(jīng)常使用空字符串,0作為默認(rèn)值曼氛。
// 自定義注解
public class Test {
@MyAnnotation(age = 18, name = "Hello")
public void test() {}
}
@interface MyAnnotation {
// 注解的參數(shù):參數(shù)類型 + 參數(shù)名();
String name() default "";
int age();
int id() default -1;
String[] schools() default {"清華大學(xué)", "北京大學(xué)"};
}
4豁辉、什么是反射
反射(Reflection):是 Java 被視為動態(tài)語言的關(guān)鍵,反射機制允許程序在執(zhí)行期借助于 Reflection API 取得任何類的內(nèi)部信息舀患,并能直接操作任意對象的內(nèi)部屬性及方法徽级。
加載完類后,在堆內(nèi)存的方法區(qū)中就產(chǎn)生了一個 Class 類型的對象(一個類只有一個 Class 對象)聊浅,這個對象包含了完整的類的結(jié)構(gòu)信息餐抢。我們可以通過這個對象看到類的結(jié)構(gòu)。這個對象就像一面鏡子低匙,透過和鏡子看到類的結(jié)構(gòu)旷痕。所以我們形象地稱之為反射。
Java發(fā)射的優(yōu)缺點:
優(yōu)點:可以實現(xiàn)動態(tài)創(chuàng)建對象和編譯努咐,體現(xiàn)很大的靈活性苦蒿;
缺點:對性能有影響殴胧,使用反射基本上是一種解釋操作渗稍,這類操作總是慢于直接執(zhí)行相同的操作佩迟。
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
// 通過反射獲取類的Class對象
Class c1 = Class.forName("Test01");
Class c2 = Class.forName("Test01");
System.out.println(c1); // class Test01
// 一個類在內(nèi)存中只有一個Class對象
// 一個類被加載后,類的整個結(jié)構(gòu)都會被封裝在Class對象中竿屹。
System.out.println(c1.hashCode()); // 685325104
System.out.println(c2.hashCode()); // 685325104
}
}
class User{
private String name;
private int age;
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
5报强、Class 類及其創(chuàng)建方式
在Object類中定義了一下方法,此方法將被所有子類繼承拱燃。
public final Class getClass()
此方法的返回值類型是一個Class類秉溉,此類是Java反射的源頭,實際上所謂反射從程序的運行結(jié)果來看也很好理解:即:可以通過對象反射求出類的名稱碗誉。
Class 類的特點:
- Class本身也是一個類
- Class對象只能由系統(tǒng)建立對象
- 一個加載的類在 JVM 中只會有一個Class實例
- 一個Class對象對應(yīng)的是一個加載到JVM中的一個.class文件
- 每個類的實例都會記得自己是由哪一個Class實例所生成的
- 通過Class可以完整的得到一個類中所有被加載的結(jié)構(gòu)
- Class類是Reflection的根源針對任何你想要動態(tài)加載召嘶、運行的類,唯有先獲取相依的Class對象哮缺。
/**
* 測試Class類的創(chuàng)建方式有哪些
*/
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("這個人是:" + person.name); // 這個人是:學(xué)生
// 方式一:通過對象獲得
Class c1 = person.getClass();
System.out.println(c1.hashCode()); // 460141958
// 方式二:forName獲取
Class c2 = Class.forName("Student");
System.out.println(c2.hashCode()); // 460141958
// 方式三:通過類名.class獲取【最為安全可靠弄跌,性能最高】
Class c3 = Student.class;
System.out.println(c3.hashCode()); // 460141958
// 方式四:基本內(nèi)置類型的包裝類都有一個Type屬性
Class c4 = Integer.TYPE;
System.out.println(c4); // int
}
}
class Person {
String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class Student extends Person{
public Student() {
this.name = "學(xué)生";
}
}
class Teacher extends Person{
public Teacher() {
this.name = "老師";
}
}
注意:Class 類創(chuàng)建方式很大概率在面試中會被問到。
6尝苇、類的加載過程和ClassLoader的理解
當(dāng)程序主動使用某個類時铛只,如果該類還未被加載到內(nèi)存中,則系統(tǒng)會通過如下三個步驟來對該類進行初始化:
- 加載:將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中糠溜,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)淳玩,然后生成一個代表這個類的java.lang.Class對象;
- 鏈接:將類的二進制代碼合并到JVM的運行狀態(tài)之中的過程非竿。
- 驗證:確保的加載的類信息符合JVM規(guī)范蜕着,沒有安全方面的問題;
- 準(zhǔn)備:正式為類變量(static)分配內(nèi)存并設(shè)置類變量默認(rèn)值的階段红柱,這些內(nèi)存都將在方法區(qū)中進行分配侮东;
- 解析:虛擬機常量池內(nèi)的符號引用(常量名)替換為直接引用(地址)的過程;
- 初始化:
- 執(zhí)行類構(gòu)造器
<clinit>()
方法的過程豹芯。類構(gòu)造器<clinit>()
方法是由編譯器自動收集類中所有類變量的賦值動作和靜態(tài)代碼塊中的語句合并產(chǎn)生的悄雅; - 當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行初始化铁蹈,則需要先觸發(fā)其父類的初始化宽闲;
- 虛擬機會保證一個類的
<clinit>()
方法在多線程環(huán)境中被正確加鎖和同步。
- 執(zhí)行類構(gòu)造器
public class Test03 {
public static void main(String[] args) {
A a = new A();
System.out.println(a.m);
}
}
class A {
static {
System.out.println("A類靜態(tài)代碼塊初始化");
m = 300;
}
static int m = 100;
public A () {
System.out.println("A類的無參構(gòu)造初始化");
}
// A類靜態(tài)代碼塊初始化
// A類的無參構(gòu)造初始化
// 100
/**
* 過程分析:
* 1. 加載到內(nèi)存握牧,會產(chǎn)生一個類對象Class對象
* 2. 鏈接容诬, 鏈接結(jié)束后 m = 0
* 3. 初始化
* <clinit>(){
* System.out.println("A類靜態(tài)代碼塊初始化");
* m = 300;
* m = 100;
* }
*/
}
7、分析類的初始化
什么時候會發(fā)生類的初始化沿腰?
- 類的主動引用:一定會發(fā)生類的初始化
- 當(dāng)虛擬機啟動览徒,先初始化 main 方法所在的類
- new 一個類的對象
- 調(diào)用類的靜態(tài)方法(除了 final 常量)和靜態(tài)方法
- 使用
java.lang.reflection
包的方法對類進行反射調(diào)用 - 當(dāng)初始化一個類,如果其父類沒有被初始化颂龙,則會先初始化其父類
- 類的被動引用:不會發(fā)生類的初始化
- 當(dāng)訪問一個靜態(tài)域時习蓬,只有真正聲明這個域的類才會被初始化纽什。如:當(dāng)通過子類引用父類的靜態(tài)變量,不會導(dǎo)致子類被初始化
- 通過數(shù)組定義類的引用躲叼,不會觸發(fā)類的初始化
- 引用常量不會觸發(fā)此類的初始化(常量在鏈接階段就存入調(diào)用類的常量池中了)芦缰。
// 測試類什么時候會被初始化
public class Test04 {
static {
System.out.println("Main類被加載");
}
public static void main(String[] args) throws ClassNotFoundException {
// 1. 主動引用
// Son son = new Son();
// Main類被加載
// 父類被加載
// 子類被加載
// 2. 反射也會產(chǎn)生主動引用
// Class.forName("Son");
// Main類被加載
// 父類被加載
// 子類被加載
// 3. 不會產(chǎn)生類的引用方法
// System.out.println(Son.b);
// Main類被加載
// 父類被加載
// 3
Son[] array = new Son[5]; // Main類被加載
}
}
class Father{
static int b = 3;
static {
System.out.println("父類被加載");
}
}
class Son extends Father{
static {
System.out.println("子類被加載");
m = 300;
}
static int m = 100;
static final int M = 1;
}
8、類加載器的作用
類加載器的作用:將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中枫慷,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)让蕾,然后再堆中生成這個類的java.lang.Class
對象,作為方法區(qū)中類數(shù)據(jù)的訪問入口或听。
類緩存:標(biāo)準(zhǔn)的 JavaSE 類加載器可以按要求查找類探孝,但一旦某個類被加載到類加載器中,他將維持加載(緩存)一段時間誉裆。不過 JVM 垃圾回收機制可以回收這些Class對象再姑。
類加載器作用:用來把類(class)裝載進內(nèi)存的。JVM 規(guī)范定義了如下類型的類的加載器找御。
public class Test05 {
public static void main(String[] args) throws ClassNotFoundException {
// 獲取系統(tǒng)類加載器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader); // sun.misc.Launcher$AppClassLoader@18b4aac2
// 獲取系統(tǒng)類加載器的父類---> 擴展類加載器
ClassLoader systemClassLoaderParent = systemClassLoader.getParent();
System.out.println(systemClassLoaderParent); // sun.misc.Launcher$ExtClassLoader@1b6d3586
// 測試當(dāng)前類是哪個類加載器加載的
ClassLoader classLoader = Class.forName("Test05").getClassLoader();
System.out.println(classLoader); // sun.misc.Launcher$AppClassLoader@18b4aac2
// 測試 JDK 內(nèi)置的類是哪個加載器加載的
ClassLoader loader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(loader); // null 引導(dǎo)類加載器
}
}
9元镀、獲取運行時類的完整結(jié)構(gòu)
通過反射獲取運行時累的完整結(jié)構(gòu)
Filed、Method霎桅、Constructor栖疑、Superclass、Interface滔驶、Annotation遇革。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
// 獲得類的信息
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("pojo.User");
// 獲得類的名稱
System.out.println(c1.getName()); // 包名+類名 pojo.User
System.out.println(c1.getSimpleName()); // 類名 User
// 獲得類的屬性
Field[] fields = c1.getFields(); // 只能找到public屬性
Field[] declaredFields = c1.getDeclaredFields(); // 找到全部屬性 private int pojo.User.id
// 獲得指定屬性的值
Field name = c1.getDeclaredField("name");
Method[] methods = c1.getMethods(); // 獲得本類及其父類的全部 public 方法
Method[] declaredMethods = c1.getDeclaredMethods(); // 獲得本類的所有方法
// 獲得指定方法
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
// 獲得指定構(gòu)造器
Constructor[] constructors = c1.getConstructors();
Constructor[] declaredConstructors = c1.getDeclaredConstructors();
Constructor declaredConstructor = c1.getDeclaredConstructor(int.class, String.class);
System.out.println(declaredConstructor); // public pojo.User(int,java.lang.String)
}
}
10、動態(tài)創(chuàng)建對象執(zhí)行方法
有了 Class 對象之后揭糕,能做什么萝快?
創(chuàng)建類的對象:調(diào)用 Class 對象的 newInstance() 方法。
- 類必須有一個無參構(gòu)造器
- 類的構(gòu)造器的訪問權(quán)限需要足夠著角。
思考揪漩?難道沒有無參構(gòu)造器就不能創(chuàng)建對象了嗎?
答:只要操作的時候明確的調(diào)用類中的構(gòu)造器吏口。并將參數(shù)傳遞進去之后奄容,才可以實例化操作。
import pojo.User;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 通過反射動態(tài)的創(chuàng)建對象
public class Test07 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 獲得 Class 對象
Class c1 = Class.forName("pojo.User");
// 構(gòu)造一個對象
User user = (User) c1.newInstance(); // 本質(zhì)調(diào)用了類的無參構(gòu)造器
System.out.println(user); // User{id=0, name='null'}
// 通過構(gòu)造器創(chuàng)建對象
Constructor constructor = c1.getDeclaredConstructor(int.class, String.class);
User jack = (User) constructor.newInstance(1, "Jack");
System.out.println(jack); // User{id=1, name='Jack'}
// 通過反射調(diào)用普通方法
User user2 = (User) c1.newInstance();
// 通過反射調(diào)用一個方法
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(user2, "小米");
System.out.println(user2); // User{id=0, name='小米'}
// 通過反射操作屬性
User user3 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
// 不能直接操作私有屬性产徊,需要取消安全監(jiān)測
name.setAccessible(true);
name.set(user3, "小黑");
System.out.println(user3); // User{id=0, name='小黑'}
}
}
Method 和 Field昂勒、Constructor 對象都有 setAccessible() 方法。
setAccessible 作用是啟動和禁用訪問安全檢查的開關(guān)舟铜。
參數(shù)值為 true 則指示反射的對象在使用時應(yīng)該取消 Java 語言訪問檢查戈盈。
11、反射操作泛型(generics)
Java 采用泛型擦除的機制來引入泛型谆刨,Java 中的泛型僅僅是給編譯器 javac 使用的塘娶,確保數(shù)據(jù)的安全性和免去強制類型轉(zhuǎn)換問題归斤,但是一旦編譯完成,所有和泛型有關(guān)的類型全部擦除血柳。
為了通過反射操作這些類型官册, Java 新增了 ParameteriedType
, GenericArrayType
, TypeVariable
和WildcardType
幾種類型來代表不能被歸一到 Class 類中的類型但是又和原始類型齊名的類型生兆。
-
ParameteriedType
:表示一個參數(shù)化類型难捌,比如Collection<String>
-
GenericArrayType
:表示一個元素類型是參數(shù)化類型或者類型變量的數(shù)組類型 -
TypeVariable
:是各種類型變量的公共父接口 -
WildcardType
:代表一種通配符類型的表達(dá)式。
// 獲取泛型(generics)信息
public class Test08 {
public void test01 (Map<String, User> map, List<User> list) {
System.out.println("test01");
}
public Map<String, User> test02 () {
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = Test08.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
// java.util.Map<java.lang.String, pojo.User>
// java.util.List<pojo.User>
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("##" + actualTypeArgument);
// java.util.Map<java.lang.String, pojo.User>
// ##class java.lang.String
// ##class pojo.User
// java.util.List<pojo.User>
// ##class pojo.User
}
}
}
System.out.println("=======================");
// 獲取返回值泛型
Method method1 = Test08.class.getMethod("test02", null);
Type genericReturnType = method1.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("##" + actualTypeArgument);
// ##class java.lang.String
// ##class pojo.User
}
}
}
}
12鸦难、反射操作注解
練習(xí):ORM
使用注解和反射完成類和表結(jié)構(gòu)映射根吁。類和表結(jié)構(gòu)對應(yīng),屬性和字段對應(yīng)合蔽、對象和記錄對應(yīng)击敌。
import java.lang.annotation.*;
import java.lang.reflect.Field;
// 練習(xí)反射操作注解
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("Student");
// 通過反射獲得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation); // @TableMy(value=db_student)
}
// 獲得注解的 value 的值
TableMy tableMy = (TableMy) c1.getAnnotation(TableMy.class);
String value = tableMy.value();
System.out.println(value); // db_student
// 獲得類指定的注解
Field f = c1.getDeclaredField("id");
FiledMy annotation = f.getAnnotation(FiledMy.class);
System.out.println(annotation.columnName()); // db_id
System.out.println(annotation.type()); // int
System.out.println(annotation.length()); // 10
}
}
// 類名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableMy{
String value();
}
// 屬性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FiledMy {
String columnName();
String type();
int length();
}
@TableMy("db_student")
class Student {
@FiledMy(columnName = "db_id", type = "int", length = 10)
private int id;
@FiledMy(columnName = "db_age", type = "int", length = 10)
private String name;
@FiledMy(columnName = "db_name", type = "varchar", length = 3)
private int age;
public Student() {
}
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}