框架的基礎——反射和註解(reflection and annotation)
一.註解
1.什麼是註解?
程式的註釋,具有一定的功能,可以被解析。是從jdk5開始引入的技術。
2.註解的作用
- 不是程式本身,可以對程式做出解釋(這一點和comment沒有什麼區別)
- 可以被其他程式(如編譯器等)讀取
3.註解的格式
@註解名,還可以新增一些引數值。如:@SuppressWarnings(value = "unchecked")
4.註解使用在哪裡?
可以附加在package,class,method,field等上面,相當於給他們添加了輔助資訊。我們通過反射機制實現對這些元資料的訪問。
5.內建註解
- @Override 定義在java.lang.Override中,此註解只適用於修辭一個方法,表示一個方法打算重寫超類中的方法 - @SuppressWarnings 定義同上,用來抑制編譯時的警告資訊。與前兩個註解有所不同,需要新增一個引數才能使用,這些引數都是已經定義好的,我們選擇使用就好。 @SuppressWarnings(value = "all") "unchecked" "checked" {"unchecked", "deprecation"} - @Deprecated 定義同上,此註解用於修飾方法,屬性,類。表示不鼓勵程式設計師使用這樣的元素,通常是因為它很危險或者有更好的替代。
6.元註解
1.元註解作用:負責註解其他註解的註解
2.四個元註解(meta-annotation),用來對其他註解型別做說明
- @Target 用於描述註解的使用範圍(即描述註解可以用在什麼地方)
- @Retention 保持的意思,表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期(Source原始碼 < Class位元組碼 < Runtime執行時),表示該註解在什麼時候是有效的,一般設定為Runtime
- @Documented 表明該註解將被包含在javadoc中
- @Inherited 說明子類可以繼承父類中的該註解
package annotation; import java.lang.annotation.*; // 測試元註解 @MyAnnotation public class Test02 { public static void main(String[] args) { } @MyAnnotation public void test() { } } // @Target表示註解的作用在哪些地方 @Target(value = {ElementType.METHOD, ElementType.TYPE}) // @Retention表示註解的有效範圍:source < class < runtuime 會向下覆蓋生效 @Retention(value = RetentionPolicy.RUNTIME) // @Documented表示是否將我們的註解生成在javadoc中 @Documented // @Inherited表示子類可以繼承該註解 @Inherited @interface MyAnnotation { }
7.自定義註解格式
使用@interface定義,使用了它就自動繼承了lang.annotation.Annotation介面
// @Target表示註解的作用在哪些地方 @Target(value = {ElementType.METHOD, ElementType.TYPE}) // @Retention表示註解的有效範圍:source < class < runtuime 會向下覆蓋生效 @Retention(value = RetentionPolicy.RUNTIME) // @Documented表示是否將我們的註解生成在javadoc中 @Documented // @Inherited表示子類可以繼承該註解 @Inherited @interface MyAnnotation { }
- 用@interface宣告一個註解。格式:public @interface name {定義內容}
- 其中宣告引數的方式為:引數型別 + 引數名 + ();
- 引數型別只能是8種基本資料型別和String,Enum,Class,annotations資料型別,以及這些型別的陣列
- 可以在()後新增defalt "",設定預設引數值
- 如果只有一個引數,一般引數名為value,這樣可以在書寫時,不寫value,直接寫引數
- 註解元素必須要有值 我們通常使用空字串”“和0作為預設值
二、反射
1.動態語言
動態語言是在執行時,可以改變其結構的語言:如新的函式,物件,甚至程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化。通俗點說就是在執行時程式碼可以根據某些條件改變自身結構。
如:js,C#,PHP,Python,Object-C
2.靜態語言
與動態語言相對,執行時不可以改變結構。Java,C,C++。
Java是靜態語言,但是可以通過反射機制,讓Java具有一定的動態性,Java的動態性讓程式設計更加靈活。但是也損失了效率,也帶來了安全問題。
3.Java反射 Reflection
反射是Java被稱作動態語言的關鍵,反射機制允許程式在執行期間藉助Reflection API獲取類的所有內部資訊,並能直接操作任意物件的內部屬性和方法。
原理:一個類載入完後,就會在堆記憶體的方法區中,產生一個Class型別物件(一個類只有一個),這個物件包含了完整的類的結構資訊,我們可以通過這個物件看到類的內部結構。這個物件就像一面鏡子,通過這個鏡子看到類的結構,所以形象的稱之為:反射
4.Java反射的優缺點
優點:可以實現動態建立物件,有很強的靈活性
缺點:對效能有影響。因為我們需要告訴JVM去做什麼,這列操作總使比直接執行要慢。
5.反射相關的主要API
java.lang.Class: 代表一個類
java.lang.reflect.Method:代表一個方法
java.lang.reflect.Filed:代表類的成員變數
java.lang.reflect.Constructor:代表類的構造器
6.Class類
在Object類中定義了public final native Class getClass();方法可以返回物件的Class物件,也就是物件的類,類模板,也就是包括類的所有資訊。
Class類是Java反射的源頭。反射就是從物件反射得到類物件。
關於Class:
- Class本身也是一個類
- Class物件只能由系統建立
- 一個載入的類在JVM中只會有一個Class例項
- 每個Class物件對應的是載入到JVM的一個class檔案
- 通過Class可以完整的得到一個類中的所有被載入的結構
- Class類是Reflection的根源,針對任何你想動態載入,執行的類,先獲取它的Class才可行。
7.獲取Class物件的方法
- 通過類的.class獲取Class物件 效能最好,一個類載入到JVM中會建立一個Class物件和它的模板對應,class屬性就是這個Class物件的控制代碼,獲取該物件的模板物件Class。
- 通過物件的getClass()方法獲取
- 通過Class.forName()方法,傳入全限定路徑名,獲取對應的類模板
- 內建基資料型別包裝類,類名.TYPE
- 還可以使用ClassLoader獲取
- 獲取父類的Class,使用c.getSuperClass();
注意:獲取的Class物件對應的類模板物件就是具體new 的哪個物件的class檔案的物件。而不是父類的,只是由於它繼承了父類,那麼它的class檔案中就包含了父類的class資訊。每一個類在JVM中都會由對應的結構資訊,這個是唯一的。一個類一個。
8. 可以有Class物件的型別
幾乎所有型別都有自己的Class
- 類
- 介面
- 註解
- 列舉
- 基本資料型別
- Class(本身就是一個類)
- 陣列 只要型別一樣,維度一樣就是同一個Class物件。
- void空型別
9.類的載入與ClassLoader的理解
載入:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換為方法區的執行時資料結構,然後生成一個代表這個類的java.lang.Class物件。
連結:校驗,準備,連結。檢查錯誤規範; 給靜態變數分配記憶體(將在方法區分配),賦預設值; 將符號引用轉換為地址引用。
初始化:執行類構造器
合併類變數的賦值動作和靜態程式碼塊中的語句如下:當然類的定義仍然按照書寫順序,也就是靜態變數的賦值合併在了它的實際定義位置。
/**
* 總結:
* 1.載入類到記憶體,生成類對應的Class物件
* 2. 連結 靜態變數預設值
* 3. 初始化:執行<clinit>方法,clinit方法會把靜態變數,靜態程式碼塊合併,按照書寫順序進行執行,為靜態變數賦初始值。此時 m = 0;
* <clinit>(){
* System.out.println("A類靜態程式碼塊執行");
* System.out.println(m);
* m = 300;
* m = 100;
* }
*/
10.什麼情況下會發生類初始化?
- 主動引用會發生類初始化
1.JVM啟動時,初始化main方法所在類
2.new一個物件
3.反射 (獲取類模板,就要載入類,載入類,就要初始化類)
4.呼叫類的變數,類的方法。除了final修飾的常量。
5.初始化會保證其父類被初始化完畢
- 被動引用不會發生類初始化
1.通過子類呼叫父類的靜態成員,不會使子類初始化,但是會初始化父類。
2.建立類的陣列不會引起類初始化,僅僅是建立了一塊空間,並且給空間命名,並不會載入類。
3.呼叫類的常量不會引起來初始化(常量在連結階段就被調入了類的常量池)
總結:只有JVM啟動,new,反射,呼叫類的變數,方法,會進行初始化類
11.類載入器的作用
1.類載入的作用:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後再堆中生成一個代表這個類的Class物件,作為方法區中類資料的訪問入口。
2.類快取:類載入後,Class物件會快取再記憶體中。GC回收。
12.獲取執行時類的完整結構
package reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class clazz = Class.forName("reflection.User");
System.out.println(clazz.getName());
System.out.println(clazz.getSimpleName());
// 獲取欄位(屬性)
Field[] fields = clazz.getFields(); // 只能獲取public屬性 本類和父類的
// fields = clazz.getDeclaredFields(); // 獲取所有宣告的屬性 本類的
for (Field field:
fields) {
System.out.println(field + " =======");
}
// Field name = clazz.getField("name"); // 獲得指定名稱的屬性 public,包含父類的
Field name = clazz.getDeclaredField("name"); // 獲得指定名稱的屬性 public
System.out.println(name);
// 獲得類的方法
Method[] methods = clazz.getMethods(); // 獲取本類,父類的public方法。
for (Method method : methods) {
System.out.println("正常的:" + method );
}
Method[] declaredMethods = clazz.getDeclaredMethods(); // 獲取本類所有方法 包括private
for (Method declaredMethod : declaredMethods) {
System.out.println("getDeclaredMethods:" + declaredMethod);
}
System.out.println("=================================");
// 獲取指定方法
Method getName = clazz.getMethod("getName", null); // (方法名,引數列表的型別的Class)
System.out.println(getName);
// 獲取有參方法
Method setName = clazz.getMethod("setName", String.class); // 過載,需要指明引數,定位具體方法
System.out.println(setName);
System.out.println("======================");
// 獲取指定的構造器
Constructor[] constructors = clazz.getConstructors(); // 獲取本類的public構造方法
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); // 獲取所有構造方法
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
Constructor constructor = clazz.getConstructor(String.class, int.class, int.class); // public的
System.out.println(constructor);
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class, int.class); // 獲取指定的宣告的構造方法
System.out.println(declaredConstructor);
}
}
13.Class的使用
通過反射建立物件,設定屬性值,呼叫方法。
私有也可以通過獲取到的屬性,方法的setAccessible(true)就可以關閉訪問安全檢測,私有可訪問。
setAccessible(true)可以提高反射的效率,如果需要頻繁對某一個方法或屬性操作,那麼請設定true。
package reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 動態的建立物件 通過反射
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 獲得Class物件
Class c1 = Class.forName("reflection.User");
// 構造一個物件
// User user = (User) c1.newInstance(); // 本質呼叫無參構造器,並且要求類的構造器訪問許可權足夠
// System.out.println(user);
/*
通過構造器建立物件
*/
// Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
// User user2 = (User) declaredConstructor.newInstance("sqzr", 12, 12);
// System.out.println(user2);
/*
通過反射呼叫方法
*/
User user3 = (User) c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class); // 方法有了,但是不知道方法 呼叫哪個物件的這個方法
setName.invoke(user3, "sqzr"); // user3 指的是呼叫的哪個物件的方法, 因為這個物件可能已經有過其他引數了,如果獲取了方法,不指定哪個物件的,就不知道呼叫哪個
System.out.println(user3);
/*
通過反射操作屬性
*/
User user4 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true); // 設定可訪問是否開啟,true會關閉安全檢測,使得私有可訪問,false會進行安全檢測。
name.set(user4, "sqzr2");
System.out.println(user4);
}
}
14.setAccessible()效能問題分析
package reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 分析效能問題
public class Test10 {
public static void main(String[] args) {
test();
try {
test02();
test03();;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 普通方法呼叫
public static void test() {
User user = new User();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式執行10億次:" + (endTime - startTime));
}
// 反射方法呼叫
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName");
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user);
}
long endTime = System.currentTimeMillis();
System.out.println("反射方式執行10億次:" + (endTime - startTime));
}
// 反射方法呼叫
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName");
getName.setAccessible(true);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user);
}
long endTime = System.currentTimeMillis();
System.out.println("關閉檢測許可權的反射方式執行10億次:" + (endTime - startTime));
}
}
執行結果:
普通方式執行10億次:4
反射方式執行10億次:3875
關閉檢測許可權的反射方式執行10億次:1812
反射的方式要比普通的方式慢1000倍左右,而關閉了訪問許可權檢測後,速度可以提高3倍左右。
15.反射操作泛型
1.泛型
泛型是一種約束機制,確保程式碼的安全性,免去一些強制型別轉換。
通過反射操作泛型,要記住:泛型(generic)
package reflection;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
// 通過反射獲取泛型
public class Test11 {
public static void main(String[] args) throws NoSuchMethodException {
Class c1 = Test11.class;
// 獲取方法
Method method = c1.getDeclaredMethod("test01", Map.class, List.class);
// generic(泛型)通過方法獲取引數泛型型別 一個是Map,一個List
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("#" + genericParameterType);
// 輸出泛型型別中的引數型別。 首先判斷是否是 當前型別是否是引數化型別,然後強轉,得到實際使用型別陣列。列印輸出。
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
Method method2 = c1.getDeclaredMethod("test02", null);
// 獲取方法的返回值泛型型別 其他和上面相同。
Type genericReturnType = method2.getGenericReturnType();
System.out.println(genericReturnType);
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
public static void test01(Map<String, User> map, List<User> list) {
}
public static Map<String, User> test02() {
System.out.println("測試");
return null;
}
}
16.反射操作註解
通過Class物件的getAnnotation()方法獲取註解,或者通過Class物件獲取到方法,屬性後通過方法,屬性的getAnnotation()方法獲取註解。
可以直接獲取註解裡定義的引數,然後根據這些引數,生成相應的程式碼,執行程式。這就是框架的基礎。
總結:框架的基礎:通過反射,獲取註解引數資訊,生成相應程式碼;通過反射在程式執行時,建立物件,進行相應的操作。
package reflection;
import java.lang.annotation.*;
import java.lang.reflect.Field;
// 練習反射操作註解
public class Test12 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("reflection.Student2");
// 通過反射獲取註解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 獲得指定註解的value的值
TableName tableName = (TableName) c1.getAnnotation(TableName.class);
String value = tableName.value();
System.out.println(value);
// 獲得類指定註解
Field name = c1.getDeclaredField("name");
FieldName fieldName = name.getAnnotation(FieldName.class);
System.out.println(fieldName.columnName());
System.out.println(fieldName.type());
System.out.println(fieldName.length());
}
}
@TableName("db_student")
class Student2 {
@FieldName(columnName = "db_id", type = "int", length = 10)
private int id;
@FieldName(columnName = "db_age", type = "int", length = 10)
private int age;
@FieldName(columnName = "db_name", type = "varchar", length = 10)
private String name;
public Student2() {
}
public Student2(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
// 類名的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableName {
String value();
}
// 屬性的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldName {
String columnName();
String type();
int length();
}
17.註解和反射總結
1.註解
Annotation:不是程式本身,起到註釋作用,同時可以被其他程式(如編譯器讀取)。
註解格式:@interface Name{宣告引數}
作用範圍:package,class,method,field等上面,相當於給他們添加了輔助資訊,我們可以通過反射獲取這些元資料
內建註解:@Override,@Deprecated,@SuppressWarnings
元註解:4個meta-annotation:@Target,@Retention,@Documented,@Inherited。註解的註解,說明其他註解。
2.反射
1.靜態語言,動態語言
2.Java反射機制提供的功能,方法。
3.Class類對於每一個物件都只有一個。
4.類載入過程。載入(生成Class物件在堆記憶體中),連結(校驗,準備(分配記憶體,賦預設值),解析),初始化(執行clinit方法,為靜態變數賦值)。
5.類載入器,雙親委派機制
6.通過Class物件的各種方法,操作這個類模板。建立物件(可以通過newInstance呼叫無參構造方法,也可以通過獲取構造方法的newInstance()方法建立),操作各種方法,屬性,獲取註解。
7.setAccessible(),關閉訪問許可權檢查,可以提高效能。(大概是不關閉的3倍效能)。
8.反射操作泛型,泛型(generic),呼叫方法獲取方法的泛型,類的泛型,還可以獲取到其中的具體型別。
9.反射獲取註解(重要): 通過反射獲取註解的引數資料,可以用來動態生成指定功能的程式。(各種框架的基礎)。