反射
- 一.反射(Java Reflection)概述
- 二.Class類
-
- 1.Class類概述
- 2.執行個體化Class類對象(四種方法)
- 三.通過反射調用類的完整結構
-
- 1.通過反射調用類構造方法(建立對象)
- 2.通過反射調用類方法
- 3.通過反射調用類屬性
- 4.通過反射調用類中的指定方法
- 5.通過反射調用類中的指定屬性
- 四.動态代理
一.反射(Java Reflection)概述
即通過類名資訊, 可以取得類的内部資訊
Reflection (反射) 是被視為動态語言的關鍵, 反射機制允許程式在執行期借助于 Reflection API 取得任何類的内部資訊, 并能直接操作任意對象的内部屬性和方法。
Java 反射機制提供的功能
- 在運作時判斷任意一個對象所屬的類
- 在運作時構造任意一個類的對象
- 在運作時判斷任意一個類所具有的成員變量和方法
- 在運作時調用任意一個對象的成員變量和方法
- 生産動态代理
Java反射機制研究及應用
反射相關的主要API:
- java.lang.Class: 代表一個類
- java.lang.reflect.Method: 代表類的方法
- java.lang.reflect.Filed: 代表類的成員變量
- java.lang.reflect.Constructor: 代表類的構造方法
二.Class類
1.Class類概述
Class類是所有類的高度抽象, 是一個可以描述所有類的類
在Object類中定義了以下的方法, 此方法将被所有子類繼承:
- public final Class getClass()
該方法的傳回值類型是一個Class類,
Class類是Java反射的源頭, 從程式的運作結果來看, 即通過對象反射求出類的名稱。
反射可以得到的資訊:
某個類的屬性、方法和構造器、某個類到底實作了哪些接口。
對于每個類而言, JRE都為其保留一個不變的 Class 類型的對象。
一個Class對象包含了特定某個類的有關資訊。
- Class本身也是個類
- Class對象隻能由系統建立對象
- 一個類在JVM中隻會有一個Class執行個體
- 一個Class對象對應的是一個加載到JVM中的一個.class檔案
- 每個類的執行個體都會記得自己是由哪個Class執行個體所生成
- 通過Class可以完整地得到一個類中的完整結構
2.執行個體化Class類對象(四種方法)
1.通過類名.class建立
- Class cls = Person.class;
- 前提:已知具體的類, 通過類的class屬性擷取, 該方法最為安全可靠, 程式性能最高
2.通過類的執行個體對象.getClass() 方法擷取
- Class cls = p.getClass();
- 前提: 已知某個類的執行個體對象
3.通過Class類的靜态方法forName() 擷取(常用方法)
- Class cls = Class.forName(“day10.Person”);
- 前提: 已知一個類的全路徑(包名.類名), 可能抛出ClassNotFoundException
4.通過ClassLoader(了解)
- ClassLoader cl = this.getClass().getClassLoader();
- Class cls = cl.loadClass(“day10.Person”);
public class TestClass {
public static void main(String[] args) {
Person p = new Person();
p.getClass();
Class<? extends Person> cls = p.getClass();//cls對象就包含對象p所屬Person類的所有資訊
Class c1 = Person.class;//通過類名.class建立指定類的Class執行個體
Class c2 = p.getClass();//通過一個類的執行個體對象.getClass()方法,擷取對應類的Class執行個體
//通過Class的靜态方法forName(String className)來擷取一個類的class執行個體
//參數String className是類的全路徑(包名.類名)
//這是擷取Clsss執行個體的常用方式
try {
Class c3 = Class.forName("day20210411.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
三.通過反射調用類的完整結構
Field 屬性
Method 方法
Constructor 結構
Superclas 父類
Interface 接口
Annotation 注解
使用反射可以取得:
類所在的包
-
public Package getPackage()
傳回此對象所在的包
1.實作的全部Interfaces(接口)
-
public Class<?>[] getInterfaces()
傳回此Class對象所表示的類或接口 所實作的所有接口
2.所繼承的Superclass(父類)
-
public Class<? Super T>getSuperclass()
傳回表示此Class 所表示的實體(類、接口、基本類型)的父類的Class
3.全部的Constructors(構造器)
-
public Constructor[] getConstructors()
傳回此對象所表示的類的全部public構造方法
-
public Constructor[] getDeclaredConstructors()
傳回此對象所表示的類聲明的全部構造方法
Constructor類中:
- 取得修飾符: public int getModifiers(); //傳回值1代表public,2代表private
- 取得方法名稱: public String getName();
- 取得參數的類型: public Class<?>[] getParameterTypes();
4.全部的Methods(方法)
-
public Method[] getMethods()
傳回此對象所表示的類或接口的全部public方法
-
public Constructor[] getDeclaredMethods()
傳回此對象所表示的類或接口的全部方法
Method類中:
- 取得修飾符: public int getModifiers();
- 取得方法名稱: public String getName();
- 取得全部的參數類型: public Class<?>[] getParameterTypes();
- 取得全部的傳回值類型: public Class<?>[] getReturnTypes();
5.全部的Fields(屬性)
-
public Field[] getFields()
傳回此Class對象所表示的類或接口的public Field
-
public Field[] getDeclaredFields()
傳回此Class對象所表示的類或接口的全部Field
Field類中:
- 取得修飾符: public int gerModifiers();
- 取得Field的名稱: public String getName();
- 取得Field的屬性類型: public Class<?> getType();
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) {
try {
Class<?> cls = Class.forName("day20210411.Student");//擷取Student類的Class對象
Class<?> superCls = cls.getSuperclass();//擷取目前類的父類
System.out.println("父類:"+ superCls.getName());
Class<?>[] interfaces = cls.getInterfaces();//擷取目前類的所有接口(數組)
for(Class<?> c : interfaces ) {
System.out.println("接口:"+ c.getName());
}
// Constructor<?>[] cons = cls.getConstructors();//獲得類的公有的構造方法
Constructor<?>[] cons = cls.getDeclaredConstructors();//獲得類的所有的構造方法
for(Constructor<?> c : cons) {
System.out.println("構造方法:"+ c.getName());//擷取方法名稱
System.out.println("構造方法:"+ c.getModifiers());//擷取方法修飾符
//方法修飾符: 傳回數值 1代表public, 2代表private
Class<?>[] types = c.getParameterTypes();//擷取參數類型
for(Class<?> c1 : types) {
System.out.println("參數類型:"+c1.getName());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
1.通過反射調用類構造方法(建立對象)
首先要擷取類的Class對象
- Class<?> cls = Class.forName(“day20210411.Student”);
1.調用類的public無參構造
-
Object obj = cls.getConstructor().newInstance();
//傳回的是Object類對象,需要向下轉型
- Student stu = (Student)obj;
2.調用類的public有參構造(根據參數類型)
-
Constructor<?> c = cls.getConstructor(String.class);
//調用有一個String類型參數的構造器
-
Student stu1 = (Student)c.newInstance(“第一中學”);
//通過newInstance()執行個體化對象,Object類對象,需要向下轉型
3.強制調用所有本類定義的有參構造
-
Constructor<?> c2 = cls.getDeclaredConstructor(String.class,
int.class);
//調用有(String,int)類型參數的構造方法
-
c2.setAccessible(true);
//解除私有化封裝,下面就可以對這個私有方法強制調用
-
Student stu2 = (Student)c2.newInstance(“李白”,16);
//傳入實參,執行個體化對象
- c2.setAccessible(false);//重新對構造進行私有封裝
- System.out.println(stu2.school);//私有封裝後已執行個體化的對象仍然可以通路
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) {
try {
Class<?> cls = Class.forName("day20210411.Student");//擷取Student類的Class對象
/**
* 如何用反射的構造方法來建立對象
*/
//相當于調用Student類的公有無參構造,需要抛出異常
Object obj = cls.getDeclaredConstructor().newInstance();//傳回Object類對象,需要向下轉型
Student stu = (Student)obj;
//相當于調用Student類的公有有參構造,
Constructor<?> c = cls.getConstructor(String.class);//有一個String類型參數
Student stu1 = (Student)c.newInstance("第一中學");//通過newInstance()執行個體化對象,Object類對象,需要向下轉型
//通過反射機制可以強制調用私有的構造方法
Constructor<?> c2 = cls.getDeclaredConstructor(String.class, int.class);//調用有(String,int)類型參數的構造方法
c2.setAccessible(true);//解除私有的封裝,下面就可以對這個私有方法強制調用
Student stu2 = (Student)c2.newInstance("李白",16);//傳入實參,執行個體化對象
c2.setAccessible(false);//重新對構造進行私有封裝
System.out.println(stu2.school);//私有封裝後已執行個體化的對象仍然可以通路
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.通過反射調用類方法
同上:
- punlic Method[] getMethods()
- 傳回此對象所表示的類或接口的全部public方法, 包括從父類繼承的public方法
- public Constructor[] getDeclaredMethods()
- 傳回此對象所表示的類或接口的本類/接口中定義的所有方法, 不受權限修飾符的限制, 不包括從父類中繼承的方法
Method類中:
- 取得修飾符: public int getModifiers();
- 取得方法名稱: public String getName();
- 取得全部的參數類型: public Class<?>[] getParameterTypes();
- 取得全部的傳回值類型: public Class<?>[] getReturnTypes();
import java.lang.reflect.Method;
public class TestMethods {
public static void main(String[] args) {
Class<Student> cls = Student.class;
// Method[] methods = cls.getMethods();//擷取類的所有的公有方法
Method[] methods = cls.getDeclaredMethods();//擷取類的所有方法
for(Method m : methods) {
System.out.print("方法:"+m.getName()+",傳回值:"+m.getReturnType()+",修飾符:"+m.getModifiers());
Class<?>[] tp= m.getParameterTypes();
if(tp != null && tp.length > 0) {
for(Class<?> c : tp) {
System.out.print(",參數:"+c);
}
}
System.out.print('\n');
}
}
}
3.通過反射調用類屬性
同上:
-
public Field[] getFields()
傳回此Class對象所表示的類或接口的public Field, 包括從父類中繼承的public 屬性
-
public Field[] getDeclaredFields()
傳回此Class對象所表示的類或接口的本類/接口中定義的全部Field, 不受權限修飾符限制, 但不包括從父類中繼承的屬性
Field類中:
- 取得修飾符: public int gerModifiers();
- 取得Field的名稱: public String getName();
- 取得Field的屬性類型: public Class<?> getType();
- 取得Field值, (需要先執行個體化對象) public Object get(obj);
import java.lang.reflect.Field;
public class TestFileds {
public static void main(String[] args) {
try {
Class<?> cls = Class.forName("day20210411.Student");
Student stu = new Student();//通路屬性值需要先執行個體化對象
// Field[] fld = cls.getFields();//擷取目前類的public屬性(default(修飾符0)修飾的屬性無法通路)
Field[] fld = cls.getDeclaredFields();//擷取目前類的全部屬性
for(Field f : fld) {
System.out.println("類型:"+f.getType()+",屬性:"+f.getName()+",修飾符:"+f.getModifiers());
f.setAccessible(true);//解除私有屬性的封裝
try {
Object value = f.get(stu);//通路stu對象的屬性值
System.out.println(value);
} catch (Exception e) {
e.printStackTrace();
}
}
Package pkg = cls.getPackage();//擷取目前類所在的包
System.out.println(pkg.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
4.通過反射調用類中的指定方法
通過反射, 調用類中的方法, 通過Method類完成。
步驟:
- 通過Class類的getMethod(String name, Class…ParaeterTypes)方法, 通過指定方法名稱、參數類型、數量, 來取得指定方法的一個Method對象
- 使用Object invoke(Object obj, Object[] args)方法進行調用, 并向調用的方法中傳遞要設定的obj對象以及實參資訊
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class TestMethods {
public static void main(String[] args) {
Class<Student> cls = Student.class;
/**
* 調用指定方法
* 注意:下面調用的都是obj對象的方法,obj對象實際上就是Student對象
*/
try {
Constructor<Student> con = cls.getConstructor();
Object obj = con.newInstance();//調用無參構造,執行個體化對象
// Person p = (Person)obj;
//調用公有方法
Method m = cls.getMethod("setInfo",String.class,String.class);//擷取指定方法getMethod(方法名,參數類型)
m.invoke(obj,"李白","清華大學");//調用有參構造需要執行個體化對象。參數1是對象,後面的參數是實參
//調用私有方法
Method m1 = cls.getDeclaredMethod("test", String.class);
m1.setAccessible(true);//解除私有化封裝
m1.invoke(obj,"李白");
//有傳回值的方法
Method m3 = cls.getMethod("getSchool");//擷取方法名為getSchool(),且沒有參數的方法
String school = (String) m3.invoke(obj);//調用有傳回值的方法,傳回值為Object類,需要向下轉型為String類型
System.out.println(school);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.通過反射調用類中的指定屬性
在反射機制中, 可以直接通過Field類操作類中的屬性, 通過Field類提供的set()和get()方法就可以完成設定和取得屬性内容的操作。
-
public Field getField(String name)
傳回此Class對象所表示的類或接口的指定的public 的Field
-
public Field getDeclaredField(String name)
傳回此Class對象所表示的類或接口的指定的Field
在Field類中:
- 取得指定對象obj上此Field屬性内容: public Object get(Object obj);
- 設定指定對象obj上此Field屬性内容: public void set(Object obj,Object value);
- 使私有屬性解除私有化封裝: public void setAccessible(true);
注: 在類中屬性都設定為private的前提下, 在使用set()和get()方法時, 首先要使用Field類中的setAccessible(true)方法将要操作的屬性設定為可以被外部通路。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class TestFileds {
public static void main(String[] args) {
try {
Class<?> cls = Class.forName("day20210411.Student");
/**
* 調用指定屬性
*/
//首先反射建立一個對象
Constructor<?> con = cls.getConstructor();
// Object obj = con.newInstance();
Student stu1 = (Student)con.newInstance();
Field fld1 = cls.getField("school");//擷取名稱為school的public的屬性
fld1.set(stu1,"北京大學");//對stu1的school屬性設定值
String school = (String) fld1.get(stu1);//擷取stu1的school屬性的值
System.out.println(school);
//調用私有的屬性
Field fld2 = cls.getDeclaredField("secret");//擷取名稱為secret的private(或default)的屬性
fld2.setAccessible(true);//解除fld2(即secret)屬性的私有化封裝
fld2.set(stu1, "密碼");
String secret = (String) fld2.get(stu1);
System.out.println(secret);
} catch (Exception e) {
e.printStackTrace();
}
}
}
四.動态代理
JDK中的動态代理是通過反射類Proxy以及InvocationHandler回調接口實作的。
但是,JDK中所要進行動态代理的類必須要實作一個接口,也就是說隻能對該類所實作接口中定義的方法進行代理,這在實際程式設計中具有一定的局限性,而且使用反射的效率也并不是很高。
Proxy類:
專門完成代理的操作類, 是所有動态代理的父類。
通過此類為一個或多個接口動态地生成實作類。
建立一個動态代理類所對應的Class對象
static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
* 參數1是代理對象的類加載器,handler.getClass().getClassLoader()
* 參數2是被代理對象的接口,test.getClass().getInterfaces()
* 參數3是代理對象,handler
*
* 傳回值為Object類,就是成功被代理後的對象
* 根據當時情況進行類型轉換
動态代理步驟:
- 建立一個實作接口InvocationHandler的類, 它必須實作invoke方法, 以完成代理的具體操作
- 建立被代理的類及接口
- 通過Proxy的靜态方法Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)建立一個Subject接口代理
- 通過Subject代理調用RealSubject實作類的方法
- 注意:如果一個對象需要通過Proxy.newProxyInstance()方法被代理。那麼這個對象的類必須有相應的接口, 就像本例中的ItestDemo接口和實作類TestDemoImpl
1.定義接口。(動态代理實作的, 是實作類繼承自接口中定義的方法)
public interface Study {
//采用Proxy.newProxyInstance()方法進行動态代理
//是對一個類所實作接口中定義的方法進行代理
void study1();
void study2();
}
2.定義實作類。
public class Student implements Study{
@Override
public void study1() {
System.out.println("執行study1方法");
}
@Override
public void study2() {
System.out.println("執行study2方法");
}
}
3.建立動态代理類, 實作代理的具體操作
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 動态代理類
* @author chenyao
*/
public class ProxyDemo implements InvocationHandler {//代理類必須實作InvocationHandler接口
Object obj;//定義被代理的對象
public ProxyDemo(Object obj) {//定義構造方法從外部傳入對象
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法開始執行");
Object result = method.invoke(this.obj, args);//正常通過反射調用類方法的傳回結果
System.out.println(method.getName()+"方法執行完畢");
return result;//傳回invoke調用的方法執行結果
}
}
4.通過動态代理對象調用實作類重寫的接口的方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
// TestDemoImpl test = new TestDemoImpl();
Study stu = new Student();//對象的多态
//直接調用方法
stu.study1();
stu.study2();
System.out.println("=======================");
/**
* 需求:
* 在執行study1和study2方法時加入相應動作
* 在執行方法前列印study1或study2開始執行
* 在執行方法後列印study1或study2執行完畢
*/
/**
* 注意:如果一個對象需要通過Proxy.newProxyInstance()方法被代理
* 這個對象的類必須有相應的接口,
* 就像本例中的Student接口和實作類Study
*/
//建立代理器, 使用代理對象調用方法
// ProxyDemo handler = new ProxyDemo(stu);
InvocationHandler handler = new ProxyDemo(stu);//傳入被代理的對象
/**
* Proxy.newProxyInstance(ClassLoader loader,
* Class<?> interfaces,
* InvocationHandler h)
* 參數1是代理對象的類加載器,handler.getClass().getClassLoader()
* 參數2是被代理對象的接口,test.getClass().getInterfaces()
* 參數3是代理對象,handler
*
* 傳回值為Object類,就是成功被代理後的對象
* 根據當時情況進行類型轉換為實作的接口類型
*/
Study s = (Study) Proxy.newProxyInstance(
handler.getClass().getClassLoader(),
stu.getClass().getInterfaces(),
handler); //建立接口對象
s.study1();//通過代理對象代理實作接口的方法
s.study2();
}
}