反射

CH_song發表於2024-08-18

反射

java.lang.Class類

Java反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為Java語言的反射機制。

要想解剖一個類,必須先要獲取到該類的Class物件。而剖析一個類或用反射解決具體的問題就是使用相關API(1)java.lang.Class(2)java.lang.reflect.*。所以,Class物件是反射的根源。

獲取Class物件的四種方式

(1)型別名.class

要求編譯期間已知型別

(2)物件.getClass()

獲取物件的執行時型別

(3)Class.forName(型別全名稱)

可以獲取編譯期間未知的型別

(4)ClassLoader的類載入器物件.loadClass(型別全名稱)

可以用系統類載入物件或自定義載入器物件載入指定路徑下的型別

public class TestClass {
	@Test
	public void test05() throws ClassNotFoundException{
		Class c = TestClass.class;
		ClassLoader loader = c.getClassLoader();
		
		Class c2 = loader.loadClass("com.atguigu.test05.Employee");
		Class c3 = Employee.class;
		System.out.println(c2 == c3);
	}
	
	@Test
	public void test03() throws ClassNotFoundException{
		Class c2 = String.class;
		Class c1 = "".getClass();
		Class c3 = Class.forName("java.lang.String");
		
		System.out.println(c1 == c2);
		System.out.println(c1 == c3);
	}
}

反射的應用

獲取型別的詳細資訊

可以獲取:包、修飾符、型別名、父類(包括泛型父類)、父介面(包括泛型父介面)、成員(屬性、構造器、方法)、註解(類上的、方法上的、屬性上的)

獲取:包、修飾符、型別名、父類、實現的介面名

public class TestClassInfo {
	public static void main(String[] args) throws NoSuchFieldException, SecurityException {
		//1、先得到某個型別的Class物件
		Class clazz = String.class;
		//比喻clazz好比是鏡子中的影子
		
		//2、獲取類資訊
		//(1)獲取包物件,即所有java的包,都是Package的物件
		Package pkg = clazz.getPackage();
		System.out.println("包名:" + pkg.getName());
		
		//(2)獲取修飾符
		//其實修飾符是Modifier,裡面有很多常量值
		/*
		 * 0x是十六進位制
		 * PUBLIC           = 0x00000001;  1    1
		 * PRIVATE          = 0x00000002;  2	10
		 * PROTECTED        = 0x00000004;  4	100
		 * STATIC           = 0x00000008;  8	1000
		 * FINAL            = 0x00000010;  16	10000
		 * ...
		 * 
		 * 設計的理念,就是用二進位制的某一位是1,來代表一種修飾符,整個二進位制中只有一位是1,其餘都是0
		 * 
		 * mod = 17          0x00000011
		 * if ((mod & PUBLIC) != 0)  說明修飾符中有public
		 * if ((mod & FINAL) != 0)   說明修飾符中有final
		 */
		int mod = clazz.getModifiers();
		System.out.println(Modifier.toString(mod));
		
		//(3)型別名
		String name = clazz.getName();
		System.out.println(name);
		
		//(4)父類,父類也有父類對應的Class物件
		Class superclass = clazz.getSuperclass();
		System.out.println(superclass);
		
		//(5)父介面們
		Class[] interfaces = clazz.getInterfaces();
		for (Class class1 : interfaces) {
			System.out.println(class1);
}

成員(屬性、構造器、方法)

//(1)類的屬性,  你宣告的一個屬性,它是Field的物件

	/*	Field clazz.getField(name)  根據屬性名獲取一個屬性物件,但是隻能得到公共的
		Field[] clazz.getFields();  獲取所有公共的屬性
		Field clazz.getDeclaredField(name)  根據屬性名獲取一個屬性物件,可以獲取已宣告的
		Field[] clazz.getDeclaredFields()	獲取所有已宣告的屬性
		*/
		Field valueField = clazz.getDeclaredField("value");
//		System.out.println("valueField = " +valueField);
		
		Field[] declaredFields = clazz.getDeclaredFields();
		for (Field field : declaredFields) {
			//修飾符、資料型別、屬性名    
			int modifiers = field.getModifiers();
			System.out.println("屬性的修飾符:" + Modifier.toString(modifiers));
			
			String name2 = field.getName();
			System.out.println("屬性名:" + name2);
			
			Class<?> type = field.getType();
			System.out.println("屬性的資料型別:" + type);
		}

//(2)構造器們
		Constructor[] constructors = clazz.getDeclaredConstructors();
		for (Constructor constructor : constructors) {
			//修飾符、構造器名稱、構造器形參列表  、丟擲異常列表
			int modifiers = constructor.getModifiers();
			System.out.println("構造器的修飾符:" + Modifier.toString(modifiers));
			
			String name2 = constructor.getName();
			System.out.println("構造器名:" + name2);
			
			//形參列表
			System.out.println("形參列表:");
			Class[] parameterTypes = constructor.getParameterTypes();
			for (Class parameterType : parameterTypes) {
				System.out.println(parameterType);
			}
            
            //異常列表
			System.out.println("異常列表:");
			Class<?>[] exceptionTypes = constructor.getExceptionTypes();
			for (Class<?> exceptionType : exceptionTypes) {
				System.out.println(exceptionType);
			}
		}
//(3)方法們
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method method : declaredMethods) {
			//修飾符、返回值型別、方法名、形參列表 、異常列表 
			int modifiers = method.getModifiers();
			System.out.println("方法的修飾符:" + Modifier.toString(modifiers));
			
			Class<?> returnType = method.getReturnType();
			System.out.println("返回值型別:" + returnType);
			
			String name2 = method.getName();
			System.out.println("方法名:" + name2);
			
			//形參列表
			System.out.println("形參列表:");
			Class[] parameterTypes = method.getParameterTypes();
			for (Class parameterType : parameterTypes) {
				System.out.println(parameterType);
			}
			
			//異常列表
			System.out.println("異常列表:");
			Class<?>[] exceptionTypes = method.getExceptionTypes();
			for (Class<?> exceptionType : exceptionTypes) {
				System.out.println(exceptionType);
			}
		}

建立任意引用型別的物件並賦值(調構造器賦值)

兩種方式:

1、直接透過Class物件來例項化(要求必須有無參構造)

2、透過獲取構造器物件來進行例項化

方式一的步驟:

(1)獲取該型別的Class物件(2)建立物件

	@Test
	public void test2()throws Exception{
		Class<?> clazz = Class.forName("com.atguigu.test.Student");
		//Caused by: java.lang.NoSuchMethodException: com.atguigu.test.Student.<init>()
		//即說明Student沒有無參構造,就沒有無參例項初始化方法<init>
		Object stu = clazz.newInstance();
		System.out.println(stu);
	}
	
	@Test
	public void test1() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
//		AtGuigu obj = new AtGuigu();//編譯期間無法建立
		
		Class<?> clazz = Class.forName("com.atguigu.test.AtGuigu");
		//clazz代表com.atguigu.test.AtGuigu型別
		//clazz.newInstance()建立的就是AtGuigu的物件
		Object obj = clazz.newInstance();
		System.out.println(obj);
	}

方式二的步驟:

(1)獲取該型別的Class物件(2)獲取構造器物件(3)建立物件

如果構造器的許可權修飾符修飾的範圍不可見,也可以呼叫setAccessible(true)

示例程式碼:

public class TestNewInstance {
	@Test
	public void test3()throws Exception{
		//(1)獲取Class物件
		Class<?> clazz = Class.forName("com.atguigu.test.Student");
		/*
		 * 獲取Student型別中的有參構造
		 * 如果構造器有多個,我們通常是根據形參【型別】列表來獲取指定的一個構造器的
		 * 例如:public Student(int id, String name) 
		 */
		//(2)獲取構造器物件
		Constructor<?> constructor = clazz.getDeclaredConstructor(int.class,String.class);
		
		//(3)建立例項物件
		// T newInstance(Object... initargs)  這個Object...是在建立物件時,給有參構造的實參列表
		Object obj = constructor.newInstance(2,"張三");
		System.out.println(obj);
	}
    //訪問私有構造器
    @Test
    public void test05() throws Exception{
        //1.獲取Class 物件
        Class<Person> personClass = Person.class;
        //2.獲取指定的構造器
        Constructor<Person> c = personClass.getDeclaredConstructor(String.class, int.class, double.class);
        //3.禁止java語言的許可權訪問檢測
        c.setAccessible(true);
        //4.給成員變數賦值
        Person person = c.newInstance("王安石", 30, 9898.6);
        //5.展示結果
        System.out.println("person = " + person);
    }
}

操作任意型別的屬性

(1)獲取該型別的Class物件
Class clazz = Class.forName("com.atguigu.bean.User");

(2)獲取屬性物件
Field field = clazz.getDeclaredField("username");

(3)設定屬性可訪問

field.setAccessible(true);

(4)建立例項物件:如果操作的是非靜態屬性,需要建立例項物件
Object obj = clazz.newInstance();

(4)設定屬性值

field.set(obj,"chai");
(5)獲取屬性值
Object value = field.get(obj);

如果操作靜態變數,那麼例項物件可以省略,用null表示

示例程式碼:

//操作靜態變數 	
@Test
public void test01() throws NoSuchFieldException, IllegalAccessException {
        //1.獲取Class物件
        Class<Monkey> monkeyClass = Monkey.class;
        //2.獲取指定的屬性
        Field message = monkeyClass.getDeclaredField("message");
        //3.設定私有的屬性可以操作
        message.setAccessible(true);
        //4.給屬性賦值
        message.set(null,"HelloWorld");
        //5.獲取屬性值
        Object value = message.get(null);
        System.out.println("value = " + value);
}
//操作普通變數 	
@Test
public void test02() throws Exception {
       //1.獲取Class物件
        Class<Monkey> monkeyClass = Monkey.class;
        //2.獲取指定的屬性
        Field gender = monkeyClass.getDeclaredField("gender");
        //todo 設定私有的屬性可以訪問
        gender.setAccessible(true);
        //3.建立物件
        Monkey monkey = monkeyClass.newInstance();
        System.out.println("monkey = " + monkey);
        //4.給屬性賦值
        //todo 給monkey 物件的gender 屬性賦值 為 男
        gender.set(monkey,'男');
        //5.展示物件
        System.out.println("monkey = " + monkey);
}

呼叫任意型別的方法

(1)獲取該型別的Class物件
Class clazz = Class.forName("com.atguigu.service.UserService");
(2)獲取方法物件
Method method = clazz.getDeclaredMethod("login",String.class,String.class);
(3)建立例項物件
Object obj = clazz.newInstance();
(4)呼叫方法
Object result = method.invoke(obj,"chai","123);

如果方法的許可權修飾符修飾的範圍不可見,也可以呼叫setAccessible(true)

如果方法是靜態方法,例項物件也可以省略,用null代替

示例程式碼:

//操作靜態方法  
@Test
    public void test04() throws Exception{
       //todo 私有的靜態方法
        //1.獲取Class物件
        Class<Animal> animalClass = Animal.class;
        //2.獲取指定的方法
        Method isPrimeNum = animalClass.getDeclaredMethod("isPrimeNum", int.class);
        //3.設定私有方法可以使用
        isPrimeNum.setAccessible(true);
        //4.執行方法 獲取結果
        Object invoke = isPrimeNum.invoke(null, 20);
        //5.輸出結果
        System.out.println("invoke = " + invoke);

    }
//操作普通方法 
    @Test
    public void test03() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
       //todo 公共的例項方法
        //1.獲取Class物件
        Class<Animal> aClass = Animal.class;
        //2.獲取指定的方法
        Method sum = aClass.getMethod("sum", int.class, int.class);
        //3.建立物件
        Animal animal = aClass.newInstance();
        //4.呼叫方法
        sum.invoke(animal,89,20);

    }

相關文章