經常聽到java 反射,自己也看過一些文章和視頻,但總是感覺沒有很好地理解,所以結合了自己覺得比較好的文章(主要是我能理解的),自己又總結了一下,作爲記錄。
java反射
前言
我們都知道,JVM加載的是.class文件。其實,類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區內的數據結構的接口。
同時,JVM規範允許類加載器在預料某個類將要被使用時就預先加載它,如果在預先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器會在程序首次主動使用該類時會生成錯誤報告(LinkageError錯誤),如果這個類一直沒有被程序主動使用,那麼類加載器就不會報告錯誤。
舉例Object o=new Object();
例如要運行一段代碼 Object o=new Object();
其運行過程:
首先JVM會啓動,你的代碼會編譯成一個.class文件,然後被類加載器加載進jvm的內存中,你的類Object加載到方法區中,創建了Object類的class對象到堆中,注意這個不是new出來的對象,而是類的類型對象,每個類只有一個class對象,作爲方法區類的數據結構的接口。jvm創建對象前,會先檢查類是否加載,尋找類對應的class對象,若加載好,則爲你的對象分配內存,初始化也就是代碼:new Object()。
上面的流程就是你自己寫好的代碼扔給jvm去跑,跑完就over了,jvm關閉,你的程序也停止了。
理解了上面的過程,再理解反射就比較容易了。
一、反射的概述
Java的反射(reflection)機制是指在程序的運行狀態中,可以構造任意一個類的對象,可以瞭解任意一個對象所屬的類,可以瞭解任意一個類的成員變量和方法,可以調用任意一個對象的屬性和方法。這種動態獲取程序信息以及動態調用對象的功能稱爲Java語言的反射機制。反射被視爲動態語言的關鍵。
反射提供的主要功能:
- 在運行時判斷任意一個對象所屬的類;
- 在運行時構造任意一個類的對象;
- 在運行時判斷任意一個類所具有的成員變量和方法(通過反射甚至可以調用private方法);
- 在運行時調用任意一個對象的方法;
- 生成動態代理.(下篇再講)
重點:是運行時而不是編譯時
二、獲取class文件對象的方式
反射是對一個類進行解剖,要想解剖一個類,必須先要獲取到該類的字節碼文件對象。
而解剖使用的就是Class類中的方法,所以先要獲取到每一個字節碼文件對應的Class類型的對象。
再強調一下, 咱們寫的代碼是存儲在後綴名.java文件中的,它會被編譯成.class文件,jvm真正執行的是.class文件。Java是面向對象的語言,一切皆爲對象,所以java認爲編譯後的.class文件也是對象,給抽象成了一個類,這個類就是Class。
通過文檔瞭解到,使用forName(String className)方法就可以得到想要反射的類。
獲取class文件對象的有三種方式,具體如下:
(1)使用Class類的forName()靜態方法
Class perClass = Class.forName("com.diligentkong.bean.Person");
System.out.println(perClass);
輸出:class com.diligentkong.bean.Person
(2)直接獲取某一個對象的class
Person person = new Person(); // new一個Person對象
Class perClass = person.getClass();// 獲取Class對象
System.out.println(perClass);
//結果輸出:class com.diligentkong.bean.Person
(3)調用某個對象的getClass()方法
Class perClass = Person.class;
System.out.println(perClass);
輸出:class com.diligentkong.bean.Person
三、反射的基本運用
1.獲取構造器信息
通過Class類的getConstructor方法得到Constructor類的一個實例,而Constructor類有一個newInstance方法可以創建一個對象實例:
Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//獲取有參構造函數
可以根據傳入的參數來調用對應的構造函數。
2.通過反射來生成對象
1 .使用Class對象的newInstance()方法來創建Class對象對應類的實例。
Class perClass = Class.forName("com.diligentkong.bean.Person");
//調用 Person 的無參數構造方法,創建此Class對象所表示的類的一個新實例
Person person = (Person) perClass.newInstance();
System.out.println(person);
輸出:Person{name='null', age=0}
2.先通過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建實例。這種方法可以用指定的構造器構造類的實例。
Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//獲取有參構造函數
Person p = (Person) constructor.newInstance("kong",22);
System.out.println(p); //Person{name='kong', age=22}
3.獲取方法
獲取某個Class對象的方法集合,主要有以下幾個方法:
1.獲取所有的:
-
public Method[] getMethods():獲取所有"公有(public)方法";(包含了父類的方法也包含Object類)
-
public Method[] getDeclaredMethods():方法返回類或接口聲明的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。
2.獲取單個的: 方法返回一個特定的方法,其中第一個參數爲方法名稱,後面的參數爲方法的參數對應Class的對象。
-
public Method getMethod(String name,Class<?>... parameterTypes):
- public Method getDeclaredMethod(String name,Class<?>… parameterTypes)
參數:
name : 方法名;
Class … : 形參的Class類型對象
調用方法:
public Object invoke(Object obj,Object… args):
參數說明:
obj : 要調用方法的對象;
args:調用方式時所傳遞的實參;
實例代碼:
public class Person {
private String name;
private int age;
public void run(){
System.out.println("I can run ....");
}
protected void eat(int num){
System.out.println("我今天吃了"+num+"頓大餐,哈哈哈!");
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) throws Exception {
//獲取Class對象
Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//獲取有參構造函數
Person p = (Person) constructor.newInstance("kong",22);
System.out.println(p); //Person{name='kong', age=22}
// 獲取所有公有方法
System.out.println("------------------獲取所有“公有”方法-------------");
Method[] methodArray = perClass.getMethods();
for (Method m: methodArray){
System.out.println(m);
}
System.out.println("----------------獲取所有的方法,包括私有的---------");
Method[] declaredMethods = perClass.getDeclaredMethods();
for (Method m : declaredMethods){
m.setAccessible(true); //解除私有限定
System.out.println(m);
}
System.out.println("----------------獲取公有的run()方法-------------------");
Method m = perClass.getMethod("run");
m.invoke(p);
System.out.println(m);
System.out.println("-------------------獲取私有的eat()方法----------------------------");
Method promethod = perClass.getDeclaredMethod("eat",int.class);
promethod.setAccessible(true);
promethod.invoke(p,3);
}
結果:
4.獲取類的成員變量信息
getFiled:訪問公有的成員變量
getDeclaredField:所有已聲明的成員變量,但不能得到其父類的成員變量
示例代碼:
在Person中加入如下字段:
public int i;
public int j;
Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//獲取有參構造函數
Field field = perClass.getDeclaredField("age");
System.out.println("-------------訪問所有的屬性,包括public屬性-------");
Field[] fields = perClass.getDeclaredFields();
for (Field f: fields){
System.out.println(f.getName());
}
System.out.println("--------------獲取public屬性--------------");
Field[] privateFields = perClass.getFields();
for (Field f: privateFields){
System.out.println(f.getName());
}
System.out.println("--------------獲取獲取特定的屬性:age--------------");
Field ff = perClass.getDeclaredField("age");
System.out.println(ff);
System.out.println("----------------獲取特定的屬性: i------------");
Field ff2= perClass.getField("i");
System.out.println(ff2);
System.out.println("---------------------");
//實例化這個類,賦值給p1
Person p1 = (Person) perClass.newInstance();
//解除私有限定
field.setAccessible(true);
// 給p1對象的age屬性賦值20
field.set(p1,20);
//獲取屬性age
System.out.println(""+field.get(p1));
}
結果:
5.通過反射越過泛型檢查
例如:ArrayList的一個對象,在這個集合中添加一個字符串數據,
// 泛型只在編譯期有效,在運行期會被擦出掉
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
Class clazz = Class.forName("java.util.ArrayList");
Method m = clazz.getMethod("add",Object.class);
m.invoke(list,"aaa");
System.out.println(list); //[100, 200, aaa]
好了,暫時總結這些,有新的認識再去補充。
參考的博文:
https://www.zhihu.com/question/24304289
https://www.sczyh30.com/posts/Java/java-reflection-1/