注解與反射
注解(annotation)
介紹
注解(Annotation)是JDK5.0開始引入的新技術
//什麼是注解
public class Test01 extends Object {
@Override //表示重寫的注解
public String toString() {
return super.toString();
}
}
注解的作用
- 可以對程式作出解釋(這點和一般注釋【comment】沒什麼差別)
- 注解可以被程式(如: 編譯器 )讀取,即 注解是給編譯器看的。
- 注解有檢查和限制的作用,規範代碼的書寫,否則編譯器會生成錯誤資訊
注解的格式:
注解是以 “@注釋名” 的形式在代碼中存在的,還可以添加一些參數值, 如:
@SuppressWarnings(value="unchecked")
注解可以在哪裡使用
注解可以添加在package, class, method ,field等的上面,相當于給它們添加了額外的輔助資訊, 我們可以通過 反射機制程式設計 實作對這些中繼資料的通路
注解的分類:
- 内置注解
- 元注解
- 自定義注解
内置注解
- @Override: 定義在java.lang.Override中,此注解隻能修飾方法,表示一個方法聲明打算重寫父類中的方法。
-
@Override //表示重寫的注解,重寫父類的toString方法 public String toString() { return super.toString(); }
-
- @Deprecated: 定義在java.lang.Deprecated中,此注解可以修飾方法,屬性,類等,表示不鼓勵程式員使用加上了這個注解的元素;通常是因為它很危險或者存在更好的選擇。
@Deprecated //表示不推薦被使用的注解
public static void test(){
System.out.println("加上了@Deprecated注解方法");
}
3. @SuppressWarnings: 定義在java.lang.SuppressWarnings中,用來抑制編譯時彈出的警告資訊,這個注解需要添加一個參數才能正确使用,而這些參數都是已經定義好的,我們選擇性的使用即可。
-
@SuppressWarnings("all") @SuppressWarnings("unchecked") @SuppressWarnings(value={"unchecked","deprecation"}) ......
-
@SuppressWarnings("all") //表示鎮壓所有的警告資訊 public void test2(){ List list = new ArrayList<>(); }
- 未使用鎮壓警告資訊滑鼠移到元素上會自動顯示警告資訊
- 加上鎮壓的注解後,警告資訊都會被鎮壓,元素不再顯示警告資訊
元注解(meta-annotation)
元注解的作用就是負責注解其它的注解,屬于注解的注解。
Java在java.lang.annotation包中定義了4個标準的meta-annotation類型,它們被用來提供對其它annotation類型作說明。
- @Target: 用來描述注解的可作用範圍(即:被定義的注解 可以被用在哪些地方)
- 注: ElementType是一個枚舉類,其中已定義了各個作用範圍字段名
- @Target應用示例: 傳遞一個參數,如下圖:
- @Target應用示例: 傳遞一個參數數組
- @Retention: 用來描述需要在什麼級别儲存該注解資訊(即: 用于描述注解的生命周期)(自定義的注解一般定義為RUNTIME)
- 注: RetentionPolicy是一個枚舉類,其中已定義了各個字段名,且RUNTIME > CLASS > SOURCE
- @Retention應用示例:如下圖:
- @Documented: 說明該注解将被包含在javadoc中。
- @Inherited: 說明子類可以繼承父類中的該注解。
自定義注解
格式: public @interface 注解名 {定義的内容}
使用@interface自定義注解,自動繼承java.lang.annotation.Annotation接口
在自定義注解中:
- 其中的方法形式,實際上代表的是聲明了一個配置參數,不是指代真正的方法
- 方法的名稱就是參數的名稱。
- 傳回值就是參數的類型(傳回值隻能是基本類型:Class,String,enum)
- 可以通過default來聲明參數的預設值
- 如果隻有一個參數成員,一般參數名為value,則調用注解時不用寫參數名。
- 注解元素必須要有值,定義注解元素時,經常使用空字元串/0作為預設值。
//調用自定義注解
@MyAnnotation2(age=20)
public class Test03 {
@MyAnnotation2(age=20)
@MyAnnotation3("直接寫參數值") //由于注解中參數名定義為value,這裡可以直接寫參數值
public static void main(String[] args) {
System.out.println("test");
}
}
//自定義注解(一般要在自定義的注解上面書寫@Targe和@Retention等元注解)
@Target(value = {ElementType.TYPE,ElementType.METHOD}) //設定注解的作用域是 類和方法
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//書寫注解參數的格式: 參數類型 + 參數名()
String name() default "";
int age();
int id() default -1; //如果預設值為-1,代表不存在
String[] schools() default {"Carson","SZ University"};
}
//自定義注解(一般要在自定義的注解上面書寫@Targe和@Retention等元注解)
@Target(value = {ElementType.TYPE,ElementType.METHOD}) //設定注解的作用域是 類和方法
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
//若隻有一個參數,參數建議命名為value
//這樣調用這個注解時,可以不寫參數名,直接寫參數值
String value();
}
反射機制(Reflection)
概述
動态類型語言:在運作期進行類型檢查的語言,也就是在編寫代碼的時候可以不指定變量的資料類型,如 Python
靜态類型語言:它的資料類型是在編譯期進行檢查的,也就是說變量在使用前要聲明變量的資料類型,這樣的好處是把類型檢查放在編譯期,提前檢查可能出現的類型錯誤,如 Java
強類型語言: 一個變量不經過強制轉換,它永遠是這個資料類型,不允許隐式的類型轉換。即:如果定義了一個double類型變量a,不經過強制類型轉換那麼程式int b = a無法通過編譯。典型代表是Java。
弱類型語言:它與強類型語言定義相反,允許編譯器進行隐式的類型轉換,典型代表C/C++。
- Java不是動态語言,但Java可以稱之為 “準動态語言” 。即Java有一定的動态性
- Java利用 反射機制 獲得類似動态語言的特性,讓程式設計時更加靈活!
- Reflection(反射)是Java被視為動态語言的關鍵,反射機制允許程式在執行期間借助于Reflection API取得任何類的内部資訊,并能直接操作任意對象的内部屬性和方法。
- 加載一個類示例:
- 加載完類後,在堆記憶體的方法區中就産生了一個Class類型的對象**(一個類隻有一個Class對象)**,這個對象就包含了完整的類的結構資訊。這個對象就像一面鏡子,透過這個鏡子可以看到類的結構,是以,形象的稱之為: 反射。
- 對象照鏡子後可以得到的資訊: 某個類的屬性,方法,構造器,實作了哪些接口。
- 所謂反射從程式的運作結果來看也很好了解: 即通過對象反射求出類的名稱。
Java反射機制提供的功能
- 在運作時判斷任意一個對象所屬的類
- 在運作時構造任意一個類的對象
- 在運作時判斷任意一個類所具有的成員變量和方法
- 在運作時擷取泛型資訊
- 在運作時調用任意一個對象的成員變量和方法
- 在運作時處理注解
- 生成動态代理
- …
Java反射的優點和缺點
- 優點: 可以實作動态建立對象和編譯,展現出很大的靈活性。
- 缺點: 對性能有影響。
- 使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼并且讓它滿足我們的要求。這類操作總是慢于 直接執行相同的操作。
反射相關的主要API:
- java.lang.Class 代表一個類
- java.lang.reflect.Method 代表類的方法
- java.lang.reflect.Field 代表類的成員變量
- java.lang.reflect.Constructor 代表類的構造器
- …
獲得反射對象示例
public static void main(String[] args) throws ClassNotFoundException {
//通過反射擷取類的Class對象
Class c1 = Class.forName("com.carson.reflection.User");
System.out.println(c1);
//驗證 一個類隻有一個Class對象
Class c2 = Class.forName("com.carson.reflection.User");
Class c3 = Class.forName("com.carson.reflection.User");
Class c4 = Class.forName("com.carson.reflection.User");
//一個類被加載後,類的整個結構都會被封裝在Class對象中
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
}
Class類
- 對于每個類而言,JRE都為其保留一個不變的Class類型的對象。
- 一個Class對象包含了特定某個結構(class/interface/enum/annotation)的有關資訊
Class類的特點:
- Class本身也是一個類。
- Class對象隻能由系統建立對象。
- 一個加載的類在JVM中隻會有一個Class執行個體。
- 一個Class對象對應的是一個加載到JVM中的一個.class檔案。
- 每個類的執行個體都會記得自己是由哪個Class對象所生成。
- 通過Class可以完整地得到一個類中的所有被加載的結構。
- Class類是Reflection(反射)的根源,針對任何你想動态加載,運作的類,唯有先獲得相應的Class對象。
Class類的常用方法:
方法名 | 功能說明 |
---|---|
static Class forName(String name) | 傳回指定完整類名name的Class對象 |
Object newInstance() | 調用無參構造函數,動态建立類的對象 |
getName() | 傳回此Class對象所表示的實體的名稱 |
Class getSuperClass() | 傳回目前Class對象的父類的Class對象 |
Class[] getInterfaces() | 傳回目前Class對象的接口 |
ClassLoader getClassLoader() | 傳回該類的類加載器 |
Constructor[] getConstructors() | 傳回一個包含某些Constructor對象的數組 |
Method getMethod(String name,Class… T) | 傳回一個Method對象,此對象的形參類型為paramType |
Field[] getDeclaredFields() | 傳回Field對象的一個數組 |
擷取Class類的執行個體對象的方式:
- 若已知具體的類和類名,通過類的class屬性擷取,該方法最為安全可靠,程式性能最高。如下:
- 若已知某個類的執行個體,調用該類的執行個體對象的getClass()方法擷取Class對象,如下
- 若已知一個類的全類名,且該類在類路徑下,可通過Class類的靜态方法forName()擷取,注意需抛出ClassNotFoundException,如下:
- 若是内置的基本資料類型的包裝類型,可以直接使用:類名.TYPE,如下:
-
class c = Integer.TYPE
-
測試代碼:
//測試Class類的建立方式有哪些
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("這個人是:"+person.name);
//方式一:通過對象獲得
Class c1 = person.getClass();
System.out.println(c1.hashCode());
//方式二:forName獲得
Class c2 = Class.forName("com.carson.reflection.Student");
System.out.println(c2.hashCode());
//方式三:通過類名.class獲得
Class c3 = Student.class;
System.out.println(c3.hashCode());
//方式四:基本資料類型的包裝類型的TYPE屬性獲得
Class c4 = Integer.TYPE;
System.out.println(c4);
//獲得父類的Class對象
Class c5 = c1.getSuperclass();
System.out.println(c5);
}
}
class Person{
public 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 = "學生";
}
}
class Teacher extends Person{
public Teacher(){
this.name = "老師";
}
}
- 利用ClassLoader
哪些類型可以有Class對象?
- class: 外部類,成員(成員内部類,靜态内部類),局部内部類,匿名内部類
- interface: 接口
- []: 數組
- enum: 枚舉
- annotation: 注解@interface
- primitive type: 基本資料類型
- void
測試代碼:
//所有類型的Class
public class Test03 {
public static void main(String[] args) {
Class c1 = Object.class; // 類
Class c2 = Comparable.class; // 接口
Class c3 = String[].class;// 一維數組
Class c4= int[][].class;// 二維數組
Class c5 = Override.class;//注解
Class c6 = ElementType.class;// 枚舉
Class c7 = Integer.class;// 基本資料類型的包裝類
Class c8 = void.class;// void類型
Class c9 = Class.class; // Class類
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
//隻要元素類型與次元一樣,就是同一個Class
int[] a = new int[10];
int[] b = new int[100];
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());
}
}
類加載
類的加載記憶體分析
Java的基本記憶體情況:
類的加載過程
當程式主動使用某個類時,如果該類還未被加載到記憶體中,則系統會通過如下三個步驟來對該類進行初始化, 如下圖:
- 加載: java程式經過javac編譯後會生成class位元組碼檔案,将class檔案位元組碼内容加載到記憶體中,并将這些靜态資料轉換成方法區的運作時資料結構,然後生成一個代表這個類的java.lang.Class對象。【故Class對象隻能擷取】
- 連結: 将Java類的二進制代碼合并到JVM的運作狀态之中的過程。
- 驗證: 確定加載的類資訊符合JVM規範,沒有安全方面的問題。
- 準備: 正式為類變量(static)配置設定記憶體并設定變量的預設初始值,這些記憶體都将在方法區中進行配置設定。
- 解析: 虛拟機常量池内的符号引用(即常量名)替換為直接引用(真實位址)
- 初始化:
- 執行類構造器 " ()"方法。類構造器 " ()"方法是由編譯期自動收集類中所有類變量(static)的指派動作和靜态代碼塊中的語句合并産生的。【類構造器是構造類資訊的,不是構造該類對象的構造器】
- 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
- 虛拟機會保證一個類的" ()"方法在多線程環境中被正确加鎖和同步。
示例代碼:
//類是怎麼加載的
public class Test10 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
/*
1:類的class檔案加載到記憶體,會産生一個類Class對象
2:連結,連接配接結束後m初始化為0
3:<clinit>()方法初始化
<clinit>(){
System.out.println("A類靜态代碼塊的初始化!");
m = 300;
m = 100;
}
//最終
m = 100;
* */
}
}
class A{
/*
加載順序: 靜态代碼塊(優先級最高)---> 構造方法
* */
static{
System.out.println("A類靜态代碼塊的初始化!");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A類的無參構造器初始化!!");
}
}
什麼時候會發生類初始化
- 類的主動引用 (一定會發生類的初始化)
- 當虛拟機啟動,先初始化Main方法所在的類
- new一個類的對象
- 調用類的靜态成員(除了final常量)和靜态方法
- 使用java.lang.reflect包的方法對類進行反射調用
- 當初始化一個類,如果其父類沒有被初始化,則會先初始化它的父類。
- 類的被動引用 (不會發生類的初始化)
- 當通路一個靜态域(static)時,隻有真正聲明這個靜态域(static)的類才會被初始化。如: 當通過子類對象引用父類的靜态變量,不會導緻子類初始化。
- 通過數組定義類引用,不會觸發此類的初始化。
- 引用常量不會觸發此類的初始化 (常量在連結階段就存入調用類的常量池中了 )
示例代碼:
//測試類什麼時候被初始化
public class Test11 {
//當虛拟機啟動,先初始化Main方法所在的類
static{
System.out.println("Main方法所在的類被加載!");
}
public static void main(String[] args) throws ClassNotFoundException {
//主動引用(會産生類的初始化) new一個類的對象
Son son = new Son();
//反射也會産生主動引用 使用java.lang.reflect包的方法對類進行反射調用
Class.forName("com.carson.reflection.Son");
//被動引用(不會産生類的初始化) 當通過子類對象引用父類的靜态變量
System.out.println(Son.b);
//被動引用(不會産生類的初始化) 通過數組定義類引用
Son[] array = new Son[5];
//被動引用(不會産生類的初始化) 引用常量
System.out.println(Son.M);
}
}
class Father{
static int b = 2;
static{
System.out.println("父類被加載!");
}
}
class Son extends Father{
static{
System.out.println("字類被加載!");
m = 300;
}
static int m = 100;
static final int M = 1;
}
類加載器的作用
- 将class檔案位元組碼内容加載到記憶體中,并将這些靜态資料轉換成方法區的運作時資料結構,然後在堆中生成一個代表這個類的java.lang.Class對象,作為方法區中類資料的通路入口。
- 類緩存: 标準的JavaSE類加載器可以按要求查找類,但一旦某個類被加載到類加載器中,它将維持加載(即:緩存)一段時間。不過JVM垃圾回收機制可以回收這些Class對象。
- 類加載器的作用是用來把類(class)裝載進記憶體的。
類加載器的類型
JVM規範定義了如下類型的類加載器。
- ***引導類加載器(根加載器)***:用c/c++編寫的,是JVM自帶的類加載器,負責JAVA平台核心庫,用來裝載核心類庫。該加載器無法通過程式直接擷取。
- 擴充類加載器: 負責jre/lib/ext目錄下的jar包或-D java.ext.dirs指定目錄下的jar包裝入工作庫。
- 系統類加載器: 負責java -classpath或-D java.class.path所指的目錄下的類與jar包裝入工作庫,是最常用的加載器,常用來加載我們自己編寫的類。
測試代碼:
//擷取各個類加載器
public class Test04 {
public static void main(String[] args) throws ClassNotFoundException {
//擷取系統類的加載器(也叫ApplicationClassLoader)
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//擷取系統類加載器的父類加載器-->擴充類加載器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);
//擷取擴充類加載器的父類加載器-->根加載器(c/c++編寫的,是程式擷取不到的)
ClassLoader rootClassLoader = extClassLoader.getParent();
System.out.println(rootClassLoader);
//測試目前類是由哪個類加載器加載的(即系統類的加載器,ApplicationClassLoader)
ClassLoader classLoader = Class.forName("com.carson.reflection.Test04").getClassLoader();
System.out.println(classLoader);
//測試Java的内置類是由哪個類加載器加載的(即 根加載器,是程式擷取不到的)
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
//獲得系統類加載器可以加載的路徑
System.out.println(System.getProperty("java.class.path"));
/*C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64. jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\mysql-connector-java-8.0.25.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\management- agent.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;
D:\Code\java_WorkSpace\注解和反射\out\production\注解和反射;
D:\SoftWare\JetBrains\IntelliJ IDEA 2020.1\lib\idea_rt.jar*/
}
}
擷取運作時類的完整結構
可通過反射擷取運作時類的完整結構:
即: Field,Method,Constructor,Superclass,Interface,Annotation 等
- 可擷取實作的全部接口
- 可擷取所繼承的父類
- 可擷取其全部的構造器
- 可擷取其全部的方法
- 可擷取其全部的Field
- 可擷取其注解
- …
測試及說明的代碼:
//通過類的Class對象,擷取類的各種資訊
public class Test05 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
//首先需要擷取一個類的Class對象,這裡以forName示例
Class c = Class.forName("com.carson.reflection.User");
//獲得類的名字
System.out.println(c.getName()); //獲得 包名+類名
System.out.println(c.getSimpleName());//獲得類名
//獲得類的屬性
System.out.println("-------------------------");
Field[] fields = c.getFields(); //隻能找到public屬性
/*for (Field field : fields) {
System.out.println(field);
}*/
Field[] declaredFields = c.getDeclaredFields(); //能找到全部的屬性(包括private屬性)
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//獲得指定的屬性
System.out.println("-------------------------");
Field name = c.getField("carson");//獲得指定的任何一個public屬性
name = c.getDeclaredField("name");//獲得指定的任何一個屬性(包括private屬性)
System.out.println(name);
//獲得類的方法
System.out.println("-------------------------");
Method[] methods = c.getMethods(); //獲得本類及其父類的全部public方法
for (Method method : methods) {
System.out.println("getMethods: "+method);
}
Method[] declaredMethods = c.getDeclaredMethods(); //獲得本類的所有方法(包括private類型的方法)
for (Method declaredMethod : declaredMethods) {
System.out.println("getDeclaredMethods: "+declaredMethod);
}
//獲得指定的方法(若方法帶有參數,格式:參數類型.class )
System.out.println("-------------------------");
Method getName = c.getMethod("getName"); //隻能擷取任意的public類型的方法
Method test = c.getDeclaredMethod("test", null);//能擷取任意類型的方法(包括private類型)
Method setName = c.getMethod("setName", String.class);//隻能擷取任意的public類型的方法
Method test1 = c.getDeclaredMethod("test", int.class);//能擷取任意類型的方法(包括private類型)
System.out.println(getName);
System.out.println(test);
System.out.println(setName);
System.out.println(test1);
//獲得構造器
System.out.println("-------------------------");
Constructor[] constructors = c.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("getConstructors: "+constructor);
}
Constructor[] declaredConstructors = c.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println("getDeclaredConstructors: "+declaredConstructor);
}
//獲得指定的構造器
System.out.println("-------------------------");
Constructor declaredConstructor = c.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println(declaredConstructor);
/*
總結: 不帶有declared的方法隻能獲得public類型的資料
帶有declared的方法能獲得所有類型的資料(包括private)
* */
}
}
- 在實際的開發中,獲得類的資訊的操作代碼,并不會經常開發。
- 一定要熟悉java.lang.reflect包的作用,反射機制
- 需要知道如何取得屬性,方法,構造器等資訊的方法。
有了Class對象,能做什麼?
建立類的執行個體對象
- 利用類的Class對象的newInstance()方法【無參構造器方法】
- 前提: 類必須要有一個無參構造器
- 前提: 類的構造器的通路權限需要足夠
-
//動态的建立對象,通過反射 //1: 獲得Class對象 Class c = Class.forName("com.carson.reflection.User"); //2: 建立一個對象 User user = (User)c.newInstance();//本質是調用了類的無參構造器 System.out.println(user);
- 通過類的Class對象獲得有參構造器,通過有參 構造器執行個體化對象。【有參構造器方法】,步驟如下:
- 通過類的Class對象的getDeclaredConstructor(Class… parameterTypes)取得指定的有參構造器。
- 向構造器的形參中傳遞參數,裡面包括了構造器中所需的各個參數。
- 通過Constructor執行個體化類的對象
-
//動态的建立對象,通過反射 //1: 獲得Class對象 Class c = Class.forName("com.carson.reflection.User"); //2: 通過構造器建立對象 Constructor constructor = c.getDeclaredConstructor(String.class, int.class, int.class); User user2 = (User)constructor.newInstance("Carson", 1, 21); System.out.println(user2);
調用指定的方法和屬性
通過反射,調用類中的方法,通過Method類完成。步驟如下:
- 通過Class類的getMethod(String name,Class… parameterTypes)方法取得一個Method對象,并設定此方法操作時所需要的參數類型。
- 再使用Object invoke(Object object,Object[] args)方法進行調用,并向方法中傳遞要設定的obj對象的參數資訊。
Object invoke(Object obj,Object… args)方法說明:
- Object對應原方法的傳回值,若原方法無傳回值,此時傳回null.
- 若原方法為靜态方法,此時形參Object obj可為null(因為靜态方法不需要對象)
- 若原方法形參清單為空,則Object[] args為null
- 若原方法權限聲明為private,則需要在調用此invoke()方法前,顯式調用方法對象的setAccessible(true)方法,将可通路private類型的方法【關閉權限安全檢測】
setAccessible()方法說明:
- 無論是Method和Field,Constructor等對象都有setAccessible()方法
- setAccessible作用是啟動或禁用通路安全檢查的開關。
- 參數值為true則訓示反射的對象在使用時應該取消Java語言通路檢查
- 可以提高反射的效率。如果代碼中必須用反射,而該句代碼頻繁地被調用,那麼請設定為true.
- 使得原本無法通路的私有成員也可以通路。
- 參數值為false則訓示反射的對象應該實施Java語言通路檢查。
示例代碼:
//通過反射調用指定方法
//獲得Class對象
Class c = Class.forName("com.carson.reflection.User");
//動态建立一個對象
User user = (User)c.newInstance(); //user.setName("Carson's Blog");
//通過反射擷取一個方法
Method setName = c.getDeclaredMethod("setName", String.class);
//方法激活使用,格式: invoke(對象,方法的值)
setName.invoke(user,"Carson's Blog");
System.out.println(user.getName());
//通過反射操作屬性
System.out.println("---------------------------");
User user1 = (User)c.newInstance();
Field name = c.getDeclaredField("name");
//不能直接操作私有屬性
//需要關閉程式的安全檢測
name.setAccessible(true);
//格式: set(對象,賦給屬性的值)
name.set(user1,"user1");
System.out.println(user1.getName());
性能對比分析:
//分析性能問題
public class Test07 {
//普通方式調用
public static void test01(){
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 static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
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 static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
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) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
test01();
test02();
test03();
}
}
擷取泛型資訊
- Java中的泛型僅僅是給編譯器javac使用的,確定資料的安全性和免去強制類型轉換問題,但是,一旦編譯完成,所有和泛型有關的類型全部擦除
- 為了通過反射操作這些類型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種類型來代表不能被歸一到Class類中的類型但是又和原始類型齊名的類型。
- ParameterizedType: 表示一種參數化類型,比如Collection
- GenericArrayType: 表示一種元素類型是參數化類型或者類型變量的數組類型
- TypeVariable:是各種類型變量的公共父接口
- WildcardType: 代表一種通配符類型表達式
示例代碼:
//通過反射擷取泛型
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 {
//反射擷取test01方法
Method method = Test08.class.getMethod("test01", Map.class, List.class);
//擷取方法的泛型參數
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);
}
}
}
//反射擷取test02方法
System.out.println("---------------------------------------");
method = Test08.class.getMethod("test02",null);
Type genericReturnType = method.getGenericReturnType();
//擷取泛型參數中具體的泛型變量類型
if(genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
擷取注解資訊
示例代碼:
//練習反射操作注解
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//擷取類的Class對象
Class c = Class.forName("com.carson.reflection.Student2");
//通過反射獲得類的注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//獲得指定的注解的value的值
TableCarson tableCarson = (TableCarson)c.getAnnotation(TableCarson.class);
String value = tableCarson.value();
System.out.println(value);
//獲得指定屬性注解的值
System.out.println("------------------------------");
Field id = c.getDeclaredField("id");
FieldCarson annotation = id.getAnnotation(FieldCarson.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@TableCarson("db_student2")
class Student2{
@FieldCarson(columnName = "db_id",type = "int",length = 10)
private int id;
@FieldCarson(columnName = "db_age",type = "int",length = 10)
private int age;
@FieldCarson(columnName = "db_name",type = "varchar",length = 3)
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 "Student2{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
//自定義類名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableCarson{
String value();
}
//自定義屬性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldCarson{
String columnName();
String type();
int length();
}
End!創作不易!歡迎點贊/評論/收藏!!