使用反射實例對象
使用反射機制,我們可以在運行時動態加載類並且實例化對象,操作對象的方法、改變類成員的值,甚至還可以改變私有(private)成員的值。
我們可以用 Class 的 newInstance() 方法來實例化一個對象,實例化的對象是以 Object 傳回的,例如:
Class c = Class.forName(className);
Object obj = c.newInstance();
下面範例動態加載list接口的類:
package CoreJava.day_2;
import java.util.List;
/**
* @author 李智
* @date 2016/12/5
*/
public class NewInstanceDemo {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
List list = (List) c.newInstance();
for (int i = 0; i < 5; i++) {
list.add("element " + i);
}
for (Object o : list.toArray()) {
System.out.println(o);
}
} catch (ClassNotFoundException e) {
System.out.println("找不到指定的類");
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
輸出:
java CoreJava.day_2.NewInstanceDemo java.util.ArrayList
element 0
element 1
element 2
element 3
element 4
實際上如果想要使用反射來動態加載類,通常是對對象的接口或類別都一無所知,也就無法像上面對 newInstance() 傳回的對象進行接口轉換。
如果加載的類中具備無參數的構造方法,則可以無參數的 newInstance() 來構造一個不指定初始化的引用,如果要在動態加載及生成對象時指定對象的引用,則要先指定參數類型、取得 Constructor 對象、使用 Constructor 的 newInstance() 並指定參數。
可以用一個例子來說明,先定義一個student類:
package CoreJava.day_2;
/**
* @author 李智
* @date 2016/12/05
*/
public class Student {
private String name;
private int score;
public Student() {
name = "N/A";
}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public void setName(String name) {
this.name = name;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public String toString() {
return name + ":" + score;
}
}
我們可以用 Class.forName() 來加載 Student ,並使用第二個有參數的構造方法來構造Student 實例:
package CoreJava.day_2;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author 李智
* @date 2016/12/5
*/
public class NewInstanceDemo2 {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
// 指定參數
Class[] params = new Class[2];
// 第一個是String
params[0] = String.class;
// 第二個是int
params[1] = Integer.TYPE;
// 取得對應的構造方法
Constructor constructor =
c.getConstructor(params);
// 指定引用內容
Object[] argObjs = new Object[2];
argObjs[0] = "caterpillar";
argObjs[1] = new Integer(90);
// 給定引用並初始化
Object obj = constructor.newInstance(argObjs);
// toString()查看
System.out.println(obj);
} catch (ClassNotFoundException e) {
System.out.println("找不到類");
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
System.out.println("沒有所指定的方法");
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
輸出:
java NewInstanceDemo2 CoreJava.day_2.Student
caterpillar:90
調用方法
使用反射可以取回類上方法的對象代表,方法的物件代表是 java.lang.reflect.Method 的實例,我們可以使用它的 invoke() 方法來動態調用指定的方法,例如調用上面 Student 上的 setName() 等方法:
package CoreJava.day_2;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author 李智
* @date 2016/12/5
*/
public class InvokeMethodDemo {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
// 使用無參構造方法實例對象
Object targetObj = c.newInstance();
// 設置參數類型
Class[] param1 = {String.class};
// 根據參數取回方法
Method setNameMethod = c.getMethod("setName", param1);
// 設置引用
Object[] argObjs1 = {"caterpillar"};
// 給引用調用指定對象的方法方法
setNameMethod.invoke(targetObj, argObjs1);
Class[] param2 = {Integer.TYPE};
Method setScoreMethod =
c.getMethod("setScore", param2);
Object[] argObjs2 = {new Integer(90)};
setScoreMethod.invoke(targetObj, argObjs2);
// 顯示類描述
System.out.println(targetObj);
} catch (ClassNotFoundException e) {
System.out.println("找不到類");
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
System.out.println("沒有這個方法");
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
我們可以指定加載 Student 類並生成實例,接着可以動態調用 setName() 和 setScore() 方法,由於調用setName() 和 setScore() 所設置的參數是 “caterpillar” 和90。
在很少的情況下,我們需要突破 Java 的存取限制來調用受保護的(protected)或私有(private)的方法(例如我們拿到一個組件(Component),但我們沒法修改它的原始碼來改變某個私有方法的權限,而我們又一定要調用某個私有方法),這時我們可以使用反射機制來達到目的,一個存取私有方法的例子如下:
Method privateMethod =
c.getDeclaredMethod("somePrivateMethod", new Class[0]);
privateMethod.setAccessible(true);
privateMethod.invoke(targetObj, argObjs);
使用反射來動態調用方法的實例例子之一是在 JavaBean 的設定,例如在 JSP/Servlet 中,可以根據使用者的請求名和 JavaBean 的屬性自動對比,將請求值設置到指定的 JavaBean 上,並自動根據參數類型轉換。
下面是一個map的小例子:
package CoreJava.day_2;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
/**
* @author 李智
* @date 2016/12/5
*/
public class CommandUtil {
public static Object getCommand(Map requestMap,
String commandClass)
throws Exception {
Class c = Class.forName(commandClass);
Object o = c.newInstance();
return updateCommand(requestMap, o);
}
// 使用reflection自動找出要更新的屬性
public static Object updateCommand(
Map requestMap,
Object command)
throws Exception {
Method[] methods =
command.getClass().getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
// 略過private、protected成員
// 且找出必須是set開頭的方法
if (!Modifier.isPrivate(methods[i].getModifiers()) &&
!Modifier.isProtected(methods[i].getModifiers()) &&
methods[i].getName().startsWith("set")) {
// 取得不包括set方法
String name = methods[i].getName()
.substring(3)
.toLowerCase();
// 如果setter名稱鍵值對相同
// 調用對應的setter並給值
if (requestMap.containsKey(name)) {
String param = (String) requestMap.get(name);
Object[] values = findOutParamValues(
param, methods[i]);
methods[i].invoke(command, values);
}
}
}
return command;
}
// 轉換對應類型
private static Object[] findOutParamValues(
String param, Method method) {
Class[] params = method.getParameterTypes();
Object[] objs = new Object[params.length];
for (int i = 0; i < params.length; i++) {
if (params[i] == String.class) {
objs[i] = param;
} else if (params[i] == Short.TYPE) {
short number = Short.parseShort(param);
objs[i] = new Short(number);
} else if (params[i] == Integer.TYPE) {
int number = Integer.parseInt(param);
objs[i] = new Integer(number);
} else if (params[i] == Long.TYPE) {
long number = Long.parseLong(param);
objs[i] = new Long(number);
} else if (params[i] == Float.TYPE) {
float number = Float.parseFloat(param);
objs[i] = new Float(number);
} else if (params[i] == Double.TYPE) {
double number = Double.parseDouble(param);
objs[i] = new Double(number);
} else if (params[i] == Boolean.TYPE) {
boolean bool = Boolean.parseBoolean(param);
objs[i] = new Boolean(bool);
}
}
return objs;
}
public static void main(String[] args) throws Exception {
Map<String, String> request =
new HashMap<String, String>();
request.put("name", "caterpillar");
request.put("score", "90");
Object obj = CommandUtil.getCommand(request, args[0]);
System.out.println(obj);
}
}
CommandUtil 可以自動根據方法上的參數類型,將Map 中的value轉換成相應的類型,目前它可以轉換基本類型和 String。
輸出:
java CommandUtilDemo CoreJava.day_2.Student
caterpillar:90
當然也可以修改成員變量,儘管直接讀取類的成員屬性(Field)是不被鼓勵的,但我們仍是可以直接存取公共的(public)成員屬性的,而我們甚至也可以通過反射機制來讀取私用成員變量,以一個例子來說明:
package CoreJava.day_2;
/**
* @author 李智
* @date 2016/12/5
*/
public class TestField {
public int testInt;
public String testString;
public String toString() {
return testInt + ":" + testString;
}
}
然後利用反射機制動態的讀取成員變量:
package CoreJava.day_2;
import java.lang.reflect.Field;
/**
* @author 李智
* @date 2016/12/5
*/
public class AssignFieldDemo {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
Object targetObj = c.newInstance();
Field testInt = c.getField("testInt");
testInt.setInt(targetObj, 99);
Field testString = c.getField("testString");
testString.set(targetObj, "caterpillar");
System.out.println(targetObj);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("沒有指定類");
} catch (ClassNotFoundException e) {
System.out.println("找不到指定的類");
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
System.out.println("找不到指定的成員變量");
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
輸出:
java AssignFieldDemo CoreJava.day_2.TestField
99:caterpillar
如果有必要的話,也可以通過反射機制來讀取私有的成員變量,例如:
Field privateField = c.getDeclaredField("privateField");
privateField.setAccessible(true);
privateField.setInt(targetObj, 99);
數組
在 Java 中數組也是一個對象,也會有一個 Class 實例來表示它,我們用幾個基本類型和String來進行測試:
package CoreJava.day_2;
/**
* @author 李智
* @date 2016/12/5
*/
public class ArrayDemo {
public static void main(String[] args) {
short[] sArr = new short[5];
int[] iArr = new int[5];
long[] lArr = new long[5];
float[] fArr = new float[5];
double[] dArr = new double[5];
byte[] bArr = new byte[5];
boolean[] zArr = new boolean[5];
String[] strArr = new String[5];
System.out.println("short 數組:" + sArr.getClass());
System.out.println("int 數組:" + iArr.getClass());
System.out.println("long 數組:" + lArr.getClass());
System.out.println("float 數組:" + fArr.getClass());
System.out.println("double 數組:" + dArr.getClass());
System.out.println("byte 數組:" + bArr.getClass());
System.out.println("boolean 數組:" + zArr.getClass());
System.out.println("String 數組:" + strArr.getClass());
}
}
輸出:
short 數組:class [S
int 數組:class [I
long 數組:class [J
float 數組:class [F
double 數組:class [D
byte 數組:class [B
boolean 數組:class [Z
String 數組:class [Ljava.lang.String;
Process finished with exit code 0
要使用反射機制動態生成數組的話,也可以這樣:
package CoreJava.day_2;
import java.lang.reflect.Array;
/**
* @author 李智
* @date 2016/12/5
*/
public class NewArrayDemo {
public static void main(String[] args) {
Class c = String.class;
Object objArr = Array.newInstance(c, 5);
for (int i = 0; i < 5; i++) {
Array.set(objArr, i, i + "");
}
for (int i = 0; i < 5; i++) {
System.out.print(Array.get(objArr, i) + " ");
}
System.out.println();
String[] strs = (String[]) objArr;
for (String s : strs) {
System.out.print(s + " ");
}
}
}
Array.newInstance() 的第一個參數是指定參數類型,而第二個參數是用來指定數組長度的,結果如下:
0 1 2 3 4
0 1 2 3 4
如果是二維數組,也是一樣的:
package CoreJava.day_2;
import java.lang.reflect.Array;
/**
* @author 李智
* @date 2016/12/5
*/
public class NewArrayDemo2 {
public static void main(String[] args) {
Class c = String.class;
// 打算建立一個3*4陣列
int[] dim = new int[]{3, 4};
Object objArr = Array.newInstance(c, dim);
for (int i = 0; i < 3; i++) {
Object row = Array.get(objArr, i);
for (int j = 0; j < 4; j++) {
Array.set(row, j, "" + (i + 1) * (j + 1));
}
}
for (int i = 0; i < 3; i++) {
Object row = Array.get(objArr, i);
for (int j = 0; j < 4; j++) {
System.out.print(Array.get(row, j) + " ");
}
System.out.println();
}
}
}
輸出結果:
1 2 3 4
2 4 6 8
3 6 9 12
如果想要知道數組元素的類型,可以在取得數組的 Class 實例之後,使用 Class 實例的 getComponentType() 方法,所取回的是元素的 Class 實例,例如:
int[] iArr = new int[5];
System.out.println(iArr.getClass().getComponentType());
對反射的總結差不多就寫到這裏了,查閱了很多資料,網絡上寫的也是參差不齊的,在手寫的幾十個demo支撐下,得出的一點關於反射的東西,肯定不能說全部正確,但是還是可以提供一些幫助的 -。-