Java学习笔记7-2——注解与反射
目錄
- 理解 Class 類并獲取 Class 實例
- Class類
- 獲取 Class 類的實例
- 哪些類型可以有Class對象
- 所有類型的Class對象
- 從內存角度分析類加載【重點】
- 類加載的過程
- 什么時候會發生類的初始化
- 類加載器
- 獲取運行時類的完整結構
- 有了Class對象能做什么
- 性能對比分析
- 通過反射操作泛型
- 通過反射操作注解
理解 Class 類并獲取 Class 實例
1.先定義一個實體類
package com.cheng.reflection;public class User {// 屬性// getter setter// ... }2.主程序
public class Test01 {public static void main(String[] args) throws Exception {// 通過反射獲取類的 Class 對象Class<?> aClass = Class.forName("com.cheng.reflection.User");System.out.println(aClass);} }輸出:
class com.cheng.reflection.User
一個類在內存中只有一個 Class 對象,一個類被加載之后,類的整個結構都會被封裝在 Class 對象中
Class類
在 Object 類中定義了以下方法,此方法將被所有子類繼承
public final native Class<?> getClass();上面的方法返回值的類型是一個 Class 類,此類是 Java 反射的源頭,實際上所謂反射從程序的運行結果來看也很好理解,即:可以通過對象反射求出類的名稱
對于每個類而言,JRE 都為其保留了一個不變的 Class 類型的對象。一個 Class 對象包含了特定某個結構(class / interface / enum / annotation / primitive type/void / [])
- Class 本身也是一個類
- Class 對象只能由系統建立對象
- 一個加載的類在 JVM 中只會有一個 Class 實例
- 一個 Class 對象對應的是一個加載到 JVM 中的一個 .class 文件
- 每個類的實例都會記得自己是由哪個 Class 實例所生成
- 通過 Class 可以完整地得到一個類中的所有被加載的結構
- Class 類是 Reflection 的根源,針對任何你想動態加載、運行的類、唯有先獲得響應的 Class 對象
Class 類的常用方法:
| static Class forName(String name) | 返回指定類名 name 的 Class 對象 |
| Object newInstance() | 調用缺省構造函數,返回 Class 對象的一個實例 |
| getName() | 返回此 Class 對象所表示的實體(類、接口、數組類、或 void)的名稱 |
| Class getSuperClass() | 返回當前 Class 對象的父類的 Class 對象 |
| Class[] getInterfaces() | 獲取當前 Class 對象的接口 |
| ClassLoader getClassLoader() | 返回該類的類加載器 |
| Constructor[] getConstructors() | 返回一個包含某些 Constructor 對象的數組 |
| Method getMethod(String name, Class… T) | 返回一個 Method 對象,此對象的形參類型為 paramType |
| Field[] getDeclaredFields() | 返回 Field 對象的一個數組 |
獲取 Class 類的實例
public class Test01 {public static void main(String[] args) throws Exception {// 方式一:forName 獲得Class aClass = Class.forName("com.java.demo.reflect.User");System.out.println(aClass);// 方式二:通過對象獲得Class aClass1 = new User().getClass();System.out.println(aClass1);// 方式三:通過類名.class 獲得Class<User> aClass2 = User.class;System.out.println(aClass2);} }輸出:
class com.cheng.reflection.User
class com.cheng.reflection.User
class com.cheng.reflection.User
哪些類型可以有Class對象
- class:外部類,成員(成員內部類、靜態內部類),局部內部類,匿名內部類
- interface:接口
- []:數組
- enum:枚舉
- annotation:注解@inerface
- primitive type:基本數據類型
- void
所有類型的Class對象
public class Test02 {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; // voidClass c9 = Class.class; // ClassSystem.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);// 只要元素類型與維度一樣,就是同一個Classint[] a = new int[10];int[] b = new int[100];System.out.println(a.getClass().hashCode());System.out.println(b.getClass().hashCode());} }輸出:
class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
class java.lang.Class
2133927002
2133927002
從內存角度分析類加載【重點】
【重點】可參考B站視頻:【狂神說Java】注解和反射
(方法區可以看作特殊的堆內存)
類加載的過程
加載:
- 將 class 文件字節碼內容加載到內存中,并將這些靜態數據轉換成方法區的運行時數據結構,然后生成一個代表這個類的java.lang.Class 對象
鏈接:
(將 Java 類的二進制代碼合并到 JVM 的運行狀態之中的過程)
- 驗證:確保加載的類信息符合 JVM 規范,沒有安全方面的問題
- 準備:正式為類變量(static)分配內存并設置類變量默認初始值的階段,這些內存都將在方法區中進行分配
- 解析:虛擬機常量池內的符號引用(常量名)替換為直接引用(地址)的過程
初始化:
- (JVM)執行類構造器< clint>() 方法的過程,類構造器< clint>()方法是由編譯期自動收集類中所有類變量的賦值動作和靜態代碼塊中的語句合并產生的(類構造器是構造類信息的,不是構造該類對象的構造器)
- 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化
- 虛擬機會保證一個類的< clint>()方法在多線程環境中被正確加鎖和同步
輸出:
A 類靜態代碼塊初始化
A 類的無參構造函數初始化
100
什么時候會發生類的初始化
類的主動引用(一定會發生類的初始化):
- 當虛擬機啟動,先初始化main方法所在的類
- new一個類的對象
- 調用類的靜態成員(除了final常量)和靜態方法
- 使用java.lang.reflect包的方法對類進行反射調用
- 當初始化一個類,如果父類沒有被初始化,則先會初始化它的父類
類的被動引用(不會發生類的初始化):
- 當訪問一個靜態域時,只有真正聲明這個域的類才會被初始化。如:當通過子類引用父類的靜態變量,不會導致子類初始化
- 通過數組定義類引用,不會觸發此類的初始化
- 引用常量不會觸發此類的初始化(常量在鏈接階段就存入調用類的常量池中了)
類加載器
類加載器作用:
輸出:
sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@7f31245a null =============================== sun.misc.Launcher$AppClassLoader@18b4aac2 null =============================== C:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar; C:\Program Files\Java\jdk1.8.0_201\jre\lib\deploy.jar; C:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar; ...(內容過多,不予展示)獲取運行時類的完整結構
通過反射獲取運行時類的完整結構:
- 實現的全部接口
- 所繼承的父類
- 全部的構造器
- 全部的方法
- 全部的 Field
- 注解
- …
有了Class對象能做什么
**創建類的對象:**調用Class對象的newInstance()方法
沒有無參構造器也能創建對象。只要在操作的時候明確調用類中的構造器,并將參數傳遞進去之后,才可以實例化操作。
步驟如下:
調用指定的方法:
通過反射,調用類中的方法,通過Method類完成
- 通過Class類的getMethod(String name,Class…parameterTypes)方法取得一個Method對象,并設置此方法操作時所需要的參數類型
- 之后使用Object invoke(Object obj, Object[] args)進行調用,并向方法中傳遞要設置的obj對象的參數信息
Object invoke(Object obj, Object… args) - Object對應原方法的返回值,若原方法無返回值,此時返回null
- 若原方法為靜態方法,此時形參Object obj可為null
- 若原方法形參列表為空,則Object[] args可為null
- 若原方法聲明為private,則需要在調用此invo()方法前,顯式調用對象的setAccessible(true)方法,即可訪問private方法
setAccessible
- Method、Field、Constructor對象都有setAccessible()方法
- setAccessible作用是啟動和禁用訪問安全檢查的開關
- 參數值為true則指示反射的對象在使用時應該取消Java語言訪問檢查,使得原本無法訪問的私有成員也可以訪問。可以提高反射的效率。如果代碼中必須使用反射,而該句代碼需要頻繁地被調用,請設置為true
性能對比分析
上面說到了setAccessible(true)可以提高反射效率,下面開始對比普通方式調用和反射方式調用(包括反射方式的setAccessible()開啟和禁用)的效率
//分析性能問題 public class Test08 {// 普通方式調用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("普通方式執行 10 億次:" + (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("反射方式執行 10 億次:" + (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("關閉檢測方式執行 10 億次:" + (endTime - startTime) + "ms");}public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {test01();test02();test03();} }輸出:
普通方式執行 10 億次:5ms
反射方式執行 10 億次:1692ms
關閉檢測方式執行 10 億次:1331ms
通過反射操作泛型
- Java 采用泛型擦除的機制來引入泛型,Java 中的泛型僅僅是給編譯器 javac使用的,確保數據的安全性和免去強制類型轉換問題,但是,一旦編譯完成,所有和泛型有關的類型全部擦除
- 為了通過反射操作這些類型,Java 新增了 ParameterizedType,GenericArrayType,TypeVariable和 WildcardType 幾種類型來代表不能被歸一到 Class 類中的類型但是又和原始類型齊名的類型。
ParameterizedType:表示一種參數化類型,比如 Collection< String>
GenericArrayType:表示一種元素類型時參數化類型或者類型變量的數組類型
TypeVariable :是各種類型變量的公共父接口
WildcardType :代表一種通配符類型表達式
通過反射操作注解
package com.cheng.reflection;import java.lang.annotation.*; import java.lang.reflect.Field;public class Test10 {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {Class c1 = Class.forName("com.cheng.reflection.Stu");// 通過反射獲得注解Annotation[] annotations = c1.getAnnotations();for (Annotation annotation : annotations) {System.out.println("---->"+annotation);}// 獲得注解的value的值TableStu tableStu = (TableStu)c1.getAnnotation(TableStu.class);String value = tableStu.value();System.out.println(value);// 獲得類指定注解Field f = c1.getDeclaredField("id");FieldStu annotation = f.getAnnotation(FieldStu.class);System.out.println(annotation.columnName());System.out.println(annotation.type());System.out.println(annotation.length());} }@TableStu("db_stu") class Stu{@FieldStu(columnName = "db_id", type = "int", length = 10)private int id;@FieldStu(columnName = "db_age", type = "int", length = 10)private int age;@FieldStu(columnName = "db_name", type = "varchar", length = 3)private String name;public Stu() {}public Stu(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;}@Overridepublic String toString() {return "Stu{" +"id=" + id +", age=" + age +", name='" + name + '\'' +'}';} }// 類名的注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface TableStu{String value(); }// 屬性的注解 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface FieldStu{String columnName();String type();int length(); }總結
以上是生活随笔為你收集整理的Java学习笔记7-2——注解与反射的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022年焊工(初级)考试及焊工(初级)
- 下一篇: LPWA技术:发展物联网的最佳选择