Java反射機制:JAVA反射機制是在運行狀態中,對於任意一個實體類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制。
反射的好處:
1.可以在程序運行過程中,操作這些對象。
2.可以進行解耦,提高程序的擴展性。
-
Java代碼在計算機中的三個階段
1. Sources源代碼階段:*.java被編譯成*.class字節碼文件。
2.Class類對象階段:*.class字節碼文件被類加載器加載進內存,並將其封裝成Class對象(用於描述在內存中描述字節碼文件),
Class對象將原字節碼文件中的成員變量,構造函數,方法等的做了封裝。
3.Runtime運行階段:創建對象的過程new
-
講解獲取Class對象的方式
獲取Class對象的三種方式對應着java代碼在計算機中的三個階段:
- 源代碼階段
Class.forName("全類名"):將字節碼文件加載進內存,返回Class對象。
- Class類對象階段
類名.class:通過類名的屬性class獲取
- Runtime運行時階段
對象.getClass():getClass()方法是定義在Object類中的方法。
結論:同一個字節碼文件(*.class)在一次程序運行過程中,只會被加載一次,無論通過哪一種方式獲取的Class對象都是同一個。
測試代碼:
package com.company.reflect;
import com.company.reflect.domain.Person;
/**
* ⊙﹏⊙&&&&&&⊙▽⊙
*
* @Auther: pangchenbo
* @Date: 2020/5/9 10:37
* @Description:
*/
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//方式一:Class.forName("全類名")
Class<?> aClass = Class.forName("com.company.reflect.domain.Person");
System.out.println(aClass);
//方式二:類名.class
Class<Person> personClass = Person.class;
System.out.println(personClass);
//方式三:對象.getClass()
Person person = new Person();
Class<? extends Person> aClass1 = person.getClass();
System.out.println(aClass1);
//比較 == 三個對象
System.out.println(aClass == aClass1);
System.out.println(personClass==aClass1);
}
}
兩個true表示Class對象是同一個。
-
獲取Class對象功能
(1)獲取成員變量們
Field[] getFields() :獲取所有public修飾的成員變量
Field getField(String name) 獲取指定名稱的 public修飾的成員變量
Field[] getDeclaredFields() 獲取所有的成員變量,不考慮修飾符
Field getDeclaredField(String name)
(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)
-
Field:成員變量
先寫一個測試類
public class Person {
private String name;
private int age;
public String a;
protected String b;
String c;
private String d;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
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 +
", a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
", d='" + d + '\'' +
'}';
}
//無參方法
public void eat(){
System.out.println("eat...");
}
//重載有參方法
public void eat(String food){
System.out.println("eat..."+food);
}
}
- 獲取所有的public修飾的成員變量
//0.獲取Person對象
Class<Person> personClass = Person.class;
//1.獲取所有public修飾的成員變量
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
- 獲取特定的成員變量(public)
//2.Field getField(String name)
Field a = personClass.getField("a");
//獲取成員變量a 的值 [也只能獲取公有的,獲取私有的或者不存在的字符會拋出異常]
Person person = new Person();
Object o = a.get(person);
System.out.println("o value: "+o);
//設置屬性a的值
a.set(person,"haha");
System.out.println(person);
- 獲取全部的成員變量
//Field[] getDeclaredFields():獲取所有的成員變量,不考慮修飾符
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField+" ");
}
System.out.println("==============================");
- 獲取特定的成員變量,在這裏如果需要對private進行修改,就必須進行暴力反射,將
d.setAccessible(true);設置爲true
System.out.println("==============================");
Field d = personClass.getDeclaredField("d");
d.setAccessible(true);//暴力反射
d.get(person);
d.set(person,"222");
System.out.println(person);
-
普通方法獲取
獲取指定名稱的方法(不帶參數的獲取)
Class<Person> personClass = Person.class;
//獲取指定名稱的方法
Method eat = personClass.getMethod("eat");
Person person = new Person();
eat.invoke(person);//執行方法
獲取指定名稱的方法(帶參數獲取)
//獲取具有參數的構造方法
Method eat1 = personClass.getMethod("eat", String.class);
eat1.invoke(person,"fans");
System.out.println("===============================");
獲取方法列表
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);//繼承的方法也會被訪問(前提是方法是public)
}
如果設置的方法中含有私有的方法,也可以設置d.setAccessible(true);設置爲true,然後就可以訪問私有方法。
-
構造方法
- 獲取無參數的構造器
Class<Person> personClass = Person.class;
//Constructor<?>[] getConstructors()
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
//Constructor<T> getConstructor(類<?>... parameterTypes)
//獲取無參
Constructor<Person> constructor1 = personClass.getConstructor();
System.out.println(constructor1);
//利用獲取的構造器創建對象
Person person = constructor1.newInstance();
System.out.println(person);
- 獲取有參數的構造器
//獲取有參
Constructor<Person> constructor = personClass.getConstructor(String.class,Integer.class);
System.out.println(constructor);
Person person1 = constructor.newInstance("PCB",100);
System.out.println(person1);
//理應Class類對象進行對象的構建獲取
Person person2 = personClass.newInstance();
System.out.println(person2);
//對於getDeclaredConstructor方法和getDeclaredConstructors方法,此外在構造器的對象內也有setAccessible(true);方法,並設置成true就可以操作了。
-
設計簡單的框架,理解反射的好處
準備測試類
package com.company.reflect.domain;
/**
* ⊙﹏⊙&&&&&&⊙▽⊙
*
* @Auther: pangchenbo
* @Date: 2020/5/9 13:27
* @Description:
*/
public class Student {
public void sleep(){
System.out.println("sleep...");
}
}
準備文件properties文件
className = com.company.reflect.domain.Student
methodName = sleep
- 需求
寫一個"框架",不能改變該類的任何代碼的前提下,可以幫我們創建任意類的對象,並且執行其中任意方法。
- 實現
(1)配置文件 (2)反射
- 步驟
(1)將需要創建的對象的全類名和需要執行的方法定義在配置文件中 (2)在程序中加載讀取配置文件 (3)使用反射技術來加載類文件進內存 (4)創建對象 (5)執行方法
package com.company.reflect.反射案例;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* ⊙﹏⊙&&&&&&⊙▽⊙
*
* @Auther: pangchenbo
* @Date: 2020/5/9 13:30
* @Description:
*/
public class ReflectTest {
public static void main(String[] args) throws Exception {
/**
* 前提:不能改變該類的任何代碼。可以創建任意類的對象,可以執行任意方法
*/
//1.加載配置文件
//1.1創建Properties對象
Properties properties = new Properties();
//1.2加載配置文件,轉換爲一個集合
//1.2.1獲取class目錄下的配置文件 使用類加載器
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
properties.load(resourceAsStream);
//2.獲取配置文件中定義的數據
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//加載類到內存中
Class<?> aClass = Class.forName(className);
//創建對象
Object o = aClass.newInstance();
//獲取對象方法
Method method = aClass.getMethod(methodName);
//執行方法
method.invoke(o);
}
}
改變配置文件
className = com.company.reflect.domain.Person
methodName = eat
- 好處
我們這樣做有什麼好處呢,對於框架來說,是人家封裝好的,我們拿來直接用就可以了,而不能去修改框架內的代碼。但如果我們使用傳統的new形式來實例化,那麼當類名更改時我們就要修改Java代碼,這是很繁瑣的。修改Java代碼以後我們還要進行測試,重新編譯、發佈等等一系列的操作。而如果我們僅僅只是修改配置文件,就來的簡單的多,配置文件就是一個實實在在的物理文件。