反射,反射程式設計師的快樂!為什麼我老是加班?為什麼我工資不如他多?原來是我不懂反射!

Java是一門准動態語言,是因為存在反射機制,如果你不會是不是就等於白學了?
看完不會,請評論,我親自給你解釋,嘻嘻!
在這裡插入圖片描述

什麼是動態語言?

  動態語言,是指程式在運行時可以改變其結構:新的函數可以被引進,已有的函數可以被刪除等在結構上的變化。比如JavaScript便是一個典型的動態語言。
  
除此之外如Ruby、Python、OC等也都屬於動態語言,而C、C++、Java等語言則不屬於動態語言。

動態類型語言,就是類型的檢查是在運行時做的,是不是合法的要到運行時才判斷,例如JavaScript就沒有編譯錯誤,只有運行錯誤。

靜態語言
  而靜態類型語言的類型判斷是在運行前判斷(如編譯階段),比如java就是靜態類型語言,靜態類型語言為了達到多態會採取一些類型鑒別手段,如繼承、介面,而動態類型語言卻不需要,

Java的反射機制被視為Java為準動態語言的主要的一個關鍵性質,這個機制允許程式在運行時透過反射取得任何一個已知名稱的class的內部資訊,包括:

正在運行中的類的屬性資訊,正在運行中的類的方法資訊,正在運行中的類的構造資訊,正在運行中的類的訪問修飾符,註解等等。

動態語言無時不刻在體現動態性,而靜態語言也在通過其他方法來趨近於去彌補靜態語言的缺陷。

為什麼么要使用反射:

  1. 反射是框架設計的靈魂
    框架: 半成品軟體。可以在框架的基礎上進行軟體開發,簡化編碼。學習框架並不需要了解反射,但是要是想自己寫一個框架,那麼就要對反射機制有很深入的了解。
  2. 解耦,提高程式的可擴展性
  3. 在運行時判斷任意一個對象所屬的類。
  4. 在運行時構造任意一個類的對象。
  5. 在運行時判斷任意一個類所具有的成員變數和方法。
  6. 在運行時調用任意一個對象的方法。

什麼是反射:

定義:

JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取資訊以及動態調用對象方法的功能稱為java語言的反射機制。

簡單來說: 將類的各個組成部分封裝成其他對象
在這裡插入圖片描述

反射機制的實現原理

在這裡插入圖片描述

Java程式碼在電腦中經歷的三個階段

  1. Source源程式碼階段:.java被編譯成.class位元組碼文件。
  2. Class類對象階段:*.class位元組碼文件被類載入器載入進記憶體,並將其封裝成Class對象(用於在記憶體中描述位元組碼文件),Class對象將原位元組碼文件中的成員變數抽取出來封裝成數組Field[],將原位元組碼文件中的構造函數抽取出來封裝成數組Construction[],在將成員方法封裝成Method[]。當然Class類內不止這三個,還封裝了很多,我們常用的就這三個。
  3. RunTime運行時階段:創建對象的過程new。
獲取Class對象的方式:
  1. Class.forname(“類全名”):
    將位元組碼載入進記憶體,返回Class對象。
    一般用於: 配置文件,將類名定義在配置文件中,讀取文件,載入類。
  2. 類名.class:
    通過類名的屬性Class獲取
    一般用於: 參數傳遞
  3. 對象.getclass()獲取:
    getclass()方法在Object類中定義
    一般用於: 對象獲取位元組碼的方式

補充:
同一個位元組碼文件(*.class)在一次程式運行中,只會被載入一次,不論通過哪一種方式獲取的Class對象都是同一個。

舉例:
在這裡插入圖片描述

public void Main() throws ClassNotFoundException {
    //方式一:Class.forName("全類名");
    Class cls1 = Class.forName("com.test.domain.Person");   //Person自定義實體類
    System.out.println("cls1 = " + cls1);

    //方式二:類名.class
    Class cls2 = Person.class;
    System.out.println("cls2 = " + cls2);

    //方式三:對象.getClass();
    Person person = new Person();        
    Class cls3 = person.getClass();
    System.out.println("cls3 = " + cls3);

    // == 比較三個對象
    System.out.println("cls1 == cls2 : " + (cls1 == cls2));    //true
    System.out.println("cls1 == cls3 : " + (cls1 == cls3));    //true
    //結論:同一個位元組碼文件(*.class)在一次程式運行過程中,只會被載入一次,無論通過哪一種方式獲取的Class對象都是同一個。
}

在這裡插入圖片描述

Class對象功能:
獲取功能:

1 獲取成員變數們

Field[] getFields() :獲取所有public修飾的成員變數
Field getField(String name)   獲取指定名稱的 public修飾的成員變數

Field[] getDeclaredFields()  獲取所有的成員變數,不考慮修飾符
Field getDeclaredField(String name)
//需要忽略訪問許可權修飾符的安全檢查 setAccessible(true):暴力反射,不然會報錯

具體測試看下文!

2.獲取構造方法們

Constructor<?>[] getConstructors()  
Constructor<T> getConstructor(類<?>... parameterTypes)  

Constructor<?>[] getDeclaredConstructors()  
Constructor<T> getDeclaredConstructor(類<?>... parameterTypes)  

具體測試看下文!

3.獲取成員方法們:

Method[] getMethods()  
Method getMethod(String name, 類<?>... parameterTypes)  

Method[] getDeclaredMethods()  
Method getDeclaredMethod(String name, 類<?>... parameterTypes)
//需要忽略訪問許可權修飾符的安全檢查 setAccessible(true):暴力反射,不然會報錯

具體測試看下文!

4.獲取全類名

String getName() 

getClass()方法是Object類的方法,需要注意一點獲取的類名是全類名(帶有路徑)
舉例:

package Test;
public class Reflect {
	public static void main(String[] args) throws Exception {
		Class Tst = Test.class;
		String s=Tst.getName(); 
		System.out.println(s);
	}
}

運行結果:
在這裡插入圖片描述

![在這裡插入圖片描述](//img-blog.csdnimg.cn/20200503024319516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYyNzExOA==,size_16,color_FFFFFF,t_70)
Field:成員變數
  1. 設置值 void set(Object obj, Object value)
  2. 獲取值 get(Object obj)

舉例:

package Test;
public class Test {
	public String a;
	protected String b;
	private String c;
	String d;
}


package Test;

import java.lang.reflect.Field;

public class Reflect {
	public static void main(String[] args) throws  Exception {
		Class Tst = Test.class;
		Test tst=new Test();
		System.out.println("-------------------測試getField--------------------");
		
		Field[] fields=Tst.getFields(); 
		for(Field f:fields)
		{
			System.out.println(f);
		}
		
		Field a=Tst.getField("a");
		a.set(tst, "我是設置值");
		System.out.println(a.get(tst));
		
		System.out.println("\n-------------測試getDeclaredField------------------");
		
		Field[] fields2=Tst.getDeclaredFields();
		for(Field f:fields2)
		{
			f.setAccessible(true);//不加出不來,詳情請看上文
			System.out.println(f);
		}
		
		Field b=Tst.getDeclaredField("b");
		b.set(tst, "我是私有的設置值");
		System.out.println(b.get(tst));
		
	}
}

測試結果:
在這裡插入圖片描述

Constructor:構造方法

創建對象:T newInstance(Object… initargs)
注意:如果使用空參數構造方法創建對象,操作可以簡化:Class對象的newInstance方法
作用就是用它來創建對象
舉例:

package Test;

public class Test {
	public String a;
	//構造方法
	public Test( ) {}
	public Test(String a) {
		this.a = a;
	}
	 
}

package Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class Reflect {
	public static void main(String[] args) throws Exception {
		Class Tst = Test.class;
		Constructor[] constructors = Tst.getConstructors();
		for (Constructor constructor : constructors) { // Constructor 對象reflect包下的 import java.lang.reflect.Constructor;
			System.out.println(constructor);
		}
		System.out.println("------------------無參構造函數創建對象----------------------");
		Constructor cst=Tst.getConstructor(); //獲得無參構造函數
		System.out.println(cst);
		Object test=cst.newInstance(); //利用無參構造函數創建對象
		System.out.println(test);
		
		System.out.println("------------------有參構造函數創建對象----------------------");
		Constructor cst2=Tst.getConstructor(String.class); //獲得有參構造函數
		System.out.println(cst2);
		Object test2=cst2.newInstance("張3");//利用有參構造函數創建對象
		System.out.println(test2);
		
		System.out.println("------------------基於Class創建對象----------------------");

		Object test3=Tst.newInstance();
		//只能用於無參構函數,而且已經被棄用,不建議使用
		System.out.println(test3);

	}
}

在這裡插入圖片描述
喜歡問問題的小朋友要來了?
為什麼沒有getDeclaredConstructor方法和getDeclaredConstructors方法?
為什麼?為什麼?
有啊!!
getDeclaredConstructor方法可以獲取到任何訪問許可權的構造器,而getConstructor方法只能獲取public修飾的構造器。具體不再測試。此外在構造器的對象內也有setAccessible(true);方法,並設置成true就可以操作了。
關於為什麼要使用private訪問許可權的構造器,使用這個構造器不就不能外部訪問了嘛,不也就無法進行實例化對象了嗎?無法在類的外部實例化對象正是私有構造器的意義所在,在單例模式下經常使用,整個項目只有一個對象,外部無法實例化對象,可以在類內的進行實例化並通過靜態方法返回,由於實例化的對象是靜態的,故只有一個對象,也就是單例的,這就是單例模式中的餓漢模式,不管是否調用,都創建一個對象。

Method:方法對象

執行方法:Object invoke(Object obj, Object… args)
獲取方法名稱:String getName();

舉例:

package Test;

public class Test {
	public String a;

	// 構造方法
	public Test() {
	}

	public Test(String a) {
		this.a = a;
	}

	public void do_Something()	{
		System.out.println("吃飯睡覺打豆豆");
	}
	public String do_Something(String s)	{
		System.out.println("吃飯睡覺打"+s);
		return "爽";
	}
}


package Test;
 
import java.lang.reflect.Method;

public class Reflect {
	public static void main(String[] args) throws Exception {
		Class Tst = Test.class;
		Method[] mtd=Tst.getMethods();
		for(Method m:mtd) System.out.println(m);
	}
}


運行結果:
在這裡插入圖片描述
舉例2.0:

package Test;

public class Test {
	public String a;

	// 構造方法
	public Test() {
	}

	public Test(String a) {
		this.a = a;
	}

	public void do_Something()	{
		System.out.println("吃飯睡覺打豆豆");
	}
	public String do_Something(String s)	{
		System.out.println("吃飯睡覺打"+s);
		return "爽";
	}
}

package Test;

import java.lang.reflect.Method;

public class Reflect {
	public static void main(String[] args) throws Exception {
		Class Tst = Test.class;
		Test test = new Test();

		System.out.println("-----------------無參測試----------------------");
		Method mtd1 = Tst.getMethod("do_Something");// 獲得無參的方法
		Object return_Value = mtd1.invoke(test); // 調用方法
		// 有返回值就得到一個值,沒有就得到一個null
		System.out.println(return_Value);

		System.out.println("-----------------含參測試----------------------");
		Method mtd2 = Tst.getMethod("do_Something", String.class);// 獲得無參的方法
		Object return_Value2 = mtd2.invoke(test, "張三"); // 調用方法
		// 有返回值就得到一個值,沒有就得到一個null
		System.out.println(return_Value2);
	}
}

運行結果:
在這裡插入圖片描述

總結

這時候又會有小朋友問:
為什麼要這麼麻煩,我直接調用不就好了?

不知你是否發現,從類的創建的方法的使用,所有的一切都是用的字元串,那麼也就是說,我可以通過讀入數據,或者配置文件的方式,創建類,調用方法。

舉個簡單點的例子:
就拿英雄聯盟這款遊戲來說,這遊戲三天兩頭的輪換一個娛樂模式,難道每次上線都要對源程式碼進行修改,今天在Client調用「無限活力」,明天就要調用”魄羅大亂斗」,每天就對著源碼改?幾萬行的程式碼就這麼放心讓你改?除非你老闆想做空公司,故意的!必然不可能,這時候我們就算哪一個txt文件,就放一行字元串,用反射之後,只用改txt文件不就完了!不用反射,是做不到用字元串創建類,和運行方法(別抬杠,寫個if-else 或者 switch啥的)。

舉例可能不太恰當,一般不會使用txt,一般使用XML或者java配置文件。
在這裡插入圖片描述

寫在最後:
我叫風骨散人,名字的意思是我多想可以不低頭的自由生活,可現實卻不是這樣。家境貧寒,總得向這個世界低頭,所以我一直在奮鬥,想改變我的命運給親人好的生活,希望同樣被生活綁架的你可以通過自己的努力改變現狀,深知成年人的世界裡沒有容易二字。目前是一名在校大學生,預計考研,熱愛編程,熱愛技術,喜歡分享,知識無界,希望我的分享可以幫到你!
如果有什麼想看的,可以私信我,如果在能力範圍內,我會發布相應的博文!
感謝大家的閱讀!😘你的點贊、收藏、關注是對我最大的鼓勵!