什么是Java的反射機制?
Java 反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為 Java 語言的反射機制。
簡單來說,反射就是可以在程序運行的時候動態裝載類,查看類的信息,生成對象,或操作生成的對象。
Java反射機制相關API
Class類介紹
Class 類的實例表示正在運行的 Java 應用程序中的類和接口。JVM 中有 N 多的實例,每個類的實例都有 Class 對象。(包括基本數據類型)
Class 沒有公共構造方法。Class 對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的 defineClass 方法自動構造的。也就是這不需要我們自己去處理創建,JVM 已經幫我們創建好了。
Java 類的加載過程:
在 Java 中,類裝載器把一個類裝入 Java 虛擬機中,要經過三個步驟來完成:裝載、鏈接和初始化,其中鏈接又可以分成校驗、准備和解析三步:
- 裝載:查找和導入類或接口的二進制數據;
- 鏈接:執行下面的校驗、准備和解析步驟,其中解析步驟是可以選擇的;
- 校驗:檢查導入類或接口的二進制數據的正確性;
- 准備:給類的靜態變量分配並初始化存儲空間;
- 解析:將符號引用轉成直接引用;
- 初始化:激活類的靜態變量的初始化 Java 代碼和靜態 Java 代碼塊。
如果知道一個實例,那么可以通過實例的getClass()
方法獲得運行實例的 Class(該類型的字節碼文件對象),如果你知道一個類型,那么你也可以使用.class
的方法獲得運行實例的 Class。
部分相關api如下所示
//返回與給定字符串名稱的類或接口相關聯的類對象。
public static Class <?> forName(String className)throws ClassNotFoundException
//返回類表示此所表示的實體(類,接口,基本類型或void)的超類類 。
public Class<? super T> getSuperclass()
//確定由該對象表示的類或接口實現的接口。
public Class< ? >[] getInterfaces()
//創建此 Class 對象所表示的類的一個新實例
public T newInstance() throws InstantiationException, IllegalAccessException
//返回表示此類公共構造方法的 Constructor 對象數組
public Constructor< ? >[] getConstructors() throws SecurityException
//返回 Constructor 對象的一個數組
public Constructor<?>[] getDeclaredConstructors() throws SecurityException
//返回表示公共字段的 Field 對象的數組
public Field[] getFields() throws SecurityException
//返回表示此類所有已聲明字段的 Field 對象的數組
public Field[] getDeclaredFields() throws SecurityException
//表示此類中公共方法的 Method 對象的數組
public Method[] getMethods() throws SecurityException
//表示此類所有聲明方法的 Method 對象的數組
public Method[] getDeclaredMethods() throws SecurityException
//返回該類的類加載器。
public ClassLoader getClassLoader()
//返回:使用參數 args 在 obj 上指派該對象所表示方法的結果
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
//查找具有給定名稱的資源。
public InputStream getResourceAsStream(String name)
//返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
具體相關方法及其使用方法可以查看API文檔
Java 反射機制舉例
以下例子基於Person類
package DateTest;
public class Person implements Comparable{
public String name;
private int age;
public int id;
protected String phone;
public Person() {
System.out.println("默認的無參構造方法執行了");
}
public Person(String name) {
System.out.println("姓名:"+name);
}
public Person(String name,int age) {
System.out.println("姓名:"+name+"年齡:"+age);
}
//受保護的構造方法
protected Person(boolean b) {
System.out.println("這是一個受保護的構造方法:b="+b);
}
//私有構造方法
private Person(int age) {
System.out.println("這是一個私有構造方法,年齡="+age);
}
@Override
public int compareTo(Object arg0) {
// TODO Auto-generated method stub
return 0;
}
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;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
通過一個對象獲取某個類的完整包名和類名
通過實例的 getClass() 方法獲得運行實例的字節碼文件對象,然后通過 getName() 方法獲得類的完整包名和類名。
public void test1() {
Person person = new Person();
System.out.println(person.getClass().getName());
}
//輸出結果:
默認的無參構造方法執行了
DateTest.Person
獲取 Class 對象
- 方式1:通過 Class 類的靜態方法獲取 Class 類對象
- 方式2:因為所有類都繼承 Object 類。因而可通過調用 Object 類中的 getClass 方法來獲取
- 方式3:任何數據類型(包括基本數據類型)都有一個“靜態”的 class 屬性
public void test2() throws ClassNotFoundException {
Class<?> class1 = null;
Class<?> class2 = null;
Class<?> class3 = null;
class1 = Class.forName("DateTest.Person");
class2 = new Person().getClass();
class3 = Person.class;
System.out.println("類名稱1:"+class1.getName());
System.out.println("類名稱2:"+class2.getName());
System.out.println("類名稱3:"+class3.getName());
}
//輸出結果
默認的無參構造方法執行了
類名稱1:DateTest.Person
類名稱2:DateTest.Person
類名稱3:DateTest.Person
獲取一個對象的父類和實現的接口
為了測試getInterfaces()
方法,此處將Person類繼承Comparable接口
public void test3() throws ClassNotFoundException {
Class<?> clazz = Class.forName("DateTest.Person");
//獲取父類
Class<?> parentClass = clazz.getSuperclass();
System.out.println("父類為:"+parentClass.getName());
//獲取所有接口
Class<?> interf[] = clazz.getInterfaces();
System.out.println("實現的接口有:");
for(int i = 0; i < interf.length; i++) {
System.out.println((i+1)+":"+interf[i].getName());
}
}
//輸出結果
父類為:java.lang.Object
實現的接口有:
1:java.lang.Comparable
獲取某個類的全部構造函數並調用私有構造方法
獲取 Student 類的全部構造函數,並使用 Class 對象的 newInstance() 方法來創建 Class 對象對應類 Person 的實例來調用私有構造方法。
public void test4() throws Exception {
//1.加載Class對象
Class clazz = Class.forName("DateTest.Person");
//2.獲取所有公有構造方法
System.out.println("所有公有構造方法");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray) {
System.out.println(c);
}
System.out.println("----------------------------");
System.out.println("所有構造方法");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray) {
System.out.println(c);
}
System.out.println("----------------------------");
System.out.println("公有、無參的構造方法");
Constructor con = clazz.getConstructor(null);
System.out.println("con = " + con);
//調用構造方法
Object obj = con.newInstance();
System.out.println("obj = " + obj);
System.out.println("----------------------------");
System.out.println("獲取私有的構造方法,並調用");
con = clazz.getDeclaredConstructor(int.class);
System.out.println(con);
//調用構造方法
con.setAccessible(true);
obj = con.newInstance(20);
}
//輸出結果
所有公有構造方法
public DateTest.Person(java.lang.String,int)
public DateTest.Person(java.lang.String)
public DateTest.Person()
----------------------------
所有構造方法
private DateTest.Person(int)
protected DateTest.Person(boolean)
public DateTest.Person(java.lang.String,int)
public DateTest.Person(java.lang.String)
public DateTest.Person()
----------------------------
公有、無參的構造方法
con = public DateTest.Person()
默認的無參構造方法執行了
obj = DateTest.Person@7a4f0f29
----------------------------
獲取私有的構造方法,並調用
private DateTest.Person(int)
這是一個私有構造方法,年齡=20
獲取某個類的全部屬性
獲取 Person類的全部屬性並調用:
public void test5() throws Exception {
//1.獲取Class對象
Class pclass = getClass().forName("DateTest.Person");
//2.獲取字段
System.out.println("獲取所有公有字段");
Field[] fieldArray = pclass.getFields();
for(Field f : fieldArray) {
System.out.println(f);
}
System.out.println("----------------------------");
System.out.println("獲取所有字段");
fieldArray = pclass.getDeclaredFields();
for(Field f : fieldArray) {
System.out.println(f);
}
System.out.println("----------------------------");
Field f = pclass.getField("name");
System.out.println(f);
//獲取一個對象
Object obj = pclass.getConstructor().newInstance();
f.set(obj,"張三");
//驗證
Person p = (Person)obj;
System.out.println("驗證的姓名:"+p.getName());
System.out.println("----------------------------");
f = pclass.getDeclaredField("age");
System.out.println(f);
f.setAccessible(true);
f.set(obj, 21);
System.out.println("驗證年齡:"+p);
}
//輸出結果
獲取所有公有字段
public java.lang.String DateTest.Person.name
public int DateTest.Person.id
----------------------------
獲取所有字段
public java.lang.String DateTest.Person.name
private int DateTest.Person.age
public int DateTest.Person.id
protected java.lang.String DateTest.Person.phone
----------------------------
public java.lang.String DateTest.Person.name
默認的無參構造方法執行了
驗證的姓名:張三
----------------------------
private int DateTest.Person.age
驗證年齡:Person [name=張三, age=21]
獲取某個類的全部方法
public void test6() throws Exception {
//1.獲取Class對象
Class pClass = Class.forName("DateTest.Person");
//2.獲取所有公有方法
System.out.println("獲取所有的公有方法*");
pClass.getMethods();
Method[] methodArray = pClass.getMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("----------------------------");
System.out.println("獲取所有的方法");
methodArray = pClass.getDeclaredMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("獲取公有的test1()方法");
Method m = pClass.getMethod("test1", String.class);
System.out.println(m);
//實例化一個Student對象
Object obj = pClass.getConstructor().newInstance();
m.invoke(obj, "lisi");
System.out.println("獲取私有的test4()方法*");
m = pClass.getDeclaredMethod("test4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要兩個參數,一個是要調用的對象(獲取有反射),一個是實參
System.out.println("返回值:" + result);
}
//輸出結果
獲取所有的公有方法*
public java.lang.String DateTest.Person.toString()
public int DateTest.Person.compareTo(java.lang.Object)
public java.lang.String DateTest.Person.getName()
public int DateTest.Person.getId()
public void DateTest.Person.setName(java.lang.String)
public void DateTest.Person.test1(java.lang.String)
public int DateTest.Person.getAge()
public java.lang.String DateTest.Person.getPhone()
public void DateTest.Person.setAge(int)
public void DateTest.Person.setPhone(java.lang.String)
public void DateTest.Person.setId(int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
----------------------------
獲取所有的方法
public java.lang.String DateTest.Person.toString()
public int DateTest.Person.compareTo(java.lang.Object)
public java.lang.String DateTest.Person.getName()
public int DateTest.Person.getId()
public void DateTest.Person.setName(java.lang.String)
public void DateTest.Person.test1(java.lang.String)
protected void DateTest.Person.test2()
private java.lang.String DateTest.Person.test4(int)
void DateTest.Person.test3()
public int DateTest.Person.getAge()
public java.lang.String DateTest.Person.getPhone()
public void DateTest.Person.setAge(int)
public void DateTest.Person.setPhone(java.lang.String)
public void DateTest.Person.setId(int)
獲取公有的test1()方法
public void DateTest.Person.test1(java.lang.String)
默認的無參構造方法執行了
調用了:公有的,String參數的test1(): name = lisi
獲取私有的test4()方法*
private java.lang.String DateTest.Person.test4(int)
調用了,私有的,並且有返回值的,int參數的test4(): age = 20
返回值:abcd
調用某個類的方法
Java 反射獲取 Class 對象並使用invoke()方法調用方法 reflect1() 和 reflect2():
public void test7() throws Exception{
Class<?> clazz = Class.forName("DateTest.ReflectTest");
Method method = clazz.getMethod("show1");
method.invoke(clazz.newInstance());
method = clazz.getMethod("show2", int.class, String.class);
method.invoke(clazz.newInstance(), 20, "張三");
}
public void show1() {
System.out.println("調用show1()");
}
public void show2(int age, String name) {
System.out.println("調用show2()");
System.out.println("age: " + age + "name: " + name);
}
//輸出結果
調用show1()
調用show2()
age: 20name: 張三
反射機制的動態代理
首先需要定義一個 InvocationHandler 接口的子類,完成代理的具體操作
package DateTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandler implements InvocationHandler{
private Object obj = null;
public Object bind(Object obj) {
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object temp = method.invoke(this.obj, args);
return temp;
}
}
package DateTest;
public interface Animal {
public void eat(String name);
}
package DateTest;
public class Dog implements Animal{
public void eat(String name) {
System.out.println(name+"eat!");
}
}
新建類實現接口Animal,使用 MyInvocationHandler.bind() 方法獲取即可實現調用。
public void test8(){
MyInvocationHandler invo = new MyInvocationHandler();
Animal sub = (Animal) invo.bind(new Dog());
sub.eat("小狗");
}
//輸出結果
小狗eat!
通過反射越過泛型檢查
泛型作用在編譯期,編譯過后泛型擦除(消失掉)。所以是可以通過反射越過泛型檢查的。
public void test9() throws Exception{
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(111);
list.add(222);
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "越過泛型檢查");
//遍歷集合
for(Object obj : list){
System.out.println(obj);
}
}
//輸出結果
111
222
越過泛型檢查
通過反射機制獲得數組信息並修改數組的大小和值
通過反射機制分別修改int和String類型的數組的大小並修改int數組的第一個值。
public static void test10() throws Exception{
int[] temp = { 12,45,65,5,1,32,4,56,12};
int[] newTemp = (int[]) arrayInc(temp, 15);
print(newTemp);
Array.set(newTemp, 0, 100);
System.out.println("修改之后數組第一個元素為: " + Array.get(newTemp, 0));
print(newTemp);
String[] atr = { "a", "b", "c" };
String[] str1 = (String[]) arrayInc(atr, 8);
print(str1);
}
// 修改數組大小
public static Object arrayInc(Object obj, int len) {
Class<?> arr = obj.getClass().getComponentType();
Object newArr = Array.newInstance(arr, len);
int co = Array.getLength(obj);
System.arraycopy(obj, 0, newArr, 0, co);
return newArr;
}
// 打印
public static void print(Object obj) {
Class<?> c = obj.getClass();
if (!c.isArray()) {
return;
}
Class<?> arr = obj.getClass().getComponentType();
System.out.println("數組類型: " + arr.getName());
System.out.println("數組長度為: " + Array.getLength(obj));
for (int i = 0; i < Array.getLength(obj); i++) {
System.out.print(Array.get(obj, i) + " ");
}
System.out.println();
}
//輸出結果
數組類型: int
數組長度為: 15
12 45 65 5 1 32 4 56 12 0 0 0 0 0 0
修改之后數組第一個元素為: 100
數組類型: int
數組長度為: 15
100 45 65 5 1 32 4 56 12 0 0 0 0 0 0
數組類型: java.lang.String
數組長度為: 8
a b c null null null null null
將反射機制應用於工廠模式
對於普通的工廠模式當我們在添加一個子類的時候,就需要對應的修改工廠類。 當我們添加很多的子類的時候,會很麻煩。
package DateTest;
public interface Animal {
public abstract void sleep();
}
package DateTest;
public class Dog implements Animal{
@Override
public void sleep() {
System.out.println("dog");
}
}
package DateTest;
public class Cat implements Animal{
@Override
public void sleep() {
System.out.println("cat");
}
}
package DateTest;
public class Factory {
public static Animal getInstance(String ClassName) {
Animal a = null;
try {
a = (Animal) Class.forName(ClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return a;
}
}
public void test11()throws Exception{
Animal a = Factory.getInstance("DateTest.Dog");
if (a != null) {
a.sleep();
}
}
//輸出結果
dog
加載配置文件
//配置文件內容為
//className = DateTest.ReflectTest
public void test13() throws IOException {
Properties pro = new Properties();//獲取配置文件的對象
InputStream in=new Person().getClass().getResourceAsStream("/pro.txt");
pro.load(in);//將流加載到配置文件對象中
in.close();
System.out.println(pro.getProperty("className"));
}
//輸出結果
默認的無參構造方法執行了
DateTest.ReflectTest
獲取 ClassLoader 類加載器
public void test12() throws ClassNotFoundException {
//1、獲取一個系統的類加載器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
//2、獲取系統類加載器的父類加載器
classLoader = classLoader.getParent();
System.out.println(classLoader);
//3、獲取擴展類加載器的父類加載器
//輸出為Null,引導類加載器無法被Java程序直接引用
classLoader = classLoader.getParent();
System.out.println(classLoader);
//4、測試當前類由哪個類加載器進行加載 ,結果是系統的類加載器
classLoader = Class.forName("DateTest.ReflectTest").getClassLoader();
System.out.println(classLoader);
//5、測試JDK提供的Object類由哪個類加載器負責加載的
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
}
//輸出結果
sun.misc.Launcher$AppClassLoader@6bc7c054
sun.misc.Launcher$ExtClassLoader@2ef1e4fa
null
sun.misc.Launcher$AppClassLoader@6bc7c054
null
總結
反射機制的應用雖然有很多優點,但是反射會額外消耗一定的系統資源,因此,反射操作的效率要比那些非反射操作低得多。另外,反射允許代碼執行一般情況下不被允許的操作,所以一般能用其它方式實現的就盡量不去用反射。
參考文章
https://blog.csdn.net/sinat_38259539/article/details/71799078