Java註解和反射筆記
阿新 • • 發佈:2020-07-28
1 註解
1.1 定義
Annotation是從JDK1.5開始引入的技術
作用
- 不是程式本身,可以對程式作出解釋
- 可以被其他程式(編譯器等)讀取
格式
- @註釋名,可以新增一些數值
- 註解可以附加在package,class,method,field上面,可以通過反射機制實現對這些元資料的訪問
1.2 內建註解
- @Override:定義在java.lang.Override中,只適用於修飾方法,表示一個方法宣告打算重寫超類中的另一個方法宣告
- @Deprecated:定義在java.lang.Deprecated中,可以修飾方法,屬性,類,表示不建議使用這樣的元素,有更好的選擇
- @SuppressWarnings:定義在java.lang.SuppressWarnings中,用來抑制編譯時的警告資訊
1.3 元註解
元註解的作用是負責註解其他註解,Java定義了4個標註的meta-annotation型別
- @Target:用於描述註解的使用範圍(類,方法,屬性等)
- @Retention:表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期
- SOURCE < CLASS < RUNTIME
- @Documented:說明該註解將被包含在javadoc中
- @Inherited:說明子類可以繼承父類中的該註解
1.4 自定義註解
- 使用@interface自定義註解,自動繼承java.lang.annotation.Annotation介面
- 其中的每個方法實際上是聲明瞭配置引數,方法的名稱就是引數的名稱,返回值的型別就是引數的型別
- 用default來宣告引數的預設值
- 如果只有一個引數成員,一般引數名為value,且定義為value後,使用時可以省略引數名value,直接寫值
@Target(value = {ElementType.METHOD, ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME) @Documented @Inherited public @interface MyAnnotation { //註解的引數:引數型別+引數名() String name() default ""; //預設為空 int age() default 0; int id() default -1; //預設值為-1,代表不存在 String[] jobs(); }
2 靜態語言和動態語言
- 動態語言
- 在執行時可以改變其結構,新的函式、物件、程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化
- Object-C,C#,JavaScript,PHP,Python
- 靜態語言
- 執行時結構不可改變
- C,C++,Java
Java不是動態語言,但是可以利用反射機制獲得類似動態語言的特性
3 反射
3.1 概述
反射機制允許程式在執行期間藉助於Reflection API獲得任何類的內部資訊,並能直接操作任意物件的內部屬性及方法
Class c = Class.forName("java.lang.String");
載入完類之後,在堆記憶體的方法區中就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件包含了完整的類的結構資訊,通過這個類可以看到類的結構
- 正常方式
- 引入需要的包類名稱 ---> 通過new例項化 ---> 取得例項化物件
- 反射方式
- 例項化物件 ---> getClass()方法 ---> 得到完整的包類結構資訊
3.2 Class類
對於每個類而言,jre都會為其保留一個不變的Class型別的物件。一個Class物件包含了特定某個結構的資訊
- Class本身也是一個類
- Class物件只能由系統建立物件
- 一個載入的類在JVM中只會有一個Class例項
- 一個Class物件對應的是一個載入到JVM中一個.class檔案
- 每個類的例項都會記得自己是由哪個Class例項所生成的
- Class類是Reflection的根源,針對任何想動態載入、執行的類,只有先獲得相應的Class物件
3.3 獲得反射物件
首先,哪些型別有Class物件
- class:外部類,成員內部類,靜態內部類,區域性內部類,匿名內部類
- interface:介面
- []:陣列
- 只要元素型別和維度一樣,就是同一個Class,不管資料長度
- enum:列舉
- annotation:註解@interface
- primitive type:基本資料型別
- void
獲得Class物件的方法
-
已知具體的類,通過類的class屬性獲取,這種方法最安全快速
-
Class clazz = Person.class;
-
-
已知某個類的例項,呼叫該例項的getClass()方法獲取Class物件
-
Class clazz = persion.getClass();
-
-
已知類的全類名,通過Class類的靜態方法forName()獲取,可能丟擲異常
-
Class clazz = Class.forName("com.hjc.pojo.User");
-
-
內建基本資料型別可以直接用類名.TYPE
-
利用ClassLoader
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println(person.name);
//通過物件獲得
Class c1 = person.getClass();
//通過forName獲得
Class c2 = Class.forName("com.hjc.reflection.Student");
//通過類名.class獲得
Class c3 = Student.class;
//基本資料型別
Class c4 = Integer.TYPE;
//獲得父類
Class c5 = c1.getSuperClass();
}
}
class Person {
String name;
//省略建構函式和set/get方法
}
class Student extends Person {
public Student() {
this.name = "student";
}
}
class Teacher extends Person {
public Teacher() {
this.name = "teacher";
}
}
3.4 類載入過程
當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過三個步驟對該類進行初始化
- 載入
- 將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,生成一個代表這個類的java.lang.Class物件
- 連結
- 將Java類的二進位制程式碼合併到JVM的執行狀態之中
- 驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題
- 準備:正式為類變數(static)分配記憶體並設定類變數預設初始值,這些記憶體都在方法區中進行分配
- 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)
- 將Java類的二進位制程式碼合併到JVM的執行狀態之中
- 初始化
- 執行類構造器<clinit>()方法的過程
- 當初始化一個類的時候,如果發現父類還沒有初始化,則要先觸發父類的初始化
- 虛擬機器會保證一個類的<clinit>()方法在多執行緒環境中被正確加鎖和同步
3.5 類初始化
什麼時候會發生類初始化
- 類的主動引用(一定會發生類的初始化)
- 當虛擬機器啟動,先初始化main方法所在的類
- new一個類的物件
- 呼叫類的靜態成員(除了final常量)和靜態方法
- 使用java.lang.reflect包的方法對類進行反射呼叫
- 當初始化一個類,如果其父類沒有被初始化,則會先初始化其父類
- 類的被動引用(不會發生類的初始化)
- 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化
- 比如,通過子類引用父類的靜態變數,不會導致子類初始化
- 通過陣列定義類引用,不會觸發此類的初始化
- 引用常量不會觸發此類的初始化
- 常量在連結階段就存入呼叫類的常量池中了
- 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化
public class Test {
static {
System.out.println("Main類被載入");
}
public static void main(String[] args) throws ClassNotFoundException {
//主動引用
B b = new B();
//反射
Class.forName("com.hjc.reflection.B");
//B不會被載入,會載入A
System.out.println(B.n);
//B不被載入
B[] array = new B[10];
System.out.println(B.M);
}
}
class A {
static {
System.out.println("父類被載入");
}
static int n = 2;
}
classs B extends A {
static {
System.out.println("子類被載入");
m = 300;
}
static int m = 100;
static final int M = 1;
}
3.6 類載入器
類載入器的作用
- 將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換程方法區的執行時資料結構,在堆中生成一個代表這個類的java.lang.Class物件,作為方法區中類資料的訪問入口
類快取
- 標準的JavaSE類載入器可以按要求查詢類,但一旦某個類被載入到類載入器中,它將維持載入(快取)一段時間,但JVM垃圾回收機制可以回收這些Class物件
類載入器
- 引導類載入器
- 用C++編寫,是JVM自帶的類載入器,負責Java平臺核心庫,用來裝載核心類庫,該載入器無法直接獲取
- 擴充套件類載入器
- 負責jre/lib/ext目錄下的jar包裝入工作庫
- 系統類載入器
- 負責java -classpath下的jar包裝入工作庫,是最常用的載入器
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//獲取系統類載入器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//獲取系統類載入器的父類,擴充套件類載入器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//獲取擴充套件類載入器的父類,引導類載入器,無法獲取
parent = parent.getParent();
System.out.println(parent);
//測試當前類是由哪個類載入器載入的
ClassLoader classLoader = Class.forName("com.hjc.reflection.Test").getClassLoader();
System.out.println(ClassLoader);
//測試jdk內建的類
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(ClassLoader);
}
}
3.7 獲取執行時類的完整結構
獲得了類的Class物件,那麼我們就可以得到類的執行時資訊
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c = Class.forName("com.hjc.reflection.User");
//獲得類的名字
System.out.println(c.getName()); //包名+類名
System.out.println(c.getSimpleName()); //類名
//獲得類的屬性
Field[] fields = c.getFields(); //只能得到public屬性
for (Field field : fields) {
System.out.println(field);
}
fields = c.getDeclaredFields(); //可以得到全部屬性
for (Field field : fields) {
System.out.println(field);
}
//獲得指定屬性
Field name = c.getField("name"); //找不到,因為name是private
System.out.println(name);
Field name = c.getDeclaredField("name"); //可以找到
System.out.println(name);
//獲得類的方法
Method[] methods = c.getMethods(); //獲得本類及其父類的全部public方法
for (Method method : methods) {
System.out.println(method);
}
methods = c.getDeclaredMethods(); //獲得本類的全部方法
for (Method method : methods) {
System.out.println(method);
}
//獲得類的指定方法
Method getName = c.getMethod("getName", null); //方法名+引數
Method setName = c.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);
//獲得類的構造器
Constructor[] constructors = c.getConstructors(); //獲得public
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
constructors = c.getDeclaredConstructors(); //獲得全部
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
//獲得指定構造器
Constructor declaredConstructor = c.getDeclaredConstructor(int.class, String.class, int.class);
System.out.println(declaredConstructor);
}
}
class User {
private int id;
private String name;
private int age;
//省略構造方法和set/get方法
}
3.8 動態建立物件執行方法
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("com.hjc.reflection.User");
//構造一個物件
User user = (User) c.newInstance(); //本質上呼叫了類的無參構造器
System.out.println(user);
//如果沒有無參構造器,通過有參構造器建立物件
Constructor constructor = c.getDeclaredConstructor(int.class, String.class, int.class);
User user = (User) constructor.newInstance(1, "test", 18);
System.out.println(user);
//反射呼叫方法
Method setName = c.getDeclaredMethod("setName", String.class);
setName.invoke(user, "test1"); //invoke傳遞物件和方法引數值
System.out.println(user);
//反射操作屬性
Field name = c.getDeclaredField("name");
//不能直接操作私有屬性,需要關閉程式的安全檢測
//呼叫屬性或者方法的setAccessible(true)
name.setAccessible(true);
name.set(user, "test2");
System.out.println(user);
}
}
3.9 效能分析
public class Test {
//普通方式
public void test1() {
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式執行十億次:" + (endTime - startTime) + "ms");
}
//反射方式
public void test2() throws NoSuchMethodException {
User user = new User();
Class c = user.getClass();
Method getName = c.getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式執行十億次:" + (endTime - startTime) + "ms");
}
//反射方式,關閉檢測
public void test3() throws NoSuchMethodException {
User user = new User();
Class c = user.getClass();
Method getName = c.getDeclaredMethod("getName", null);
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("關閉檢測後,反射方式執行十億次:" + (endTime - startTime) + "ms");
}
public static void main(String[] args) {
test1();
test2();
test3();
}
}
3.10 反射操作泛型
Java採用泛型擦除的機制來引入泛型,Java中的泛型僅僅是給編譯器javac使用的,確保資料的安全性和免去強制型別轉換問題,但是一旦編譯完成,所有和泛型有關的型別全部擦除
為了通過反射操作泛型,Java引入了幾個類
- ParameterizedType:表示一種引數化型別,如Collection<String>
- GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別
- TypeVariable:各種型別變數的公共父介面
- WildcardType:代表一種萬用字元型別表示式
public class Test {
public void test1(Map<String, User> map, List<User> list) {
System.out.println("test1");
}
public Map<String, User> test2() {
System.out.println("test2");
return null;
}
public static void main(String[] args) {
Method method = Test.class.getMethod("test1", Map.class, List.class);
Type[] types = method.getGenericParameterTypes();
for (Type type : types) {
System.out.println(type);
if (type instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
for (Type actualType : actualTypes) {
System.out.println(actualType);
}
}
}
method = Test.class.getMethod("test2", null);
Type returnType = method.getGenericReturnType();
System.out.println(resultType);
if (returnType instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type actualType : actualTypes) {
System.out.println(actualType);
}
}
}
}
3.11 反射操作註解
很多框架都是通過反射獲取註解資訊,來幫我們解決了很多事情
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("com.hjc.reflection.Student");
//通過反射獲得註解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//獲得註解的值
Table1 table1 = (Table1) c.getAnnotation(Table1.class);
String value = table1.value();
System.out.println(value);
//獲得指定的註解
Field f = c.getDeclaredField("name");
Field1 f1 = f.getAnnotation(Field1.class);
System.out.println(f1.columnName());
System.out.println(f1.type());
System.out.println(f1.length());
}
}
@Table1("db_student")
class Student {
@Field1(columnName = "db_id", type = "int", length = 10)
private int id;
@Field1(columnName = "db_name", type = "varchar", length = 3)
private String name;
@Field1(columnName = "db_age", type = "int", length = 10)
private int age;
//省略構造器和set/get方法
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table1 {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field1 {
String columnName();
String type();
int length();
}