反射和類加載機制

一 類加載機制

1.1 加載機制簡介

Java程序從編寫到運行這個過程大致可以分為兩個階段:編譯階段運行階段

編譯階段指的是,java源代碼文件**(*.java)被java編譯器(javac)編譯成字節碼文件(*.class)**的過程。這個過程不需要直接與硬件打交道,可以說,編譯器無視操作系統,將源代碼文件按照一套特定的規則將其翻譯成一種特定的字節碼形式。咱們這里不展開敘述了。

而當程序開始運行了,那么類的生命周期也就開始了。類的生命周期分為七個階段,分別是加載,驗證,準備,解析,初始化,使用,卸載。

階段序號階段名稱說明
階段1加載將類的二進制數據加載到內存中,存放在方法區,并在堆區創建一個java.lang.Class對象,作為該類方法區數據的訪問入口。
階段2驗證確保類的二進制數據符合JVM規范,包括字節碼格式、變量和方法定義的有效性等。 A.class文件
階段3準備為類的靜態變量分配內存,并將其初始化為默認值。
階段4解析將類、接口、字段和方法的符號引用轉換為直接引用。 String a = “Helloworld”
階段5初始化執行類中的初始化代碼塊和靜態初始化器。
階段6使用類被應用程序使用。
階段7卸載當類不再被使用且沒有其他類引用時,從內存中卸載。

參考下圖:

在這里插入圖片描述

1.2 類的加載時機

Java類的加載時機是在該類首次被使用時,比如:

  • 啟動main方法時,main方法所在的類會被加載
  • 調用new關鍵字來創建對象時
  • 訪問類的靜態變量
  • 訪問類的靜態方法
  • 初始化子類時,父類也會被加載
  • 使用Class.forName("....")反射機制時

1.3 類加載過程解析

類加載過程是由類加載器完成的,它將字節碼(*.class)文件加載到JVM中。這個過程包括加載、鏈接(驗證、準備、解析)和初始化三個小階段

在這里插入圖片描述

1)加載階段

加載階段是指從文件系統、網絡或其他來源獲取類的字節碼文件,讀到方法區,然后在堆內存創建Class對象的過程,具體分為以下三件事情

  • 通過類的全限定名來獲取定義該類的二進制字節流,將字節碼文件讀進JVM的方法區中(jdk1.8以后為元空間Metaspace)
  • 在方法區中將該字節流所代表的靜態存儲結構轉化為運行時數據結構
  • 在堆內存中生成一個代表這個類的 java.lang.Class對象,作為方法區中該類信息的訪問入口。

2)鏈接階段

整個鏈接階段都是為了確保類的正確性和安全性,為類的后續使用提供了基礎

  • 驗證

    為了確保class文件的字節流包含的信息符合當前虛擬機的要求,不會危害虛擬機自身的安全。有文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。

  • 準備

    JVM 會在該階段對靜態變量分配內存并進行默認初始化(不同數據類型會有其默認初始值,如:int ---- 0,boolean ---- false 等)。這些變量的內存空間會在方法區中分配。

     public class ClassLoad {// 屬性=成員變量=字段public int n1 = 10;   //是實例屬性, 不是靜態變量,因此在準備階段,是不會分配內存的public static  int n2 = 20;  //n2是靜態變量,在該階段 JVM 會為其分配內存,n2 默認初始化的值為0 ,而不是20public static final  int n3 = 30;  //n3 被 static final 修飾,是常量, 它和靜態變量不一樣, 其一旦賦值后值就不變,因此其默認初始化 n3 = 30}
    
  • 解析

    將符號引用轉換為直接引用。符號引用是指通過字符串描述的類、字段、方法等,而直接引用則是內存地址或指針。

3)初始化階段

該階段會執行一個叫clinit的方法。該方法被叫做類構造器,由編譯器按照源文件中的代碼順序,自動收集類的所有靜態代碼塊和靜態變量賦值語句合并生成的。

看下面例子:

public class ClassLoad {public static void main(String[] args) throws ClassNotFoundException {System.out.println(B.num);// 直接使用類的靜態屬性,也會導致類的加載}
}class B {static { // 靜態代碼塊System.out.println("B 靜態代碼塊被執行");num = 300;}static int num = 100;// 靜態變量public B() {// 構造器System.out.println("B() 構造器被執行");}
}

輸出結果:

B 靜態代碼塊被執行

100

代碼說明:

1.加載階段: 加載 B類,并生成 B的 Class對象

2.連接階段: 進行默認初始化 num = 0

3.初始化階段: 執行 () 方法,該方法會依次自動收集類中的所有靜態變量的賦值操作和靜態代碼塊中的語句,并合并。如下:

clinit() {

? System.out.println(“B 靜態代碼塊被執行”);

? num = 300;

? num = 100;

}

注意:加載類的時候,具有同步機制控制。如下

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {//正因為有這個機制,才能保證某個類在內存中, 只有一份Class對象synchronized (getClassLoadingLock(name)) {//....}
}

1.4 類加載器說明

類的加載過程是由類加載器來完成的,類加載器根據一個類的全限定名讀取類的二進制字節流到JVM中,然后生成對應的java.lang.Class對象實例

虛擬機默認提供了3種類加載器,啟動類加載器(Bootstrap ClassLoader)、擴展類加載器(Extension ClassLoader)、應用類加載器(Application ClassLoader),如果有必要還可以加入自己定義的類加載器。

加載器類型說明
啟動類加載器(Bootstrap ClassLoader)負責加載\lib目錄 和 被-Xbootclasspath參數所指定的路徑中的類庫
擴展類加載器(Extension ClassLoader)負責加載\lib\ext目錄 和 被java.ext.dirs系統變量所指定的路徑中的所有類庫
應用程序類加載器(Application ClassLoader)負責加載用戶類路徑classPath所指定的類庫,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
自定義加載器(CustomClassLoader)由應用程序根據自身需要自定義,如 Tomcat、Jboss 都會根據 j2ee 規范自行實現。

任意一個類在 JVM 中的唯一性,是由加載它的類加載器和類的全限定名一共同確定的。因此,比較兩個類是否“相等”的前提是這兩個類是由同一個類加載器加載的,否則,即使兩個類來源于同一個 Class 文件,被同一個虛擬機加載,只要加載他們的類加載器不同,那這兩個類就必定不相等。

JVM 的類加載機制,規定一個類有且只有一個類加載器對它進行加載。而如何保證這個類只有一個類加載器對它進行加載呢?則是由雙親委派模型來實現的。

1.5 雙親委派模型

雙親委派模型要求除了頂層的啟動類加載器外,其余類加載器都應該有自己的父類加載器。(類加載器之間的父子關系不是以繼承的關系實現,而是使用組合關系來復用父加載器的代碼

在這里插入圖片描述

工作原理

如果類加載器收到了類加載的請求,他首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層級的類加載器都是如此,因此所有請求最終都會被傳到最頂層的啟動類加載器中,只有當父類加載器反饋自己無法完成這個加載請求時,子加載器才會嘗試自己去加載。因此,加載過程可以看成自底向上檢查類是否已經加載,然后自頂向下加載類。

雙親委派模型的優點

  1. 使用雙親委派模型來組織類加載器之間的關系,Java類隨著它的類加載器一起具備了一種帶有優先級的層次關系。
  2. 避免類的重復加載,當父類加載器已經加載了該類時,子類加載器就沒必要再加載一次。
  3. 解決各個類加載器的基礎類的統一問題,越基礎的類由越上層的加載器進行加載。避免Java核心API中的類被隨意替換,規避風險,防止核心API庫被隨意篡改。

例如類 java.lang.Object,它存在在 rt.jar 中,無論哪一個類加載器要加載這個類,最終都是委派給處于模型最頂端的 Bootstrap ClassLoader 進行加載,因此 Object 類在程序的各種類加載器環境中都是同一個類。相反,如果沒有雙親委派模型而是由各個類加載器自行加載的話,如果用戶編寫了一個 java.lang.Object 的同名類并放在 ClassPath 中,那系統中將會出現多個不同的 Object 類,程序將混亂。因此,如果開發者嘗試編寫一個與 rt.jar 類庫中重名的 Java 類,可以正常編譯,但是永遠無法被加載運行。

1.6 類加載器源碼

實現雙親委派的代碼都集中在 java.lang.ClassLoader 的 loadClass() 方法之中,下面我們簡單看下 loadClass() 的源碼是怎么樣的:

public abstract class ClassLoader {// 每個類加載器都有一個父加載器private final ClassLoader parent;public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{// 首先,檢查該類是否已經加載過了Class<?> c = findLoadedClass(name);// 如果沒有加載過if (c == null) {if (parent != null) {// 先委托給父加載器去加載,注意這是個遞歸調用c = parent.loadClass(name, false);} else {// 如果父加載器為空,查找 Bootstrap 加載器是不是加載過了c = findBootstrapClassOrNull(name);}// 如果父加載器沒加載成功,調用自己的 findClass 去加載if (c == null) {        c = findClass(name);}} return c;}// ClassLoader 中 findClass 方式需要被子類覆蓋,下面這段代碼就是對應代碼protected Class<?> findClass(String name){//1. 根據傳入的類名 name,到在特定目錄下去尋找類文件,把 .class 文件讀入內存//...//2. 調用 defineClass 將字節數組轉成 Class 對象return defineClass(buf, off, len)}// 將字節碼數組解析成一個 Class 對象,用 native 方法實現protected final Class<?> defineClass(byte[] b, int off, int len){//...}   
}

二 反射機制簡介

2.1 反射的概念

在上一章節中,我們在談到類的加載時機時,提到過使用java的反射機制時,也會加載類。那么到底什么是反射機制,它能干什么,有哪些優缺點呢?

在這之前,你如何獲取一個類的實例對象?是不是只學習了一種方式,使用new關鍵字來調用構造器獲取實例。

而使用new關鍵字來獲取實例,有如下優點:

  • 1)性能高,JVM已經對這種調用進行了優化。
  • 2)不需要額外的權限檢查、直接調用構造器獲取實例、簡單方便

但是也有如下這些缺點:

  • 1)new是在編譯時創建對象,不能在運行時根據需要來動態的創建對象(編譯時:指的是JIT即時編譯期間,也就是將字節碼翻譯成本地機器碼時)
  • 2)new出來的對象只能訪問公共成員,不能訪問私有成員
  • 3)程序運行時,new只能獲取對象,不能獲取到類的信息(類全名、屬性、方法以及類型、修飾詞等)

為了體現java語言的健壯性、以及更好的推廣、Sun公司從JDK1.1版本開始、引入了反射機制這種高級特性,旨在提供一種在運行時動態的訪問類和對象的技術。

下面來看一下Oracle 官方對反射的解釋是:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions. The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

簡單來說,反射機制,就是讓程序在運行時,根據需求,可以獲取任意一個類的所有信息(私有的、公有的屬性和方法),可以調用對象的任意屬性和方法(包括私有的)。 而沒有反射機制,程序要想獲取這些信息,必須在運行前提前編譯好相關信息。

2.2 運行時加載

我們再來看看下面的代碼:

/*
*  編譯期,有兩個階段,
*  1. 第一個階段是*.java被翻譯成*.class文件的時候,這個階段我們稱之為前端編譯
*  2. 還有一個后端編譯,指的是*.class文件加載到內存后,在真正運行前,需要將字節碼翻譯成本地機器碼的過程。
* */
public class ClassLoad {public static void main(String[] args) throws ClassNotFoundException {Scanner sc = new Scanner(System.in);int key = sc.nextInt();switch(key) {case 0:// 普通代碼: 后端編譯時,編譯到此處時,會加載Cat類的字節碼文件到內存,// 進行各種驗證工作。即使程序在運行時不走進case 0這個分支Cat cat = new Cat(); break;case 1:// 反射機制代碼:后端編譯時,編譯到此處時,不會加載Dog這個類的Class文件,即使該字節碼文件不存在,也不會報錯。// 只有程序在運行時真正走到該行代碼時,才會正式加載Dog類Class<?> dogClass = Class.forName("com.youcai.day04.Dog");// 注意:如果換成下列寫法,Dog的字節碼文件就必須在后端編譯時,加載到內存中。無論是否走進case 1這個分支。Class<?> dogClass2 = Dog.class;break;}}
}
class Cat{}

從上面的案例中,我們可以發現:

  • 編譯時加載: 后端編譯時加載相關的類,如果沒有則報錯,依賴性太強。如果程序不執行到該處,還占用內存資源。(也叫靜態加載)
  • 運行時加載: 運行時加載需要的類,如果運行時不用該類,則不報錯,降低了依賴性,也不占用過多內存資源。(也叫動態加載)

而Java程序大多數時候都是編譯時加載,也就是對象的類型在編譯期就確定下來了,但是Java的反射機制不需要在編譯期就確定類型,而是在真正使用時,去確定類型。

2.3 反射的用途(面試題)

那么,來總結一下反射都可以干什么?

  • 運行時獲取類的信息: 在許多情況下,我們需要在運行時獲取類的信息,例如類的名稱、修飾符、父類、接口等
  • 運行時創建對象、調用方法: 這對于框架開發和插件系統非常有用(通過反射甚至可以調用private方法);
  • 運行時訪問和修改字段:反射甚至可以訪問私有成員變量
  • 動態代理:這是一種常用的設計模式。通過動態代理,我們可以在運行時生成代理對象,并在代理對象中添加額外的邏輯

java的反射,在很多框架和庫中被廣泛使用,例如Spring框架的依賴注入,Mybatis的resultType 對象等。相關API封裝在java.lang.reflect包下

2.4 反射與new區別

Pet p1 = new Pet("貓","花花",2); //直接初始化,[正射] [正向]
p1.eat("魚骨頭");//反射獲取對象,調用方法 
Class clz = Class.forName("com.se.day11.aClass.Pet");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);

兩者都可以創建對象,但是他們之間有以下區別:

  • 調用方式上

    new直接找到構造器創建對象實例,簡單直接。 反射通過java.lang.Class類的newInstance()方法或Constructor類的實例來創建對象。這種方式需要在運行時動態獲取類的信息,然后通過反射機制創建對象。

  • 創建對象的時機上

    使用反射可以在運行時動態地創建對象,而使用new是在編譯時創建對象。

  • 性能上

    反射的性能比使用new要差,因為在反射創建對象時需要進行類型檢查和方法查找等操作,而使用new則不需要進行這些操作。

  • 安全性上

    直接通過new關鍵字創建對象,不需要額外的權限檢查,比較安全。而反射需要更多的權限檢查,且在運行時動態獲取類信息,可能會引發安全問題。

  • 應用場景上

    new適用于所有需要創建對象的場景,是最常用的方式。而反射適用于需要動態創建對象的情況,例如在框架中動態加載類并創建對象,或者在測試中動態調用方法等。

2.4 反射的優缺點

優點

  1. 提高程序的靈活性:

  2. 反射允許在程序運行時獲取和修改對象和類的信息,這使得程序更加動態和靈活,不需要硬編碼。例如,即使是一個private的字段,通過反射也可以獲取其值。

  3. 降低耦合性,提升復用率:

  4. 使用反射可以在運行時動態地確定類,降低了代碼之間的耦合度,提高了代碼的復用率。例如,動態代理技術就利用了反射來在運行時確定具體的類。

  5. 反射還可以用于實現插件架構和模塊化設計,使得程序更加易于擴展和維護。

缺點:

  1. 性能不如直接調用:

    反射需要動態加載類,獲取類的信息,進行安全檢查等額外開銷,這些都會影響程序的性能。相比于直接調用,反射的效率較低。

  2. 代碼可讀性下降:

    反射的動態性使得代碼難以閱讀和維護,缺乏類型檢查,錯誤只能在運行時發現,增加了調試的難度。

  3. 可能破壞代碼的抽象性:

    反射可以繞過final等限制訪問的屬性或方法,可能會破壞代碼的封裝性和抽象性,影響代碼的穩定性和安全性。

因此,如果不需要動態地創建一個對象,那么就別用反射;

三 Class類介紹

3.1 簡要說明

Java語言中的任何類型在磁盤上都有一個獨立*.class字節碼文件,該文件以一種特殊的數據結構來組織和描述一個類的所有信息。每當一個字節碼文件被加載到JVM的內存中時,在堆內存中都會創建一個對象與之對應,用來描述該字節碼文件里的所有信息。

舉例說明

Person.class字節碼文件-----> 堆中創建一個對象,描述信息可能是: 類名是Person, 三個屬性,兩個構造器,三個方法 Cat.class字節碼文件--------> 堆中創建一個對象,描述信息可能是: 類名是Cat, 沒有屬性,一個構造器,一個方法 Employee.class字節碼文件----> 堆中創建一個對象,描述信息可能是: 類名是Employee, 十個屬性,四個構造器,六個方法 … 如果讓你來給堆中的這些對象抽象出一個類型來,你會怎么做? 應該如下吧: class DescribeClass{ String nameClass, Field[] fieldInfo, Construct[] constructInfo, Method[] methodInfo, … }

而Java語言設計者,在最初時,就已經設計出來了相關類型,其中一個最重要的類型名,就叫Class,注意,開頭字母是大寫的C

Class是Java中的一個特殊的類型, 用來描述Java中比如每一個類、 接口、 枚舉…編譯后生成的.class字節碼文件,里面封裝的信息比如屬性、構造方法、 方法等。這個類也是繼承自Object類。

簡單來說:Class類型就是用來描述Java中的其他任何類型。Class類型的一個實例就是對一個字節碼文件的描述。

再思考一個問題

在堆內存中,會有一個Class類型的實例來描述Person.class這個字節碼文件,也就是Person類型。那么有必要再創建一個Class類型的實例來描述Person類型嗎?

沒有必要,一個足夠了。因此Class類型的實例都是單例模式的。

3.2 Class對象的獲取

方式1:getClass方法

在Object類中, 有一個方法, 叫做 getClass()。 可以通過對象調用這個方法, 獲取到一個用來描述這個對象所對應的類的Class信息。


public class ClassDemo01 {public static void main(String[] args) {//使用對象的getClass()方法來獲取Class對象Person p1 = new Person();//Class 定義一個變量,指向了Person類型的類對象,每一個類都只且只有一個類型對象//   Person只有一個類對象 Person.class     Student類型也只有一個類對象Student.classClass<? extends Person> aClass = p1.getClass(); //獲取了對應的唯一的類對象}public static class Person{}
}

方式2:class屬性

可以使用類的屬性 class 獲取Class信息。


public class ClassDemo02 {public static void main(String[] args) {/*** 使用類的屬性class,來獲取本類的類對象。*/Class<Person> personClass = Person.class;System.out.println(personClass);}public static class Person{}
}

說明: 第一種方式和第二種方式,不能體現動態獲取一個類對象,因為在使用普通類的時候內存里早就已經加載了普通類的字節碼文件。

方式3:Class.forName方法(推薦)

由于在反射中,重點強調動態性。 類的獲取、 屬性的獲取、 方法的獲取、 構造方法的獲取, 都是要通過一個字符串進行獲取的, 我們甚至可以將一個需要加載的類名寫到一個配置文件中, 在程序中讀取這個配置文件中的數據, 加載不同的類。 這樣一來, 當需要進行不同的類加載的時候, 直接改這個配置文件即可, 其他程序不用修改。 再例如, 可以通過屬性的名字, 用反射獲取到對應的屬性, 并進行訪問; 通過方法的名字, 用反射獲取對應的方法, 并進行訪問。 因此, 上述的兩種Class對象的獲取都不夠動態, 我們需要用來獲取Class信息, 更多的使用的就是這個方法。

這里會出現異常: ClassNotFoundException, 就是字面意思, 沒有找到這個類, 很可能是因為類的名字寫錯了。 這里, 通過類的名字進行類的獲取, 需要寫類的全限定名, 即從最外層的包開始, 逐層向內查找, 直到找到這個類。 如果是內部類, 則需要用$進行向內查詢。 參考內部類編譯后生成的字節碼文件的格式信息。

這個方法, 會將類加載到內存, 如果是第一次加載, 會觸發這個類中的靜態代碼段。


public class ClassDemo03 {public static void main(String[] args) {/*** 使用Class.forName(String name)來獲取一個類的類對象。* name: 是一個字符串,指的是一個類全名,* 比如下面的Person類型的 類全名:com.qf.reflect.ClassDemo03$Person*/try {Class<?> aClass = Class.forName("com.qf.reflect.Inter1");System.out.println(aClass);} catch (ClassNotFoundException e) {e.printStackTrace();}}public static class Person{}
}
interface Inter1{}

3.3 Class常用API

方法解析
static Class<?> forName(String className)
Class<?> getClass()
ClassLoader getClassLoader()
Field[] getFields()
Field getField(String name)
Field[] getDeclaredFields()
Field getDeclaredField(String name)
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Method[] getMethods()
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
T newInstance()

3.3.1 查看屬性


import java.lang.reflect.Field;
import java.lang.reflect.Method;/*** 獲取成員變量*/
public class _01GetFieldDemo {public static void main(String[] args) {try{//獲取描述類的對象Class<?> aClass = Class.forName(ReflectionUtil.getClassNameString());//獲取所有的屬性,包括私有的Field[] declaredFields = aClass.getDeclaredFields();for (Field declaredField : declaredFields) {//獲取修飾詞System.out.println("修飾詞:"+declaredField.getModifiers());//獲取屬性類型System.out.println("屬性類型:"+declaredField.getType());//獲取屬性名System.out.println("屬性名:"+declaredField.getName());}}catch (Exception e){e.printStackTrace();}}
}

3.3.2 查看構造器


import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;/*** 獲取所有的構造器,包括私有的** 修飾詞對應的是常量:* public ---> 1* private ---->2* protected---->4* static---->8* final---->16* synchronized--->32*/
public class _01GetAllConstructorDemo {public static void main(String[] args) {try {//先獲取描述類的對象,描述Person類型Class<?> aClass = Class.forName(ReflectionUtil.getClassNameString());//獲取所有的構造器Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();//遍歷數組for (Constructor<?> constructor : declaredConstructors) {//獲取構造器的修飾詞System.out.println("構造器修飾詞:"+constructor.getModifiers());//構造器的名字System.out.println("構造器名字:"+constructor.getName());//獲取構造器的參數Parameter[] parameters = constructor.getParameters();for (Parameter parameter : parameters) {System.out.println("參數類型:"+parameter.getType());System.out.println("參數名稱:"+parameter.getName());}System.out.println("----------------------------");}} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}
}

3.3.3 查看方法


import java.lang.reflect.Method;
import java.lang.reflect.Parameter;/*** 獲取一個類中所有的方法,包括私有的*/
public class _02GetAllMethodDemo {public static void main(String[] args) {try{//獲取描述Person類的類對象Class<?> aClass = Class.forName(ReflectionUtil.getClassNameString());//獲取所有的方法Method[] methods = aClass.getDeclaredMethods();//遍歷for (Method method : methods) {//方法的修飾詞System.out.println("修飾詞: " + method.getModifiers());//方法的返回值類型System.out.println("返回值類型:"+method.getReturnType());//方法名System.out.println("方法名:" + method.getName());//方法參數Parameter[] parameters = method.getParameters();for (Parameter parameter : parameters) {//參數類型System.out.println("參數類型:"+parameter.getType());}System.out.println("----------------------------");}}catch (Exception e){e.printStackTrace();}}
}

四 反射的基本操作

4.1 實例化對象

在反射中的對象實例化, 和之前面向對象部分不太一樣。 在反射部分, 我們更多強調的是動態, 動態的進行一個類的對象實例化。 只需要通過一個類名字符串, 即可完成對這個對象的實例化。 很多時候, 我們可以將需要加載的不同的類, 以配置文件的形式寫入到一個文件中, 可以使用程序讀取這個文件, 從而得到一個類的名字, 通過反射進行不同的對象的實例化。

  • 使用newInstance():

    是Class類中的一個非靜態的方法。 需要首先獲取到Class對象, 使用這個Class對象進行方法的調用, 完成對象的實例化。

  • 使用有權限的構造器

  • 使用無權限的構造器


public class InstanceDemo01 {public static void main(String[] args) {/*** 獲取com.qf.reflect.Person的類對象*/try {Class<?> aClass = Class.forName("com.qf.reflect.Person");/** 調用newInstance()來獲取com.qf.reflect.Person類的對象* 此方法會調用類的無參構造器來獲取對象** NoSuchMethodException:沒有對應的無參構造方法*/Object o = aClass.newInstance();//System.out.println(o.getClass().getName());} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {e.printStackTrace();}}
}

異常解析:

  • InstantiationException: 類中沒有無參構造方法, 有可能是因為在類中寫了有參構造方法了。
  • IllegalAccessException: 類中的無參構造方法的訪問權限不足, 在類外無法進行訪問。

4.1.2 方式2:指定構造方法

4.1.2.1 有權限的構造方法

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class InstanceDemo02 {public static void main(String[] args) throws ClassNotFoundException {//獲取類對象try {Class<?> aClass = Class.forName(ReflectUtil.getStringfromFile());/*** Constructor  getConstructor(Class .... parameters);* 通過制定參數的類對象以及順序,來獲取類的構造器**/Constructor<?> constructor = aClass.getConstructor(String.class, int.class);/***Object newInstance(Object .... paramenters)*** IllegalArgumentException:  調用的構造器的參數與實際參數的個數或者類型不匹配*/Object obj = constructor.newInstance("小明",23);System.out.println(obj);} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {e.printStackTrace();}}
}
4.1.2.2 無權限的構造方法

在反射中, 可以通過指定的方法, 獲取到一些訪問權限不足的成員。 例如, 可以通過 getDeclaredConstructor(Class...) 獲取私有權限的構造方法。 但是, 即便獲取到了訪問權限不足的成員, 依然無法直接使用。 如果直接使用會出現 IllegalAccessException 異常, 表示訪問權限不足。

此時, 可以使用 setAccessible 方法, 設置是否需要跳過權限的校驗。

setAccessible(boolean flag)參數true: 代表關閉訪問權限校驗, 即任意的訪問權限都可以訪問。參數false: 代表不關閉訪問權限校驗, 此時在進行成員訪問的時候, 依然需要進行權限的校驗。

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class InstanceDemo03 {public static void main(String[] args) throws ClassNotFoundException {//獲取類對象try {Class<?> aClass = Class.forName(ReflectUtil.getStringfromFile());/*** Constructor  getDeclaredConstructor(Class .... parameters);** 此方法可以獲取任意一種構造方法,包括私有的**/Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);/*** 調用setAccessable(boolean flag)方法,設置為true表示關閉權限的校驗* false:開啟校驗*/constructor.setAccessible(true);/***Object newInstance(Object .... paramenters)* IllegalArgumentException:  調用的構造器的參數與實際參數的個數或者類型不匹配*/Object obj = constructor.newInstance("小明");System.out.println(obj);} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {e.printStackTrace();}}
}

4.2 訪問屬性

  • 訪問有權限的非靜態屬性
  • 訪問有權限的非靜態屬性
  • 訪問靜態屬性
/*** 使用反射機制來訪問屬性*/
public class reflectDemo04 {public static void main(String[] args) {try {Class pClass = Class.forName("com.youcai.day10._06Reflect.Person");Constructor c1 = pClass.getDeclaredConstructor(String.class, int.class, char.class, double.class);Person o = (Person) c1.newInstance("小明", 17, '男', 18000);//獲取成員屬性 ageField ageFiled = pClass.getDeclaredField("age");//獲取屬性值:調用屬性的get方法(Object o)Object o1 = ageFiled.get(o);System.out.println(o1);//修改屬性值:調用屬性的set方法(Object o,Object newValue)ageFiled.set(o,18);System.out.println(o);//訪問靜態屬性:和對象沒有關系,因此第一個參數為nullField countFiled = pClass.getDeclaredField("count");Object initValue = countFiled.get(null);System.out.println("默認值"+initValue);//修改靜態屬性值countFiled.set(null,100);System.out.println("修改后的值"+countFiled.get(null));System.out.println("----------------");Field maxAgeFiled = pClass.getDeclaredField("MAX_AGE");System.out.println("MAX_AGE:"+maxAgeFiled.get(null));}catch (Exception e){e.printStackTrace();}}
}

4.3 訪問方法

  • 訪問有權限的無參方法
  • 訪問有權限的有參方法
  • 訪問有權限的靜態方法
  • 訪問無權限的方法

/*** 使用反射機制訪問方法* 1.成員方法* 2.靜態方法*/
public class reflectDemo05 {public static void main(String[] args) {try {Class pClass = Class.forName("com.youcai.day10._06Reflect.Person");System.out.println("-------------成員方法的訪問-------------");//獲取對象Object  o =  pClass.newInstance();System.out.println("賦值前的樣子:"+o);//獲取setName()方法Method m1 = pClass.getDeclaredMethod("setName", String.class);//調用invoke方法m1.invoke(o,"張三");System.out.println("賦值后的樣子:"+o);//獲取私有的方法Method m2 = pClass.getDeclaredMethod("show", null);m2.setAccessible(true);m2.invoke(o);//獲取帶有返回值類型的方法Method m3 = pClass.getDeclaredMethod("getName");Object rValue = m3.invoke(o);System.out.println("返回值:"+rValue);System.out.println("-------------靜態方法的訪問-------------");Method m4 = pClass.getDeclaredMethod("getPI");Object sValue = m4.invoke(null);System.out.println("靜態方法返回值:"+sValue);} catch (Exception e) {e.printStackTrace();}}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/93484.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/93484.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/93484.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在CentOS 7 上安裝 MySQL 數據庫

文章目錄前言一、使用官方 MySQL 倉庫安裝 MySQL1.1 下載并安裝 MySQL 官方 YUM 倉庫1.2 安裝 MySQL YUM 倉庫1.3 安裝 MySQL1.3.1 補充&#xff1a;1.4 啟動 MySQL 服務1.5 設置 MySQL 服務開機啟動1.6 獲取臨時 root 密碼1.7 配置 MySQL1.7.1 注意事項1.8 完成安裝二、使用默…

Linux:套接字

從進程的視角來看&#xff0c;網絡通信就是一個主機上的進程和另外一個主機上的進程進行信息傳遞&#xff0c;因此對于操作系統而言&#xff0c;網絡通信就是一種進程間通信的方式。不過這種進程間通信有特殊之處&#xff1a;同一臺主機下可以通過進程ID來標識一個唯一的進程&a…

Android init.rc詳解3

關于Android Init的詳解&#xff0c;關于Action&#xff0c;Service&#xff0c;Trigger的請參考Android init.rc詳解1&#xff0c;關于Options的請參考Android init.rc詳解2&#xff0c;本章將介紹常見的Commands。 1 Commands bootchart [start|stop] 啟動或停止bootcharti…

Sentinel原理之規則管理

文章目錄1. 基礎知識2. 數據源使用2.1 RedisDatasource2.2 ZookeeperDatasource1. 基礎知識 流量控制規則&#xff08;FlowRule&#xff09;&#xff1a; 閾值類型grade&#xff1a; 0&#xff08;并發線程數&#xff09;&#xff1a;限制同時處理請求的線程1&#xff08;QPS…

系統時鐘配置

STM32F103C8T6的系統時鐘配置成72MHZ1. 什么是 STM32 系統時鐘系統時鐘&#xff08;System Clock&#xff09;是整個 MCU&#xff08;微控制器&#xff09;運行的“節拍信號”&#xff0c;所有 CPU 指令執行、外設操作、定時器計時、總線數據傳輸等&#xff0c;都依賴這個時鐘頻…

Al大模型-本地私有化部署大模型-大模型微調

魔塔社區 魔塔社區平臺介紹 https://www.modelscope.cn/models/Qwen/Qwen2.5-0.5B-Instruct 申請免費的試用機器 如果自己沒有機器 &#xff0c;從這里申請機器 。 下載大模型 pip install modelscope 下載到當前目錄 mkdir -p /root/autodl-tmp/demo/Qwen/Qwen2.5-0.5B-Ins…

國內著名AI搜索優化專家孟慶濤發表《AI搜索內容可信度評估綜合指南》

近日&#xff0c;國內著名AI搜索優化專家、中國GEO生成式引擎優化領域的開拓者與實踐專家孟慶濤正式發布《AI搜索內容可信度評估綜合指南》&#xff0c;針對當前AI生成內容&#xff08;AIGC&#xff09;在搜索場景中可信度參差不齊的痛點&#xff0c;首次提出覆蓋"技術-內…

ruoyi-flowable系統防xss攻擊配置(使用富文本的方式)

背景。開發小程序過程中。用戶使用富文本的方式比較多。但在傳輸后發現如上傳到系統中的圖片鏈接地址被清空了。問題&#xff1a;想要使用富文本。還需要開啟xss過濾。有什么好的解決方案嗎&#xff1f;解決方案&#xff08;我比較傾向的&#xff09;&#xff1a;通過對富文本內…

【opencv-Python學習筆記(2): 圖像表示;圖像通道分割;圖像通道合并;圖像屬性】

目標&#xff1a;1.學會圖像的通道分割與合并2.學會圖像的的常規操作##一些概念&#xff1a;二值圖像&#xff1a;只包含黑色和白色兩種顏色的圖像&#xff0c;1為白色&#xff0c;0為黑色灰度圖像&#xff1a;計算機會將灰度處理為256個灰度級&#xff0c;用區間[0,255]來表示…

Qt——常用Widget(控件)

常用控件 Widget 需要說明&#xff0c;此處說明的控件都繼承于QWiget&#xff0c;因此之前所說的控件屬性&#xff0c;和相關API&#xff0c;在這里的控件都適用 文章目錄常用控件 Widget按鈕類控件QPushButtonQRadioButtonQCheckBox顯示類控件QLabel初識事件LCD NumberProgre…

Cursor/VSCode/VS2017 搭建Cocos2d-x環境,并進行正常的調試和運行(簡單明了)

作者&#xff1a;求一個demo 版權聲明&#xff1a;著作權歸作者所有&#xff0c;商業轉載請聯系作者獲得授權&#xff0c;非商業轉載請注明出處 內容通俗易懂&#xff0c;沒有廢話 廢話不多說&#xff0c;我們直接開始------>>>>>> &#xff01;&#xff…

從 LLM 到自主 Agent:OpenCSG 打造開源 AgenticOps 生態

從 LLM 到自主 Agent&#xff1a;OpenCSG 打造開源 AgenticOps 生態在產業拐點上&#xff0c;交付可持續、可落地的智能體未來在生成式 AI 的時代洪流中&#xff0c;大語言模型&#xff08;LLM&#xff09;已成為行業標配&#xff0c;但如何突破“會說不會做”的局限&#xff0…

黑馬程序員mysql課程中在Linux系統中安裝mysql出現問題

問題描述在安裝linux的最后一步的指令的時候報錯警告&#xff1a;mysql-community-server-8.0.26-1.el7.x86_64.rpm: 頭V3 DSA/SHA256 Signature, 密鑰 ID 5072e1f5: NOKEY 錯誤&#xff1a;依賴檢測失敗&#xff1a;net-tools 被 mysql-community-server-8.0.26-1.el7.x86_64 …

「iOS」————APP啟動優化

iOS學習APP的啟動流程啟動流程缺頁錯誤主要階段pre-main階段main階段啟動優化pre-mainmain階段啟動優化總結流程總結APP的啟動流程 啟動 首先我們來了解啟動的概念&#xff1a; 廣義上的啟動是點擊圖標到首頁數據加載完畢狹義上的啟動是點擊圖標到啟動圖完全消失的第一幀 啟…

知名車企門戶漏洞或致攻擊者遠程解鎖汽車并竊取數據

漏洞概況一家大型汽車制造商的在線系統存在安全漏洞&#xff0c;可能導致客戶數據泄露&#xff0c;并允許攻擊者遠程訪問車輛。該漏洞由安全研究員Eaton Zveare發現&#xff0c;他已于2025年2月向涉事車企報告并促使漏洞修復。Zveare雖未公開車企名稱&#xff0c;但透露這是在美…

Elasticsearch JS 自定義 ConnectionPool / Connection / Serializer、敏感信息脫敏與 v8 平滑遷移

0. 什么時候該用“高階配置”&#xff1f; 復雜網絡/路由需求&#xff1a;自定義“健康節點”判定、權重路由、多租戶隔離。替換 HTTP 棧&#xff1a;接入企業內網網關、打通自研代理/審計、細化超時/連接細節。序列化治理&#xff1a;為超大 JSON、Bulk、查詢串做定制編碼/壓縮…

希爾排序專欄

在排序算法的大家庭中&#xff0c;希爾排序&#xff08;Shell Sort&#xff09;以其獨特的 "分組插入" 思想占據著重要地位。它是對插入排序的創造性改進&#xff0c;通過引入 "增量分組" 策略&#xff0c;大幅提升了排序效率。本文將帶你深入理解希爾排序…

Android 歐盟網絡安全EN18031 要求對應的基本表格填寫

Android 歐盟網絡安全EN18031 要求對應的基本表格填寫 文章目錄Android 歐盟網絡安全EN18031 要求對應的基本表格填寫一、背景二、18031認證預填表格三、其他1、Android EN 18031 要求對應的基本表格小結2、EN 18031的要求表格內容填寫3、一定要做三方認證&#xff1f;4、歐盟網…

《Attention-driven GUI Grounding》論文精讀筆記

論文鏈接&#xff1a;[2412.10840] Attention-driven GUI Grounding: Leveraging Pretrained Multimodal Large Language Models without Fine-Tuning 摘要 近年來&#xff0c;多模態大型語言模型&#xff08;Multimodal Large Language Models&#xff0c;MLLMs&#xff09;的…

PIDGenRc函數中lpstrRpc的由來和InitializePidVariables函數的關系

第一部分&#xff1a;./base/ntsetup/syssetup/setupp.h:404:#define MAX_PID30_RPC 5BOOL InitializePidVariables() {//// Get the Pid from HKEY_LOCAL_MACHINE\SYSTEM\Setup\Pid//Error RegOpenKeyEx( HKEY_LOCAL_MACHINE,((MiniSetup || OobeSetup) ? szFinalPidKeyNa…