Java進階03-反射,泛型

Java進階知識點-反射和泛型

老規矩先放一張思維導圖鎮樓
在這裏插入圖片描述
Java中的進階知識點有很多,這一篇主要學習反射和泛型。可以說這2個知識點我們自己平時的開始用的可能不多,但是系統源碼和網上的第三方開源庫中用到的是非常多。如果rxjava ,熱修復,dagger2 ,等等都需要用到,還包括Hook點啊 動態代理 AOP APT 啊等等。所以掌握好反射 是學習這些框架的基礎。

反射

  • 反射是什麼
  • 反射怎麼使用
  • 反射的優缺點
  • 反射的原理是什麼
  • 反射使用時機

反射是什麼?
反射(reflex)是指機體對內在或外在刺激有規律的反應。光沿着原路返回就叫反射,這個是物理的反射。java中的反射是指:程序在運行時能夠獲取自身的信息。

簡而言之,你可以在運行狀態中通過反射機制做到:
對於任意一個類,都能夠知道這個類的所有屬性和方法;
對於任意一個對象,都能夠調用它的任意一個方法和屬性;

這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
應該已經比較清楚了。就是非常規手段調用類中方法或者對象和字段。

反射怎麼使用
我們先看正常的調用方法流程。如狗叫
1、定義一個接口有叫方法

public interface IAnimalCall {
	
	void call();

}

2、寫一個類Gog實現接口,實現方法叫

public class Dog implements IAnimalCall {

	private String mName;

	public Dog() {
	}

	public Dog(String name) {
		this.mName = name;
	}

	@Override
	public void call() {
		System.out.println("狗:" + mName + "---汪汪汪");
	}

	@Override
	public String toString() {
		return "Dog [mName=" + mName + "]";
	}

}

3、new出對象,調用方法。

public static void main(String[] args) {
		IAnimalCall dog = new Dog("旺財");
		dog.call();

	}

4、運行結果
在這裏插入圖片描述
恩,結果不出我們所料(廢話這麼簡單,大家都會)

下面想一想如果我把構造函數改爲private私有的,這樣才能調用到call方法呢?

public class Dog implements IAnimalCall {
	private String mName;
	private Dog() {
	}
	private Dog(String name) {
		this.mName = name;
	}
	@Override
	public void call() {
		System.out.println("狗:" + mName + "---汪汪汪");
	}
	@Override
	public String toString() {
		return "Dog [mName=" + mName + "]";
	}
}

私有的構造函數是不能直接用New創建對象的。
在這裏插入圖片描述
編譯報錯了,提示說構造方法是不可見的。這麼辦? 先不要槓 這樣設計,就問你一句這麼辦,還怎麼調用Call方法。5分鐘考慮。。。

時間到了,想到辦法沒。哈哈 只要反射就可以了。
對於任意一個類,都能夠知道這個類的所有屬性和方法;
反射是Java寫好提供給我使用的。其實每個類class都有一個Class類去描述它。它會記錄這個類有什麼東西比如構造函數,字段,方法等等是對類的解釋和說明文檔,它由Java提供的在類被加載的時候就會存儲在方法區中。
平是我們接觸多的就是:
public static final String TAG=Main.class.getSimpleName();
這個Main.class 就是拿到Class注意是大寫,相當於:
Class main=Main.class;
String Tag=main.getSimpleName();

根據官方文檔我們獲取Class有3種方式:

  1. 通過 Object.getClass()
  2. 通過 .class 標識
  3. 通過 Class.forName() 方法
    大家可以測試一下。
public static void main(String[] args) {
		Dog dog=new Dog("旺財");
		Class<? extends Dog> dog1=dog.getClass();
		System.out.println("Object.getClass name="+dog1.getSimpleName());
		
		Class<Dog> dog2=Dog.class;
		System.out.println("Dog.class name="+dog2.getSimpleName());
		
		try {
			Class<?> dog3=Class.forName("com.zx.parse.annotation.Dog");
			System.out.println("Class.forName name="+dog3.getSimpleName());
		} catch (ClassNotFoundException e) {
			System.out.println("Dog.class name="+e.toString());
		}
	}

運行結果:
在這裏插入圖片描述
這裏第三種方法就厲害了 你不需要有對象,甚至類都不需要知道,只要給全限定名稱就好了 其實就是包名加類名。因爲它要根據這個路徑去找你的類(文件類 真實的文件)。
現在我們根據第三種方式獲得了Dog類的 Class。然後就可以根據這個Class去獲取構造函數了:有條件的可以點進Class類中看看到底有什麼,它裏面有很多的getxxx()方法
如果我們的Dog類構造函數不是private就用下面代碼就可以了

Class<?> dogClass=Class.forName("com.zx.parse.annotation.Dog");
			Dog dog=(Dog) dogClass.newInstance();
			dog.call();

但是由於我們的構造函數是private所以這招行不通。我們再去看看官方文檔怎麼獲取構造函數:
首先用Class點一下發現有個getConstructor方法,看名字就知道應該是獲取構造函數的。好的 我們繼續。

Constructor<?> constructor=dogClass.getConstructor(dogClass);

Constructor這個其實也是一個類和Class類似。Class是用來描述類的,那Constructor就是來描述構造函數。比如構造函數的修飾符,參數啊什麼的。
測試用getConstructor獲取不到構造函數,要用getDeclaredConstructors

Class<?> dogClass = Class.forName("com.zx.parse.annotation.Dog");
Constructor<?>[] constructor = dogClass.getDeclaredConstructors();
println(constructor[1].getParameterCount());
Dog dog=(Dog) constructor[1].newInstance("旺財");
dog.call();

直接調用會報錯,檢測access說你這個是私有的,要你確保權限
在這裏插入圖片描述
怎麼確保,我們點擊看這個錯誤是怎麼產生的。
完,點不進去 eclipse就是這點不好,不要問我爲什麼還在用eclipse。這難不倒我們。我們可以去網上看在線源碼,還是什麼都看不見,
在這裏插入圖片描述
什麼只要把override 設置爲true就不會進來了。剛好就有一個方法
setAccessible
加上之後
在這裏插入圖片描述
其實只要你的是私有的就得加上這個,否則就會報使用異常,激動人心的時刻來了運行一下看看:

在這裏插入圖片描述
看到運行成功了。可以看到在構造函數是私有的情況下 我們也成功的獲取了對象調用了方法。
接下來 我們把call方法也至爲私有的。看怎麼通過反射調用到。
猜測原理應該類似,經過測試:
getDeclaredMethods 是獲取所有方法
getDeclaredMethod是獲取指定的方法

Class<?> dogClass = Class.forName("com.zx.parse.annotation.Dog");
Constructor<?>[] constructor = dogClass.getDeclaredConstructors();
println(constructor[1].getParameterCount());
constructor[1].setAccessible(true);
Dog dog = (Dog) constructor[1].newInstance("旺財-反射");
Method methodEat = dogClass.getDeclaredMethod("eat", String.class);
println(methodEat.getName());
methodEat.setAccessible(true);
methodEat.invoke(dog, "飯");

運行結果
在這裏插入圖片描述
哈哈 又調到了。
這裏之所以花這麼多功夫 自己一個一個的摸索而不是 直接去網上看別人的。就是想檢驗一下自己的動手能力 有沒有掉入
眼睛:我會了。大腦:我會了。 手:不,你不會。
果然知識點還是要自己動手實踐實踐的好,以前我看過幾遍關於反射,但是這次動手來 花了幾個小時,不過好在還弄出來了

接下來基本就是大同小異,就是熟練度了。修改變量等等,其實反射主要是爲了獲取註解,和調用方法。

反射的優缺點: 任何事物,都有兩面性,反射的優點,也同是就是它的缺點。
優點:
(1)能夠運行時動態獲取類的實例,大大提高系統的靈活性和擴展性。
(2)與Java動態編譯相結合,可以實現無比強大的功能
缺點:
(1)使用反射的性能較低
(2)使用反射相對來說不安全
(3)破壞了類的封裝性,可以通過反射獲取這個類的私有方法和屬性
仔細想想也是,你要只要一個路徑String就可以獲取到對象 還可以調用任何方法太變態了。
其實反射是Java提供好給我們用,你發生全程我們就是調用系統準備好的反射相關的API。所以會用反射沒什麼值得高興 滿足的,是個人對着反射說明書(官方文檔看兩遍就會了)。既然如此 那我們怎麼和別人區別開,那就要學習下一個問題了。
反射的實現原理是什麼?
反射就是把java類中的各種成分映射成一個個的Java對象
例如:一個類有:成員變量、方法、構造方法、包等等信息,利用反射技術可以對一個類進行解剖,把個個組成部分映射成一個個對象。
那這些描述的對象類 如Class Method Constructor 類在哪?
在這裏插入圖片描述
它們也是普通的類,只不過是JDK寫好的,在java.lang.reflect包下面。
它們什麼時候加載的?
類加載器負責根據一個類的全限定名來讀取此類的二進制字節流到JVM內部,並存儲在運行時內存區的方法區,然後將其轉換爲一個與目標類型對應的java.lang.Class對象實例

看看反射中最關鍵的一句:
Class.forName(“com.zx.parse.annotation.Dog”);
這個是怎麼拿到Class對象的。
在這裏插入圖片描述

這裏我們先插入一個知識點就是 類加載器 ClassLoader:這裏先簡單說明,後續學習虛擬機的時候會詳細講解。
看了這麼多 原理都是和類的加載有關,看來虛擬機的學習要提前。
反射使用時機: 或者說反射的作用,對於我來說完全就是爲了理解別人寫的第三方框架。
1.能用正常方法就不要用反射
2.看別人的開源框架
3.需要動態代理的時候

泛型

  • 泛型是什麼
  • 泛型怎麼使用
  • 泛型的優缺點
  • 泛型的原理是什麼
  • 泛型使用時機

泛型是什麼
泛型這個東西大家可能用的不多,但是寫SDK的人 應該就會經常使用,我們接觸最多就是集合框架中 如ArrayList
在這裏插入圖片描述
我們是用的時候就必須先告訴它類型 否則會爆紅
ArrayList list = new ArrayList();
如上,我告訴了ArrayList 使用String字符串類型,之後只能list添加String。
仔細想想,這個有什麼用?
1.安全
2.減少重複代碼。
3.通用性強
否則ArrayList 要針對每種數據類型 建立一個數組,如果只是8大基本數據還好,那些自定義數據 還沒法玩了。
泛型官方定義:參數化類型。可以理解爲把 類型當做參數 傳給類,這一點明顯符合依賴倒置原則 和依賴注入。要什麼類型 客戶端自己決定。
但是也有不好的,因爲類型擦除的原因 類的行爲就被抹除了,就是說泛型T不能使用原類裏面自己的方法,只能用object中通用的方法。如:
在這裏插入圖片描述
上面我定義了一個泛型方法println打印,可以看到你傳什麼進來,這個msg只能用object中的方法。所以說泛型其實沒有想象中的那麼厲害,最大的作用就是存儲了。因爲 它調不了 你自己寫的方法。

泛型怎麼使用
泛型有3種:泛型類,泛型方法,泛型接口
泛型類:

public class Animal<T> {
	private T mT;

	public void setT(T t) {
		this.mT = t;
	}

	public T getT() {
		return mT;
	}

	public void call() {
		// mT.call();
	}

}

<>尖括號是關鍵。T 隨意 你可以用任意大寫字母,但是爲了規範還是用T吧
T 代表一般的任何類。
E 代表 Element 的意思,或者 Exception 異常的意思。
K 代表 Key 的意思。
V 代表 Value 的意思,通常與 K 一起配合使用。
使用:

public static void main(String[] args) {
		Animal<Cat> animal=new Animal<>();
		Cat cat=new Cat();
		animal.setT(cat);
		animal.getT().call();
	}

比較簡單,好像什麼用都沒有,只能存儲一下,因爲
在這裏插入圖片描述
必須強制轉化爲Cat才能調用call。這是爲什麼呢,我們傳進來的明明就是Cat對象,這個我們反編譯一下Animal看看 傳進來的mT到底是什麼
使用javap進行反編譯:javap -c -l Animal.class
在這裏插入圖片描述
在這裏插入圖片描述
可以看到mT 類型是Object:所以不能當做Cat類,必須要先轉化。泛型只有在編譯器纔有用,編譯後類型都被擦出了。
那我們可不可以 不轉化直接使用。這個可以是可以 後面講解

泛型方法
之前面的那個println()方法就是泛型方法。

public static  <T> void println(T msg) {
		System.out.println("Main:" + msg.toString());
	}

使用

public static void main(String[] args) {
		println("泛型");
		println(true);
		println('H');
		println(122);
		println(12.12);
	}

結果:
在這裏插入圖片描述
這就是泛型的好處,不然就要重載println方法參數爲String,int boolean 等。
泛型接口
泛型接口與泛型類基本一樣。

public interface IAnimal<T> {

	public void setT(T t);

	public T getT();

	public void call();

}

下面我們想想有什麼辦法 可以不轉化直接調用。這裏就需要用到接口或者繼承了。我們先定義一個接口。

public interface ICall {
	void call();
}

然後讓貓Cat實現ICall

public class Cat implements ICall {
	private String name;

	public Cat(String name) {
		this.name = name;
	}

	public Cat() {
	}

	@Override
	public void call() {
		System.out.println(name + ":喵喵喵");
	}

}

然後改造一下泛型類Animal,讓T繼承ICall

public class Animal<T extends ICall> {
	private T mT;

	public void setT(T t) {
		this.mT = t;
	}

	public T getT() {
		return mT;
	}

	public void call() {
		mT.call();
	}

}

然後運行:

public static void main(String[] args) {
		Animal<Cat> mAnimal = new Animal<Cat>();
		mAnimal.setT(new Cat("白嫖"));
		mAnimal.call();
	}

結果:
在這裏插入圖片描述
哈哈,我們沒有經過轉化也調用了call方法。我們再去看看此時mT編譯後是什麼類型-用javap反編譯:
在這裏插入圖片描述
看到mT不再是Object類型而是變成了ICall類型,所以可以直接調用接口中的call()方法了。是不是瞬間感覺 好厲害了。其實也沒有多大用。
這就叫做 泛型的通配符 嗎?
通配符通常有2種 <? extends T> 和<? super T>
我們 一個一個講解,先看<? extends T>

public <T extends ICall> void call(T t) {
		t.call();
		System.out.println(t.getClass().getName());
	}

其實這個還不算通配符,也和簡單就是 你傳進來的類型必須是 繼承了ICall的子類,本來如果不加extends ICall 你可以傳任意類型,但是加了之後 就只能傳ICall子類。這個好像沒什麼用,失去了泛型的意義了。
我還不如直接寫成:

public  void call(ICall t) {
		t.call();
		System.out.println(t.getClass().getName());
	}

其實通配符這個東西是 給集合類使用的。舉個栗子:
其實還是網上的那個水果例子。那個例子就已經說的很好了。
首先我們有盤子放什麼東西不知道,所以是泛型:

public class Plate<T> {

	private T mT;

	public Plate(T t) {
		this.mT = t;
	}

	public Plate() {
	}

	public void setT(T t) {
		this.mT = t;
	}

	public T getT() {
		return mT;
	}

}

很簡單就是能夠set 和get 。接着定義2個類 水果和蘋果,繼承關係

public class Fruit {
	public void describe() {
		System.out.println("我是水果");
	}
}
public class Apple extends Fruit {
	public void describe() {
		System.out.println("我是蘋果");
	}
}

接着測試:

	public static void main(String[] args) {
		Plate<Fruit> plateFruit = new Plate<>(new Fruit());
		plateFruit.getT().describe();
/////////////////////////////////////////////////
		Plate<Apple> plateApple = new Plate<>(new Apple());
		plateApple.getT().describe();
	}

運行結果:
在這裏插入圖片描述
一切都沒什麼問題。但是如果我在放水果的盤子 放蘋果 可以嗎,試試看
Plate plateFruit = new Plate<>(new Apple());
這一句編譯不過,提示不能轉化。 那怎麼才能轉化呢?答案就是通配符
我們改造一下改爲:
Plate<? extends Fruit> plateFruit = new Plate<>(new Apple());
哈哈 可以編譯過去 也可以運行出結果:我是蘋果。
現在不管是蘋果還是橘子只要是 繼承於Fruit類都可以了。
別高興太早了,當加了通配符之後你會發現它不能調用set方法了
在這裏插入圖片描述
怎麼辦? 沒辦法 就是不能set了 原因就是 編譯器不知道 你要放什麼進來。我們反編譯看看Plate:看不出什麼

泛型的優缺點
優點:1.適配,簡潔,消除重複代碼
2.複用
3.類型安全
缺點:其實它的優點沒那麼強大,有點雞肋其實,缺點的話 由於優點不明顯,相應的缺點也不大,硬要說的話就是 不好看 不好理解。還有就是 類型擦除導致 原來定義的方法都不能用了。
泛型的原理
原理就是類型擦除了。而且泛型它只是在編譯器有用,在JVM運行時 類型都變成了Object。java 有編譯時 和運行時 Runtime 運行時。先放着,複習虛擬機時再來看看這個類是幹什麼的。
泛型使用時機
綜合來看,和容器相關的時候可以考慮使用,如果Adapter 集合框架 等等,這些不需要 類的具體方法 就可以使用了。其次就是看別人的框架 因爲框架就是一種廣泛適配的東西 就很適合使用泛型。

總結:泛型並不難,唯一難理解的地方就那個 通配符了。我們平時在框架中接觸的很多,多思考一下就好了。記住一點 泛型=參數化類型+類型擦除 類型擦除怎麼實現 那是編譯器實現的,定義好的語法規則。

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