Java(老白再次入門) - Java反射機制

本系列文章均爲尚硅谷資源!如有侵權,我將立即刪除!

Java基礎知識圖解

在這裏插入圖片描述

1.Java反射機制概述

Java Reflection

😃 Reflection(反射)是被視爲動態語言的關鍵,反射機制允許程序在執行期藉助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法。
😃 加載完類之後,在堆內存的方法區中就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就包含了完整的類的結構信息。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之爲:反射。
在這裏插入圖片描述

補充:動態語言 vs 靜態語言

1、動態語言
是一類在運行時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被刪除或是其他結構上的變化。通俗點說就是在運行時代碼可以根據某些條件改變自身結構。
主要動態語言:Object-C、C#、JavaScript、PHP、Python、Erlang。

2、靜態語言
與動態語言相對應的,運行時結構不可變的語言就是靜態語言。如Java、C、C++。

Java不是動態語言,但Java可以稱之爲“準動態語言”。即Java有一定的動態性,我們可以利用反射機制、字節碼操作獲得類似動態語言的特性。Java的動態性讓編程的時候更加靈活!

Java反射機制研究及應用

Java反射機制提供的功能

  • 在運行時判斷任意一個對象所屬的類
  • 在運行時構造任意一個類的對象
  • 在運行時判斷任意一個類所具有的成員變量和方法
  • 在運行時獲取泛型信息
  • 在運行時調用任意一個對象的成員變量和方法
  • 在運行時處理註解
  • 生成動態代理
反射相關的主要API
  • java.lang.Class:代表一個類
  • java.lang.reflect.Method:代表類的方法
  • java.lang.reflect.Field:代表類的成員變量
  • java.lang.reflect.Constructor:代表類的構造器
  • … …

2.理解Class類並獲取Class的實例

Class 類

在Object類中定義了以下的方法,此方法將被所有子類繼承:

  • public final 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() 返回該類的類加載器
Class getSuperclass() 返回表示此Class所表示的實體的超類的Class
Constructor[] getConstructors() 返回一個包含某些Constructor對象的數組
Field[] getDeclaredFields() 返回Field對象的一個數組
Method getMethod(String name,Class … paramTypes) 返回一個Method對象,此對象的形參類型爲paramType
反射的應用舉例
String str = "test4.Person";
Class clazz = Class.forName(str);
Object obj = clazz.newInstance();
Field field = clazz.getField("name");
field.set(obj, "Peter");
Object name = field.get(obj);
System.out.println(name);

注:test4.Person是test4包下的Person類

獲取Class類的實例(四種方法)

  1. 前提:若已知具體的類,通過類的class屬性獲取,該方法最爲安全可靠,程序性能最高
    實例Class clazz = String.class;
  2. 前提:已知某個類的實例,調用該實例的getClass()方法獲取Class對象
    實例Class clazz = “www.atguigu.com”.getClass();
  3. 前提:已知一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能拋出ClassNotFoundException
    實例Class clazz = Class.forName(“java.lang.String”);
  4. 其他方式(不做要求)
    ClassLoader cl = this.getClass().getClassLoader();
    Class clazz4 = cl.loadClass(“類的全類名”);
哪些類型可以有Class對象?
  1. class:
    外部類,成員(成員內部類,靜態內部類),局部內部類,匿名內部類
  2. interface:接口
  3. []:數組
  4. enum:枚舉
  5. annotation:註解@interface
  6. primitive type:基本數據類型
  7. void
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要元素類型與維度一樣,就是同一個Class
System.out.println(c10 == c11);

3.類的加載與ClassLoader的理解

瞭解:類的加載過程

當程序主動使用某個類時,如果該類還未被加載到內存中,則系統會通過如下三個步驟來對該類進行初始化。
在這裏插入圖片描述

加載:將class文件字節碼內容加載到內存中,並將這些靜態數據轉換成方法區的運行時數據結構,然後生成一個代表這個類的java.lang.Class對象,作爲方法區中類數據的訪問入口(即引用地址)。所有需要訪問和使用類數據只能通過這個Class對象。這個加載的過程需要類加載器參與。
鏈接:將Java類的二進制代碼合併到JVM的運行狀態之中的過程。

  • 驗證:確保加載的類信息符合JVM規範,例如:以cafe開頭,沒有安全方面的問題
  • 準備:正式爲類變量(static)分配內存並設置類變量默認初始值的階段,這些內存都將在方法區中進行分配。
  • 解析:虛擬機常量池內的符號引用(常量名)替換爲直接引用(地址)的過程。

初始化

  • 執行 類構造器() 方法的過程。 類構造器()方法是由編譯期自動收集類中所有類變量的賦值動作和靜態代碼塊中的語句合併產生的。(類構造器是構造類信息的,不是構造該類對象的構造器)。
  • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類 的初始化。
  • 虛擬機會保證一個類的 <clinit>() 方法在多線程環境中被正確加鎖和同步。
public class ClassLoadingTest {
	public static void main(String[] args) {
		System.out.println(A.m);
	} 
}
class A {
	static { m = 300;
	}
	static int m = 100;
}
//第二步:鏈接結束後m=0
//第三步:初始化後,m的值由<clinit>()方法執行決定
// 這個A的類構造器<clinit>()方法由類變量的賦值和靜態代碼塊中的語句按照順序合併產生,類似於
// <clinit>(){
// m = 300;
// m = 100;
// }
瞭解:什麼時候會發生類初始化?

類的主動引用(一定會發生類的初始化)

  • 當虛擬機啓動,先初始化main方法所在的類
  • new一個類的對象
  • 調用類的靜態成員(除了final常量)和靜態方法
  • 使用java.lang.reflect包的方法對類進行反射調用
  • 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類

類的被動引用(不會發生類的初始化)

  • 當訪問一個靜態域時,只有真正聲明這個域的類纔會被初始化
    當通過子類引用父類的靜態變量,不會導致子類初始化
  • 通過數組定義類引用,不會觸發此類的初始化
  • 引用常量不會觸發此類的初始化(常量在鏈接階段就存入調用類的常量池中了)

public class ClassLoadingTest {									class Father {
		public static void main(String[] args) {					static int b = 2;
	/* 主動引用:一定會導致A和Father的初始化*/							static {
		/*  A a = new A();	*/													System.out.println("父類被加載");
	/* System.out.println(A.m);		*/									}		
	/* Class.forName("com.atguigu.java2.A");	*/					}	
	/* 被動引用	*/												
	A[] array = new A[5];/*不會導致A和Father的初始化	*/			class A extends Father {
	/* System.out.println(A.b);//只會初始化Father	*/					static {
	/* System.out.println(A.M);//不會導致A和Father的初始化	*/				System.out.println("子類被加載");
}																			m = 300;
	static {															}
		System.out.println("main所在的類");								static int m = 100;
	} 																	static final int M = 1;
}																	}	
																

在這裏插入圖片描述

類加載器的作用:

  • 類加載的作用:將class文件字節碼內容加載到內存中,並將這些靜態數據轉換成方法區的運行時數據結構,然後在堆中生成一個代表這個類的java.lang.Class對象,作爲方法區中類數據的訪問入口。
  • 類緩存:標準的JavaSE類加載器可以按要求查找類,但一旦某個類被加載到類加載器中,它將維持加載(緩存)一段時間。不過JVM垃圾回收機制可以回收這些Class對象。
瞭解:ClassLoader

類加載器作用是用來把類(class)裝載進內存的。JVM 規範定義瞭如下類型的類的加載器。

在這裏插入圖片描述

1.獲取一個系統類加載器

  • ClassLoader classloader = ClassLoader.getSystemClassLoader();
  • System.out.println(classloader);

2.獲取系統類加載器的父類加載器,即擴展類加載器

  • classloader = classloader.getParent();
  • System.out.println(classloader);

3.獲取擴展類加載器的父類加載器,即引導類加載器

  • classloader = classloader.getParent();
  • System.out.println(classloader);

4.測試當前類由哪個類加載器進行加載

  • classloader = Class.forName(“exer2.ClassloaderDemo”).getClassLoader();
  • System.out.println(classloader);

5.測試JDK提供的Object類由哪個類加載器加載

  • classloader =
  • Class.forName(“java.lang.Object”).getClassLoader();
  • System.out.println(classloader);

6.關於類加載器的一個主要方法:getResourceAsStream(String str):獲取類路徑下的指定文件的輸入流

  • InputStream in = null;
  • in = this.getClass().getClassLoader().getResourceAsStream(“exer2\test.properties”);
  • System.out.println(in);

4.創建運行時類的對象

有了Class對象,能做什麼?

創建類的對象: 調用Class對象的newInstance()方法
要 求:

  1. 類必須有一個無參數的構造器。
  2. 類的構造器的訪問權限需要足夠。

難道沒有無參的構造器就不能創建對象了嗎?
不是!只要在操作的時候明確的調用類中的構造器,並將參數傳遞進去之後,纔可以實例化操作。
步驟如下:

  1. 通過Class類的**getDeclaredConstructor(Class … parameterTypes)**取得本類的指定形參類型的構造器
  2. 向構造器的形參中傳遞一個對象數組進去,裏面包含了構造器中所需的各個參數。
  3. 通過Constructor實例化對象。
    在這裏插入圖片描述

以上是反射機制應用最多的地方。


**1.根據全類名獲取對應的Class對象**
  • String name = “atguigu.java.Person";
  • Class clazz = null;
  • clazz = Class.forName(name);

2.調用指定參數結構的構造器,生成Constructor的實例

  • Constructor con = clazz.getConstructor(String.class,Integer.class);

3.通過Constructor的實例創建對應類的對象,並初始化類屬性

  • Person p2 = (Person) con.newInstance(“Peter”,20);
  • System.out.println(p2);

5.獲取運行時類的完整結構

通過反射獲取運行時類的完整結構

Field、Method、Constructor、Superclass、Interface、Annotation
  • 實現的全部接口
  • 所繼承的父類
  • 全部的構造器
  • 全部的方法
  • 全部的Field

使用反射可以取得:
1.實現的全部接口

  • public Class<?>[] getInterfaces()
    確定此對象所表示的類或接口實現的接口。

2.所繼承的父類

  • public Class<? Super T> getSuperclass()
    返回表示此 Class 所表示的實體(類、接口、基本類型)的父類的Class。

3.全部的構造器

  • public Constructor[] getConstructors()
    返回此 Class 對象所表示的類的所有public構造方法。
  • public Constructor[] getDeclaredConstructors()
    返回此 Class 對象表示的類聲明的所有構造方法。

Constructor類中:

  • 取得修飾符: public int getModifiers();
  • 取得方法名稱: public String getName();
  • 取得參數的類型:public Class<?>[] getParameterTypes();

4.全部的方法

  • public Method[] getDeclaredMethods()
    返回此Class對象所表示的類或接口的全部方法
  • public Method[] getMethods()
    返回此Class對象所表示的類或接口的public的方法

Method類中:

  • public Class<?> getReturnType()取得全部的返回值
  • public Class<?>[] getParameterTypes()取得全部的參數
  • public int getModifiers()取得修飾符
  • public Class<?>[] getExceptionTypes()取得異常信息

5.全部的Field

  • public Field[] getFields()
    返回此Class對象所表示的類或接口的public的Field。
  • public Field[] getDeclaredFields()
    返回此Class對象所表示的類或接口的全部Field。

Field方法中:

  • public int getModifiers() 以整數形式返回此Field的修飾符
  • public Class<?> getType() 得到Field的屬性類型
  • public String getName() 返回Field的名稱。

6. Annotation相關

  • get Annotation(Class annotationClass)
  • getDeclaredAnnotations()

7.泛型相關
獲取父類泛型類型:Type getGenericSuperclass()
泛型類型:ParameterizedType
獲取實際的泛型類型參數數組:getActualTypeArguments()
8.類所在的包 Package getPackage()


小 結:

  1. 在實際的操作中,取得類的信息的操作代碼,並不會經常開發。
  2. 一定要熟悉java.lang.reflect包的作用,反射機制。
  3. 如何取得屬性、方法、構造器的名稱,修飾符等。

6.調用運行時類的指定結構

1.調用指定方法

通過反射,調用類中的方法,通過Method類完成。步驟:

  1. 通過Class類的getMethod(String name,Class…parameterTypes)方法取得一個Method對象,並設置此方法操作時所需要的參數類型。
  2. 之後使用Object invoke(Object obj, Object[] args)進行調用,並向方法中傳遞要設置的obj對象的參數信息。

在這裏插入圖片描述

Object invoke(Object obj, Object … args)

說明:

  1. Object 對應原方法的返回值,若原方法無返回值,此時返回null
  2. 若原方法若爲靜態方法,此時形參Object obj可爲null
  3. 若原方法形參列表爲空,則Object[] argsnull
  4. 若原方法聲明爲private,則需要在調用此invoke()方法前,顯式調用方法對象的setAccessible(true)方法,將可訪問private的方法。

2.調用指定屬性

在反射機制中,可以直接通過Field類操作類中的屬性,通過Field類提供的**set()get()**方法就可以完成設置和取得屬性內容的操作。

  • public Field getField(String name) 返回此Class對象表示的類或接口的指定的public的Field。
  • public Field getDeclaredField(String name) 返回此Class對象表示的類或接口的 指定的Field。

在Field中

  • public Object get(Object obj) 取得指定對象obj上此Field的屬性內容
  • public void set(Object obj,Object value) 設置指定對象obj上此Field的屬性內容
關於setAccessible方法的使用

😃 Method和Field、Constructor對象都有setAccessible()方法。
😃 setAccessible啓動和禁用訪問安全檢查的開關。
😃 參數值爲true則指示反射的對象在使用時應該取消Java語言訪問檢查。

  • 提高反射的效率。如果代碼中必須用反射,而該句代碼需要頻繁的被調用,那麼請設置爲true。
  • 使得原本無法訪問的私有成員也可以訪問

😃 參數值爲false則指示反射的對象應該實施Java語言訪問檢查。

7.反射的應用:動態代理

代理設計模式的原理:
使用一個代理將對象包裝起來, 然後用該代理對象取代原始對象。任何對原始對象的調用都要通過代理。代理對象決定是否以及何時將方法調用轉到原始對象上。
😃 之前爲大家講解過代理機制的操作,屬於靜態代理,特徵是代理類和目標對象的類都是在編譯期間確定下來,不利於程序的擴展。同時,每一個代理類只能爲一個接口服務,這樣一來程序開發中必然產生過多的代理。最好可以通過一個代理類完成全部的代理功能。

😃 動態代理是指客戶通過代理類來調用其它對象的方法,並且是在程序運行時根據需要動態創建目標類的代理對象。

😃 動態代理使用場合:

  • 調試
  • 遠程方法調用

😃 動態代理相比於靜態代理的優點:

  • 抽象角色中(接口)聲明的所有方法都被轉移到調用處理器一個集中的方法中 處理,這樣,我們可以更加靈活和統一的處理衆多的方法。
Java動態代理相關API

Proxy :專門完成代理的操作類,是所有動態代理類的父類。通過此類爲一個或多個接口動態地生成實現類。


提供用於創建動態代理類和動態代理對象的靜態方法
在這裏插入圖片描述

動態代理步驟

1.創建一個實現接口InvocationHandler的類,它必須實現invoke方法,以完成代理的具體操作。
在這裏插入圖片描述
2.創建被代理的類以及接口
在這裏插入圖片描述
3.通過Proxy的靜態方法
newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 創建一個Subject接口代理

RealSubject target = new RealSubject();
// Create a proxy to wrap the original implementation
DebugProxy proxy = new DebugProxy(target);
// Get a reference to the proxy through the Subject interface
Subject sub = (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),new Class[] { Subject.class }, proxy);

4.通過 Subject代理調用RealSubject實現類的方法

  • String info = sub.say(“Peter", 24);
  • System.out.println(info);
動態代理與AOP(Aspect Orient Programming)

前面介紹的Proxy和InvocationHandler,很難看出這種動態代理的優勢,下面介紹一種更實用的動態代理機制
在這裏插入圖片描述
在這裏插入圖片描述
改進後的說明:代碼段1、代碼段2、代碼段3和深色代碼段分離開了,但代碼段1、2、3又和一個特定的方法A耦合了!最理想的效果是:代碼塊1、2、3既可以執行方法A,又無須在程序中以硬編碼的方式直接調用深色代碼的方法

public interface Dog{
	void info();
	void run();
}

public class HuntingDog implements Dog{
	public void info(){
		System.out.println("我是一隻獵狗");
	}
	public void run(){
		System.out.println("我奔跑迅速");
	} 
}
public class DogUtil{
	public void method1(){
		System.out.println("=====模擬通用方法一=====");
	}
	public void method2(){
		System.out.println("=====模擬通用方法二=====");
	} 
}
public class MyInvocationHandler implements InvocationHandler{
	// 需要被代理的對象
	private Object target;
	public void setTarget(Object target){
		this.target = target;
	}
	// 執行動態代理對象的所有方法時,都會被替換成執行如下的invoke方法
	public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
		DogUtil du = new DogUtil();
		// 執行DogUtil對象中的method1。
		du.method1();
		// 以target作爲主調來執行method方法
		Object result = method.invoke(target , args);
		// 執行DogUtil對象中的method2。
		du.method2();
		return result;
	}
}
public class MyProxyFactory{
	// 爲指定target生成動態代理對象
	public static Object getProxy(Object target) throws Exception{
		// 創建一個MyInvokationHandler對象
		MyInvokationHandler handler = new MyInvokationHandler();
	// 爲MyInvokationHandler設置target對象
	handler.setTarget(target);
	// 創建、並返回一個動態代理對象
	return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , handler);
	} 
}
public class Test{
	public static void main(String[] args) throws Exception{
		// 創建一個原始的HuntingDog對象,作爲target
		Dog target = new HuntingDog();
		// 以指定的target來創建動態代理
		Dog dog = (Dog)MyProxyFactory.getProxy(target);
		dog.info();
		dog.run();
	} 
}
動態代理與AOP(Aspect Orient Programming)

😃 使用Proxy生成一個動態代理時,往往並不會憑空產生一個動態代理,這樣沒有太大的意義。通常都是爲指定的目標對象生成動態代理
😃 這種動態代理在AOP中被稱爲AOP代理,AOP代理可代替目標對象,AOP代理包含了目標對象的全部方法。但AOP代理中的方法與目標對象的方法存在差異:AOP代理裏的方法可以在執行目標方法之前、之後插入一些通用處理

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章