天天看點

java初學筆記10-反射一.反射(Java Reflection)概述二.Class類三.通過反射調用類的完整結構四.動态代理

反射

  • 一.反射(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可以完整地得到一個類中的完整結構
java初學筆記10-反射一.反射(Java Reflection)概述二.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類,就是成功被代理後的對象
 * 根據當時情況進行類型轉換
           

動态代理步驟:

  1. 建立一個實作接口InvocationHandler的類, 它必須實作invoke方法, 以完成代理的具體操作
  2. 建立被代理的類及接口
  3. 通過Proxy的靜态方法Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)建立一個Subject接口代理
  4. 通過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();
	}
}