淺析java反射(位元組碼檔案)
什麼是反射?
先談談java程式的執行步驟吧! 先編譯後執行對嗎?
其實你想一想, 你寫的java程式碼機器真的能認識嗎? 早在以前就聽過了吧機器是隻認識0和1的
所以編譯這一階段也就是將java檔案編譯成位元組碼檔案也就是.class檔案 也就是01碼
那什麼又是反射呢?
我現在覺得反射就是拿到它的位元組碼檔案,對位元組碼檔案做操作
位元組碼檔案
我先舉個例子什麼叫做位元組碼檔案
怎麼樣 刺不刺激 你也可以對著寫下來放在text檔案中 字尾改成.class 檔案 然後看看他執行的結果是什麼
應該是hello world
一大串我們看不懂的01編碼 但是java的開發者們應該是通過某一種編碼方式開發了java語言
其實你可以想一想 真正機器能識別的就是這個位元組碼檔案 如果你知道了這種編碼方式 你是不是其實也可以改這個執行結果呢?
不通過java程式碼改 直接改class檔案 比如把輸出的hello wolrd 改成 hello java 可能比較複雜
因為我自學的時間也只有2個月左右 所以幫不到大家 還是迴歸正題
位元組碼檔案的大致介紹
這一套編碼方式 你可以去找找看 應該可以找到
這裡面都會載入一些什麼 比如 這麼多位元組碼檔案會載入你的class_info 也就是類的資訊
你看一下第一個cafe babe 你可以查一下 他代表著你這個類是 class型別
然後後面的01碼基本上就是 常量池的索引 或者欄位資訊 方法資訊 父類的資訊 初始化也就是構造器的資訊
當然 我們自然是看不懂 機器能看懂 可能開發者們也能看懂
反射
回到了正題
我們java 的類載入器 在第一次訪問一個類的時候 肯定要把他的類資訊載入到jvm裡面
只要一載入進來我是不是就可以拿到位元組碼檔案了 也就是那個01碼檔案
我只要拿到了01碼檔案 如果我會編碼的方式 我是不是可以直接改編碼
所以反射就是幹這樣的一個事情 我直接拿你01碼 直接改的01碼
得到Class檔案的三種方式
把這位元組碼檔案封裝成一個類 我們通過三種方式可以拿到它
1.類名.class
說明: JVM將使用類裝載器, 將類裝入記憶體(前提是:類還沒有裝入記憶體),不做類的初始化工作.返回Class 的物件
2.Class.forName("類名字串")
(注:類名字串是包名+類名) 說明:裝入類,並做類的靜態初始化,返回Class的物件
3.例項物件.getClass()
說明:對類進行靜態初始化、非靜態初始化;
返回引用o執行時真正所指的物件(因為:子物件的引用可能會賦給父物件的引用變數中)
所屬的類的Class的物件
上述的方法還是推薦使用Class.forName的方式
反射的Method
不管是 getDeclaredMethod();
還是 getMethod();
這兩個方法要的引數都是名字 加上引數的型別 注意 這裡的 引數型別也得是位元組碼型別 也就是.class形式
因為有過載 所以你只有方法的名字是不夠的 所以還需要你的引數型別
在你這個類的位元組碼檔案中 總是有一處標識著Method欄位 然後可能就是他的許可權修飾符 返回值 方法名 及其引數等等
所以你傳入名字 會通過位元組碼檔案去找這個方法 然後 根據你傳過來的型別的位元組碼匹配所對應的過載方法
如果你使用的是getDeclaredMethod() 這個方法
其實我們也都知道這個方法 連私有的方法都可以拿到
因為我拿你位元組碼 我又不管你是誰 但是你是私有的 其實還是不想讓外界訪問到
所以出現了setAccessible(true);這樣的一個方法出現
反射的Field
其實類的Field 也是一樣的 總有位元組碼去標記欄位的開始 許可權 型別 暱稱等等 這些都寫在了位元組碼檔案中
其實到這也能很好的解釋了 你java程式碼變了 為什麼要重新編譯 因為其實起決定性作用的並不是你的java程式碼
而是編譯出來的位元組碼檔案
所以這裡不再多說廢話 一會總結一下 反射的方法 並且寫個反射的妙一點的例子
反射的構造器Constructor
因為前面的位元組碼檔案已經說了很多 這裡就是通過 Class 拿到類構造器
然後可以去new物件
反射方法總結
1.類名.class
說明: JVM將使用類裝載器, 將類裝入記憶體(前提是:類還沒有裝入記憶體),不做類的初始化工作.返回Class的物件
2.Class.forName("類名字串")
(注:類名字串是包名+類名) 說明:裝入類,並做類的靜態初始化,返回Class的物件
3.例項物件.getClass()
說明:對類進行靜態初始化、非靜態初始化;
返回引用o執行時真正所指的物件(因為:子物件的引用可能會賦給父物件的引用變數中)
所屬的類的Class的物件
這裡我們假設有個Student類 至於細節什麼的 應該都懂
//一、通過getclass 返回Class物件
s.getClass(); //返回Class物件
c.getSuperclass(); //返回Class物件
//二、類名.calss
Student.class; //返回Class物件
//三、通過forname
Class.forName("com.sxt.User"); //返回Class物件
//四、包裝類的反射
Integer.TYPE; //返回Class物件
//獲取全路徑名字
System.out.println(c2.getName());
//獲取類名
System.out.println(c2.getSimpleName());
//獲取許可權,1、public 2、protected 3、default 4、private
System.out.println(c2.getModifiers());
//判斷是否是介面
System.out.println(c2.isInterface());
//返回public屬性
Field[] fields = c2.getFields();
//返回全部屬性
Field[] declaredFields = c2.getDeclaredFields();
//返回一個public屬性物件
Field field = c2.getField("name");
//返回一個屬性物件
Field declaredField = c2.getDeclaredField("name");
//public方法陣列
Method[] methods = c2.getMethods();
//所有方法陣列
Method[] declaredMethods = c2.getDeclaredMethods();
//返回一個許可權是public方法名是run,引數型別為空的方法物件
Method m =c2.getMethod("run",null);
//返回一個方法名是run,引數型別是String和int 的method 型別
Method m1=c2.getDeclaredMethod("run", String.class,int.class);
//返回public建構函式陣列
Constructor[] constructors = c2.getConstructors();
//返回所有建構函式陣列
c2.getDeclaredConstructors();
//返回一個建構函式是引數是public的String,int型別的陣列
c2.getDeclaredConstructor(String.class,int.class);
//返回一個建構函式列表為空的陣列
c2.getConstructor(null);
//建立物件
Student stu1 = (Student)c2.newInstance();
//建構函式建立物件
Student stu2 = (Student)ct1.newInstance();
//建構函式建立有參物件
(Student)ct2.newInstance(String.class,Integer.class); //
反射的一個小例子
這裡我需要提一下工廠模式 因為我用到了工廠模式
但是也不一定工廠模式就要用反射 我沒這樣說哈 我就是在這個具體例子中可能會用到
工廠模式
工廠模式(Factory Pattern) 是 Java 中最常用的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式.
在工廠模式中,我們在建立物件時不會對客戶端暴露建立邏輯,並且是通過使用一個共同的介面來指向新建立的物件。
就比如 你需要一輛汽車, 你可以直接從工廠裡面提貨. 你不用去管這輛汽車是怎麼做出來的.和這個汽車的具體實現 你只需要名字就可以
就比如我們現在有這樣的一個定義
interface Car {
public void payFor();//支付價錢的功能
}
然後我們有三種車SuperCar Bus和EasyCar 他們共同實現了這個藉口
class SuperCar implements Car {
@Override
public void payFor() {
System.out.println("蘭博基尼 : 您需要支付500萬元");
}
}
class Bus implements Car {
@Override
public void payFor() {
System.out.println("公交車 : 您需要支付100萬元");
}
}
class EasyCar implements Car {
@Override
public void payFor() {
System.out.println("小轎車 : 您需要支付20萬元");
}
}
想一想 我們去買車 是不是告訴他名字 那我們可以有個工廠
class CarFactory {
private CarFactory() {}
public static Car getCar(String name) {
if (name.equalsIgnoreCase("SuperCar")) { //如果是SuperCar
return new SuperCar();
} else if(name.equalsIgnoreCase("Bus")) { // 如果是Bus
return new Bus();
} else if(name.equalsIgnoreCase("EasyCar")) { // 如果是EasyCar
return new EasyCar();
} else {
return null;
}
}
}
這樣你把名字拿來我就能給你造出來 對不對 但是 這樣你不覺得有點麻煩嘛 我後來有n多個汽車 而且事實也是這樣的
我們現在蘭博基尼 瑪莎拉蒂 法拉利等等 對吧 太多了
你這個工廠還要一個一個新增, 這是不是有點麻煩
所以你只要把你的位元組碼檔案給我 我直接用位元組碼檔案給你new就行了
class CarFactory {
private CarFactory() {}
public static Car get(Class t) throws Exception {
if(t == null)
return null;
return (Car)t.getDeclaredConstructor().newInstance();
}
}
這樣不管你多少種車 我就這一句話 是不是減少了程式碼的沉餘呢?
好了 完整程式碼 帶測試
package NewDay11;
interface Car {
public void payFor();
}
class SuperCar implements Car {
@Override
public void payFor() {
System.out.println("蘭博基尼 : 您需要支付500萬元");
}
}
class Bus implements Car {
@Override
public void payFor() {
System.out.println("公交車 : 您需要支付100萬元");
}
}
class EasyCar implements Car {
@Override
public void payFor() {
System.out.println("小轎車 : 您需要支付20萬元");
}
}
class CarFactory {
private CarFactory() {}
public static Car get(Class t) throws Exception {
if(t == null)
return null;
return (Car)t.getDeclaredConstructor().newInstance();
}
}
public class FactoryText {
public static void main(String[] args) throws Exception{
Car c = CarFactory.get(Class.forName("NewDay11.SuperCar"));
c.payFor();
}
}