理解Java反射
反射簡介
Java讓我們在執行時識別物件和類的資訊,主要有2種方式:一種是傳統的RTTI,它假定我們在編譯時已經知道了所有的型別資訊;另一種是反射機制,它允許我們在執行時發現和使用類的資訊。
JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。
想解剖一個類,必須先要獲取到該類的位元組碼檔案物件。而解剖使用的就是Class類中的方法.所以先要獲取到每一個位元組碼檔案對應的Class型別的物件
反射就是動態的從記憶體載入一個指定的類,把java類中的各種成分對映成一個個的Java物件,獲取該類中的所有的內容。
反射的功能
1.在執行時判斷任意一個物件所屬的類;
2.在執行時構造任意一個類的物件;
3.在執行時判斷任意一個類所具有的成員變數和方法(通過反射甚至可以呼叫private方法);
4.在執行時呼叫任意一個物件的方法
重點:是執行時而不是編譯時
反射的優點:增加程式的靈活性;增強了程式的擴充套件性。
反射的缺點:運用反射會使我們的軟體的效能降低,複雜度增加。
反射的使用
獲取Class物件
1:通過每個物件都具備的方法getClass來獲取。弊端:必須要建立該類物件,才可以呼叫getClass方法。
2:每一個數據型別(基本資料型別和引用資料型別)都有一個靜態的屬性class。弊端:必須要先明確該類。
前兩種方式不利於程式的擴充套件,因為都需要在程式使用具體的類來完成。
3:使用的Class類中的方法,靜態的forName方法。指定什麼類名,就獲取什麼類位元組碼檔案物件,這種方式的擴充套件性最強,只要將類名的字串傳入即可。
示例程式碼如下:
package com.luis.test; public class User { private long id; private String name; public int age; public long getId() { return id; } public void setId(long id) { this.id = id; } 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 "User [id=" + id + ", name=" + name + ", age=" + age + "]"; } } public class GetClass { public static void main(String[] args) { //1、通過物件呼叫 getClass() 方法來獲取,通常應用在:比如你傳過來一個 Object // 型別的物件,而我不知道你具體是什麼類,用這種方法 User user = new User(); Class c1 = user.getClass(); //2、直接通過 類名.class 的方式得到,該方法最為安全可靠,程式效能更高 // 這說明任何一個類都有一個隱含的靜態成員變數 class Class c2 = User.class; //3、通過 Class 物件的 forName() 靜態方法來獲取,用的最多, //丟擲 ClassNotFoundException 異常 Class c3; try { c3 = Class.forName("com.luis.test.User"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
獲取類中的方法,屬性,建構函式。
public class GetClass {
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.luis.test.User");
Constructor con = clazz.getConstructor(new Class[]{});
User instance = (User) con.newInstance();
instance.toString();
//獲得類完整的名字
String className = clazz.getName();
System.out.println("---------完整類名----------");
System.out.println(className);
//獲得類的public型別的屬性。
Field[] fields = clazz.getFields();
System.out.println("---------public屬性----------");
for(Field field : fields){
System.out.println("public類:"+field.getName());
}
//獲得類的所有屬性包括私有的
Field [] allFields = clazz.getDeclaredFields();
System.out.println("---------所有屬性----------");
for(Field field : allFields){
System.out.println(field.getName());
}
//獲得類的public型別的方法。這裡包括 Object 類的一些方法
Method [] methods = clazz.getMethods();
System.out.println("---------public方法----------");
for(Method method : methods){
System.out.println(method.getName());
}
//獲得類的所有方法。
Method [] allMethods = clazz.getDeclaredMethods();
System.out.println("---------所有方法----------");
for(Method method : allMethods){
System.out.println(method.getName());
}
//獲得指定的屬性
Field f1 = clazz.getField("age");
System.out.println("---------指定屬性----------");
System.out.println(f1);
//獲得指定的私有屬性
System.out.println("---------指定私有屬性----------");
Field f2 = clazz.getDeclaredField("name");
//啟用和禁用訪問安全檢查的開關,值為 true,則表示反射的物件在使用時應該取消 java 語言的訪問檢查;反之不取消
f2.setAccessible(true);
System.out.println(f2);
//建立這個類的一個物件
Object p2 = clazz.newInstance();
//將 p2 物件的 f2 屬性賦值為 Bob,f2 屬性即為 私有屬性 name
f2.set(p2,"luis");
//使用反射機制可以打破封裝性,導致了java物件的屬性不安全。
System.out.println("---------建立物件----------");
System.out.println(f2.get(p2)); //Bob
//獲取構造方法
Constructor [] constructors = clazz.getConstructors();
System.out.println("---------獲取構造方法----------");
for(Constructor constructor : constructors){
System.out.println(constructor.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行結果:
---------完整類名----------
com.luis.test.User
---------public屬性----------
public類:age
---------所有屬性----------
id
name
age
---------public方法----------
toString
getName
getId
setName
getAge
setId
setAge
wait
wait
wait
equals
hashCode
getClass
notify
notifyAll
---------所有方法----------
toString
getName
getId
setName
getAge
setId
setAge
---------指定屬性----------
public int com.luis.test.User.age
---------指定私有屬性----------
private java.lang.String com.luis.test.User.name
---------建立物件----------
luis
---------獲取構造方法----------
public com.luis.test.User()
反射獲取父類屬性
可以通過反射獲取父類的私有屬性,程式碼如下:
public class Parent {
public String publicField = "parent_publicField";
protected String protectField = "parent_protectField";
String defaultField = "parent_defaultField";
private String privateField = "parent_privateField";
}
package com.luis.test;
public class Admin extends Parent{
}
public class GetClass {
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.luis.test.Admin");
//獲取父類私有屬性值
System.out.println(getFieldValue(clazz.newInstance(),"privateField"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Field getDeclaredField(Object obj,String fieldName) {
Field field = null;
Class c = obj.getClass();
for(; c != Object.class ; c = c.getSuperclass()){
try {
field = c.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
}catch (Exception e){
//如果這裡的異常列印或者往外拋,則就不會執行c = c.getSuperclass(),最後就不會進入到父類中了
}
}
return null;
}
public static Object getFieldValue(Object object,String fieldName) throws Exception{
Field field = getDeclaredField(object,fieldName);
return field.get(object);
}
}
執行結果:parent_privateField
要注意:直接通過反射獲取子類的物件是不能得到父類的屬性值的,必須根據反射獲得的子類 Class 物件在呼叫 getSuperclass() 方法獲取父類物件,然後在通過父類物件去獲取父類的屬性值。
動態代理
代理類在程式執行時建立的代理方式被成為動態代理。代理類並不是在Java程式碼中定義的,而是在執行時根據我們在Java程式碼中的“指示”動態生成的。相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類中的方法。
介面和實現類:
public interface Interface {
void doSomething();
void somethingElse(String arg);
}
public class RealObject implements Interface {
@Override
public void doSomething() {
System.out.println("doSomething.");
}
@Override
public void somethingElse(String arg) {
System.out.println("somethingElse " + arg);
}
}
動態代理物件處理器:
public class DynamicProxyHandler implements InvocationHandler {
private Object proxyed;
public DynamicProxyHandler(Object proxyed) {
this.proxyed = proxyed;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理工作了.");
return method.invoke(proxyed, args);
}
}
測試類:
public class Main {
public static void main(String[] args) {
RealObject real = new RealObject();
Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
new Class[] {Interface.class},new DynamicProxyHandler(real));
proxy.doSomething();
proxy.somethingElse("luis");
}
}
反射極大地增強了程式碼的靈活性,廣泛應用於各種框架中,因而對於反射的掌握是非常重要的。
本文參考了:
https://www.cnblogs.com/ysocean/p/6516248.html
https://www.cnblogs.com/luoxn28/p/5686794.html
https://blog.csdn.net/ShadowySpirits/article/details/79756259