Java 反射程式設計(下)

煙花再美不及伱發表於2021-01-02

反射與類操作(反射呼叫方法)

呼叫普通方法(核心)

在Class 類中定義有如下兩個取得類中普通方法的定義

  1. 取得全部方法
public Method[] getMethods()
                    throws SecurityException
  1. 取得指定的方法(核心)
public Method getMethod(String name,
                        Class<?>... parameterTypes)
                 throws NoSuchMethodException,
                        SecurityException

以上兩個方法返回的型別是 java.long.reflect.Method 類的物件. 在此類中提供有一個呼叫的方法支援,即如下
最重要的方法:

public Object invoke(Object obj, Object... args)
              throws IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException

案例: 取得一個類中的全部方法

package com.beyond.dhl.exercise;

import java.lang.reflect.Method;

class Person{
	private String name;
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
}

public class Test01 {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Person.class;
		Method met[] = cls.getMethods();  // 取得全部方法
		for (int x = 0; x < met.length; x++) {
			System.out.println(met[x]);
		}	
	}
}

可以取得類中的全部方法, 包括繼承的Object類
在這裡插入圖片描述

那麼在之前所編寫的程式對於類中的 setter, getter 方法 採用的都是明確的物件呼叫. 而現在有了反射處理機制之後, 這個時候的程式即使你沒有明確的Person型別的物件(依然需要例項化物件, Object描述, 所有的普通方法必須在有例項化物件之後才可以進行呼叫), 就需要通過反射完成.

案例: 通過反射呼叫 setter和getter(有明確開發要求)

package com.beyond.dhl.exercise;

import java.lang.reflect.Method;

class Person{
	private String name;
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
}

public class Test01 {
	public static void main(String[] args) throws Exception {
		String attribute = "name";  // 明確的告訴你屬性名稱
		String value = "DHL"; // 明確告訴你要設定的內容
		Class<?> cls = Class.forName("com.beyond.dhl.exercise.Person");  // Class.forName("com.beyond.dhl.exercise.Person") 相當於 Person.class 
		Object obj = cls.newInstance();  // 任何情況下呼叫類中的普通方法, 都必須有例項化物件
		// 取得setName這個方法是例項化物件, 設定方法名稱和引數的型別
		// setName() 是方法名稱, 但是這個方法名稱是根據給定的屬性的資訊拼湊得來的, 同時該方法需要接收一個String 型別的引數
		Method setMethod = cls.getMethod("set"+initcap(attribute), String.class);
		// 隨後需要通過Method類物件呼叫指定的方法, 呼叫方法必須有例項化物件, 同時要傳入一個引數
		setMethod.invoke(obj, value); // 相當於: Person物件.setName(value)
		
		Method getMethod = cls.getMethod("get"+initcap(attribute));
		Object ret = getMethod.invoke(obj);  // 相當於 Person物件.getName()
		System.out.println(ret);
		
	}
	
	public static String initcap(String str) {
		return str.substring(0,1).toUpperCase() + str.substring(1);
	}	
}

此類操作的好處是 : 不再侷限於某一具體型別的物件, 而是可以通過 Object 型別進行所有類的方法的呼叫

反射與類操作(反射呼叫成員)

在之前已經成功的實現了類的構造呼叫, 方法呼叫, 那麼除了這兩種模式之外還需要有成員呼叫.
前提: 類中的所有屬性一定要在類物件例項化之後才會進行空間的分配, 所有此時如果要想呼叫類的屬性, 必須要有例項化物件, 而通過反射的 newInstence() 方法可以直接取得例項化物件(Object 型別).

在Class 類裡面提供了有兩組取得屬性操作的方法:

1. 取得包括繼承類的屬性(是public許可權)(第一組)

1.1 取得類中的全部屬性

public Field[] getFields()
                  throws SecurityException

1.2 取得類中指定名稱的屬性

public Field getField(String name)
               throws NoSuchFieldException,SecurityException

2. 取得本類屬性(第二組)(無許可權要求)

2.1 取得本類中的全部屬性

public Field[] getDeclaredFields()
                          throws SecurityException

2.2 取得本類中指定名稱的屬性

public Field getDeclaredField(String name)
                       throws NoSuchFieldException, SecurityException

案例: 取得類中的全部方法

package com.beyond.dhl.exercise;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

class Person{
	private String name;  // 此時的類中只是明確提供了一個屬性
	protected String nickName;  // 此時的類中只是明確提供了一個屬性
	
}

class Student extends Person{  // 做一個繼承關係
	private String school;
	public String nameString;
}

public class Test01 {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("com.beyond.dhl.exercise.Student");
		{ // 普通程式碼塊
			Field fields[] = cls.getFields();  // 取得全部屬性
			for (int x = 0; x < fields.length; x++) {
				System.out.println(fields[x]);
			}
			
		}
		System.out.println("====================");
		{ // 普通程式碼塊
			Field fields[] = cls.getDeclaredFields();  // 取得全部屬性
			for (int x = 0; x < fields.length; x++) {
				System.out.println(fields[x]);
			}
			
		}
		
 	}
}

在這裡插入圖片描述

java.lang.reflect.Field 類

現在就需要關注屬性的核心描述類: java.lang.reflect.Field, 在這個類有兩個重要的方法:

1. 設定屬性內容(必須有了例項化物件才能開闢屬性的空間):

public void set(Object obj, Object value)
         throws IllegalArgumentException, IllegalAccessException

2. 取得屬性內容:

public Object get(Object obj)
           throws IllegalArgumentException, IllegalAccessException

在 AccessibleObject 中提供有一個方法 : 動態設定封裝, 也稱為 暴力反射

public void setAccessible(boolean flag)
                   throws SecurityException

即使用如下語句可以取消封裝:

nameField.setAccessible(true);  // 取消封裝

案例: 通過反射操作類屬性

package com.beyond.dhl.exercise;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

class Person{
	private String name;  // 此時的類中只是明確提供了一個屬性
	
}



public class Test01 {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("com.beyond.dhl.exercise.Person");
		Object obj = cls.newInstance();  // 例項化本類物件
		Field nameField = cls.getDeclaredField("name");
		nameField.setAccessible(true);  // 取消封裝
		nameField.set(obj, "張三"); // 等價於 物件.name = "張三";
		System.out.println(nameField.get(obj));  // 取得屬性
		
 	}
}

注意:

但是如果在實際開發之中使用更多的屬性操作絕對不可能直接按照如上的模式進行, 一定要使用setter, getter 方法, 應該可以給使用者操作的機會.
在 Field 類中有一個特別有用的方法: 取得屬性型別

public Class<?> getType()
package com.beyond.dhl.exercise;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

class Person{
	private String name;  // 此時的類中只是明確提供了一個屬性
	
}

public class Test01 {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("com.beyond.dhl.exercise.Person");
		Object obj = cls.newInstance();  // 例項化本類物件
		Field nameField = cls.getDeclaredField("name");
		System.out.println(nameField.getType().getName());  // 包.類
		System.out.println(nameField.getType().getSimpleName()); // 類名
		
 	}
}

在這裡插入圖片描述

將 Field 取得屬性與 Method 類中的 invoke() 結合一起, 可以編寫非常靈活的程式了.

相關文章