Android & Java 反射基本知識講解(二)

獲取 Class 的成員

一個類的成員包括屬性(有人翻譯爲字段或者域)、方法。對應到 Class 中就是 Field、Method、Constructor。

獲取 Filed

獲取指定名字的屬性有 2 個 API

public Field getDeclaredField(String name)
                       throws NoSuchFieldException,
                              SecurityException;

public Field getField(String name)
               throws NoSuchFieldException,
                      SecurityException

兩者的區別就是 getDeclaredField() 獲取的是 Class 中被 private 修飾的屬性。 getField() 方法獲取的是非私有屬性,並且 getField() 在當前 Class 獲取不到時會向祖先類獲取。

獲取所有的屬性。

//獲取所有的屬性,但不包括從父類繼承下來的屬性
public Field[] getDeclaredFields() throws SecurityException {}

//獲取自身的所有的 public 屬性,包括從父類繼承下來的。
public Field[] getFields() throws SecurityException {

可以用一個例子,給大家加深一下理解。

public class Farther {
	
	public int a;
	
	private int b;

}

public class Son extends Farther {
	int c;
	
	private String d;
	
	protected float e;
}


package com.frank.test;

import java.lang.reflect.Field;

public class FieldTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Class cls = Son.class;
		
		try {
			Field field = cls.getDeclaredField("b");
			
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("getDeclaredField "+e.getMessage());
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("getDeclaredField "+e.getMessage());
		}
		
		try {
			Field field = cls.getField("b");
			
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("getField "+e.getMessage());
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("getField "+e.getMessage());
		}
		
		
		
		
		
		Field[] filed1 = cls.getDeclaredFields();
		
		for ( Field f : filed1 ) {
			System.out.println("Declared Field :"+f.getName());
		}
		
		Field[] filed2 = cls.getFields();
		
		for ( Field f : filed2 ) {
			System.out.println("Field :"+f.getName());
		}

	}

}

代碼打印結果:

java.lang.NoSuchFieldException: b
	at java.lang.Class.getDeclaredField(Unknown Source)
	at com.frank.test.FieldTest.main(FieldTest.java:13)
java.lang.NoSuchFieldException: bgetDeclaredField b

	at java.lang.Class.getField(Unknown Source)
	at com.frank.test.FieldTest.main(FieldTest.java:26)
getField b

Declared Field :c
Declared Field :d
Declared Field :e

Field :a

大家細細體會一下,不過需要注意的是 getDeclaredFileds() 方法可以獲取 private、protected、public 和 default 屬性,但是它獲取不到從父類繼承下來的屬性。

獲取 Method

類或者接口中的方法對應到 Class 就是 Method。
相應的 API 如下:

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

public Method getMethod(String name, Class<?>... parameterTypes)

public Method[] getDeclaredMethods() throws SecurityException


public Method getMethod(String name, Class<?>... parameterTypes) 

因爲跟 Field 類似,所以不做過多的講解。parameterTypes 是方法對應的參數。

獲取 Constructor

Java 反射把構造器從方法中單獨拎出來了,用 Constructor 表示。

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

public Constructor<T> getConstructor(Class<?>... parameterTypes)

public Constructor<?>[] getDeclaredConstructors() throws SecurityException 

public Constructor<?>[] getConstructors() throws SecurityException 

仍然以前面的 Father 和 Son 兩個類爲例。

public class Farther {
	
	public int a;
	
	private int b;

	public Farther() {
		super();
		// TODO Auto-generated constructor stub
	}
	

}

public class Son extends Farther {
	int c;
	
	private String d;
	
	protected float e;
	
	

	private Son() {
		super();
		// TODO Auto-generated constructor stub
	}



	public Son(int c, String d) {
		super();
		this.c = c;
		this.d = d;
	}
	
}

public class ConstructorTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Class clz = Son.class;
		
		Constructor[] constructors = clz.getConstructors();
		
		for ( Constructor c : constructors ) {
			System.out.println("getConstructor:"+c.toString());
		}
		
		constructors = clz.getDeclaredConstructors();
		
		for ( Constructor c : constructors ) {
			System.out.println("getDeclaredConstructors:"+c.toString());
		}

	}

}

測試程序代碼的打印結果如下:

getConstructor:public com.frank.test.Son(int,java.lang.String)

getDeclaredConstructors:private com.frank.test.Son()
getDeclaredConstructors:public com.frank.test.Son(int,java.lang.String)

因爲,Constructor 不能從父類繼承,所以就沒有辦法通過 getConstructor() 獲取到父類的 Constructor。

我們獲取到了 Field、Method、Constructor,但這一是終點,相反,這正是反射機制中開始的地方,我們運用反射的目的就是爲了獲取和操控 Class 對象中的這些成員。

Field 的操控

我們在一個類中定義字段時,通常是這樣。

public class Son extends Farther {
	int c;
	
	private String d;
	
	protected float e;
	
	Car car;

}

像 c、d、e、car 這些變量都是屬性,在反射機制中映射到 Class 對象中都是 Field,很顯然,它們也有對應的類別。

它們要麼是 8 種基礎類型 int、long、float、double、boolean、char、byte 和 short。或者是引用,所有的引用都是 Object 的後代。

Field 類型的獲取

獲取 Field 的類型,通過 2 個方法:

public Type getGenericType() {}

public Class<?> getType() {}

注意,兩者返回的類型不一樣,getGenericType() 方法能夠獲取到泛型類型。大家可以看下面的代碼進行理解:

public class Son extends Farther {
	int c;
	
	private String d;
	
	protected float e;
	
	public List<Car> cars;
	
	public HashMap<Integer,String> map;
	
	private Son() {
		super();
		// TODO Auto-generated constructor stub
	}



	public Son(int c, String d) {
		super();
		this.c = c;
		this.d = d;
	}
	
}

public class FieldTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Class cls = Son.class;
		
		
		Field[] filed2 = cls.getFields();
		
		for ( Field f : filed2 ) {
			System.out.println("Field :"+f.getName());
			System.out.println("Field type:"+f.getType());
			System.out.println("Field generic type:"+f.getGenericType());
			System.out.println("-------------------");
		}

	}

}

打印結果:

Field :cars
Field type:interface java.util.List
Field generic type:java.util.List<com.frank.test.Car>
-------------------
Field :map
Field type:class java.util.HashMap
Field generic type:java.util.HashMap<java.lang.Integer, java.lang.String>
-------------------
Field :a
Field type:int
Field generic type:int
-------------------

可以看到 getGenericType() 確實把泛型都打印出來了,它比 getType() 返回的內容更詳細。

Field 修飾符的獲取
同 Class 一樣,Field 也有很多修飾符。通過 getModifiers() 方法就可以輕鬆獲取。

public int getModifiers() {}

這個與前面 Class 獲取修飾符一致,所以不需要再講,不清楚的同學翻看前面的內容就好了。

Field 內容的讀取與賦值

這個應該是反射機制中對於 Field 最主要的目的了。

Field 這個類定義了一系列的 get 方法來獲取不同類型的值。

public Object get(Object obj);

public int getInt(Object obj);

public long getLong(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public float getFloat(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public short getShort(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public double getDouble(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public char getChar(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public byte getByte(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public boolean getBoolean(Object obj)
        throws IllegalArgumentException, IllegalAccessException

Field 又定義了一系列的 set 方法用來對其自身進行賦值。

public void set(Object obj, Object value);

public void getInt(Object obj,int value);

public void getLong(Object obj,long value)
        throws IllegalArgumentException, IllegalAccessException;

public void getFloat(Object obj,float value)
        throws IllegalArgumentException, IllegalAccessException;

public void getShort(Object obj,short value)
        throws IllegalArgumentException, IllegalAccessException;

public void getDouble(Object obj,double value)
        throws IllegalArgumentException, IllegalAccessException;

public void getChar(Object obj,char value)
        throws IllegalArgumentException, IllegalAccessException;

public void getByte(Object obj,byte b)
        throws IllegalArgumentException, IllegalAccessException;

public void getBoolean(Object obj,boolean b)
        throws IllegalArgumentException, IllegalAccessException

可能有同學會對方法中出現的 Object 參數有疑問,它其實是類的實例引用,這裏涉及一個細節。

Class 本身不對成員進行儲存,它只提供檢索,所以需要用 Field、Method、Constructor 對象來承載這些成員,所以,針對成員的操作時,一般需要爲成員指定類的實例引用。如果難於理解的話,可以這樣理解,班級這個概念是一個類,一個班級有幾十名學生,現在有A、B、C 3 個班級,將所有班級的學生抽出來集合到一個場地來考試,但是學生在試卷上寫上自己名字的時候,還要指定自己的班級,這裏涉及到的 Object 其實就是類似的作用,表示這個成員是具體屬於哪個 Object。這個是爲了精確定位。

下面用代碼來說明:

A testa = new A();
testa.a = 10;

System.out.println("testa.a = "+testa.a);

Class c = A.class;

try {
	Field fielda = c.getField("a");

	int ra = fielda.getInt(testa);
	
	System.out.println("reflection testa.a = "+ra);
	
	fielda.setInt(testa, 15);
	
	System.out.println("testa.a = "+testa.a);
	
} catch (NoSuchFieldException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
} catch (SecurityException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
} catch (IllegalArgumentException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
} catch (IllegalAccessException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

打印結果如下:

testa.a = 10
reflection testa.a = 10
testa.a = 15

我們再來看看 Field 被 private 修飾的情況

public class A {
	
	public int a;
	
	private int b;

	public int getB() {
		return b;
	}

	public void setB(int b) {
		this.b = b;
	}
	
}

再編寫測試代碼

A testa = new A();
testa.setB(3);

System.out.println("testa.b = "+testa.getB());

Class c = A.class;

try {
	Field fieldb = c.getDeclaredField("b");
	int rb = fieldb.getInt(testa);
	
	System.out.println("reflection testa.b = "+rb);
	
	fieldb.setInt(testa, 20);
	
	System.out.println("testa.b = "+testa.getB());
	
} catch (NoSuchFieldException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
} catch (SecurityException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
} catch (IllegalArgumentException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
} catch (IllegalAccessException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

打印的結果如下:

testa.b = 3
java.lang.IllegalAccessException: Class com.frank.test.FieldTest can not access a member of class com.frank.test.A with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
	at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(Unknown Source)
	at java.lang.reflect.AccessibleObject.checkAccess(Unknown Source)
	at java.lang.reflect.Field.getInt(Unknown Source)
	at com.frank.test.FieldTest.main(FieldTest.java:20)

拋異常了。這是因爲在反射中訪問了 private 修飾的成員,如果要消除異常的話,需要添加一句代碼。

fieldb.setAccessible(true);

再看打印結果

testa.b = 3
reflection testa.b = 3
testa.b = 20

Method 的操控
Method 對應普通類的方法。
我們看看一般普通類的方法的構成。

public int add(int a,int b);

方法由下面幾個要素構成:

  • 方法名
  • 方法參數
  • 方法返回值
  • 方法的修飾符
  • 方法可能會拋出的異常

很顯然,反射中 Method 提供了相應的 API 來提取這些元素。

Method 獲取方法名

通過 getName() 這個方法就好了。

以前面的 Car 類作爲測試對象。

public class MethodTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Car car = new Car();
		
		Class clz = car.getClass();
		
		Method methods[] = clz.getDeclaredMethods();
		
		for ( Method m : methods ) {
			System.out.println("method name:"+m.getName());
		} 
	}

}

打印結果如下:

method name:toString
method name:drive

Method 獲取方法參數

涉及到的 API 如下:

public Parameter[] getParameters() {}

返回的是一個 Parameter 數組,在反射中 Parameter 對象就是用來映射方法中的參數。經常使用的方法有:

Parameter.java

// 獲取參數名字
public String getName() {}

// 獲取參數類型
public Class<?> getType() {}

// 獲取參數的修飾符
public int getModifiers() {}

當然,有時候我們不需要參數的名字,只要參數的類型就好了,通過 Method 中下面的方法獲取。
Method.java

// 獲取所有的參數類型
public Class<?>[] getParameterTypes() {}

// 獲取所有的參數類型,包括泛型
public Type[] getGenericParameterTypes() {}

下面,同樣進行測試。

public class Car {
	
	private String mBand;
	
	private Color mColor;
	
	public enum Color {
		RED,
		WHITE,
		BLACK,
		BLUE,
		YELLOR
	}
	
	

	public Car() {
		super();
		// TODO Auto-generated constructor stub
	}


	public Car(String mBand) {
		this.mBand = mBand;
	}
	
	
	public void drive() {
		System.out.println("di di di,開車了!");
	}

	@Override
	public String toString() {
		return "Car [mBand=" + mBand + ", mColor=" + mColor + "]";
	}
	
	public void test(String[] paraa,List<String> b,HashMap<Integer,Son> maps) {}
	

}

public class MethodTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Car car = new Car();
		
		Class clz = car.getClass();
		
		Method methods[] = clz.getDeclaredMethods();
		
		
		
		for ( Method m : methods ) {
			System.out.println("method name:"+m.getName());
			
			Parameter[] paras = m.getParameters();
			
			for ( Parameter p : paras ) {
				System.out.println(" parameter :"+p.getName()+" "+p.getType().getName());
			}
			
			Class[] pTypes = m.getParameterTypes();
			
			System.out.println("method para types:");
			for ( Class type : pTypes ) {
				System.out.print(" "+ type.getName());
			}
			System.out.println();
			
			Type[] gTypes = m.getGenericParameterTypes();
			System.out.println("method para generic types:");
			for ( Type type : gTypes ) {
				System.out.print(" "+ type.getTypeName());
			}
			System.out.println();
			System.out.println("==========================================");
			
		} 
	}

}

打印結果如下:

method name:toString
method para types:

method para generic types:

==========================================
method name:test
 parameter :arg0 [Ljava.lang.String;
 parameter :arg1 java.util.List
 parameter :arg2 java.util.HashMap
method para types:
 [Ljava.lang.String; java.util.List java.util.HashMap
method para generic types:
 java.lang.String[] java.util.List<java.lang.String> java.util.HashMap<java.lang.Integer, com.frank.test.Son>
==========================================
method name:drive
method para types:

method para generic types:

==========================================
Method 獲取返回值類型
// 獲取返回值類型
public Class<?> getReturnType() {}


// 獲取返回值類型包括泛型
public Type getGenericReturnType() {}

Method 獲取修飾符

public int getModifiers() {}

這部分內容前面已經講過。

Method 獲取異常類型

public Class<?>[] getExceptionTypes() {}

public Type[] getGenericExceptionTypes() {}

Method 方法的執行

這個應該是整個反射機制的核心內容了,很多時候運用反射目的其實就是爲了以常規手段執行 Method。

public Object invoke(Object obj, Object... args) {}

Method 調用 invoke() 的時候,存在許多細節

invoke() 方法中第一個參數 Object 實質上是 Method 所依附的 Class 對應的類的實例,如果這個方法是一個靜態方法,那麼 ojb 爲 null,後面的可變參數 Object 對應的自然就是參數。

invoke() 返回的對象是 Object,所以實際上執行的時候要進行強制轉換。

在對 Method 調用 invoke() 的時候,如果方法本身會拋出異常,那麼這個異常就會經過包裝,由 Method 統一拋出 InvocationTargetException。而通過 InvocationTargetException.getCause() 可以獲取真正的異常。

下面同樣通過例子來說明,我們新建立一個類,要添加一個 static 修飾的靜態方法,一個普通的方法和一個會拋出異常的方法。

public class TestMethod {
	
	public static void testStatic () {
		System.out.println("test static");
	}
	
	private  int add (int a,int b ) {
		return a + b;
	}
	
	public void testException () throws IllegalAccessException {
		throw new IllegalAccessException("You have some problem.");
	}

}

我們編寫測試代碼:

public class InvokeTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Class testCls = TestMethod.class;
		
		try {
			Method mStatic = testCls.getMethod("testStatic",null);
			// 測試靜態方法
			mStatic.invoke(null, null);
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		TestMethod t = new TestMethod();
		
		try {
			Method mAdd = testCls.getDeclaredMethod("add",int.class,int.class);
			// 通過這句代碼才能訪問 private 修飾的 Method
			mAdd.setAccessible(true);
			int result = (int) mAdd.invoke(t, 1,2);
			System.out.println("add method result:"+result);
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			Method testExcep = testCls.getMethod("testException",null);
			
			try {
				testExcep.invoke(t, null);
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				//e.printStackTrace();

				// 通過 InvocationTargetException.getCause() 獲取被包裝的異常
				System.out.println("testException occur some error,Error type is :"+e.getCause().getClass().getName());
				System.out.println("Error message is :"+e.getCause().getMessage());
			}
			
			
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

打印結果如下:

test static
add method result:3
testException occur some error,Error type is :java.lang.IllegalAccessException
Error message is :You have some problem.

Constructor 的操控

在平常開發的時候,構造器也稱構造方法,但是在反射機制中卻把它與 Method 分離開來,單獨用 Constructor 這個類表示。

Constructor 同 Method 差不多,但是它特別的地方在於,它能夠創建一個對象。

在 Java 反射機制中有兩種方法可以用來創建類的對象實例:Class.newInstance() 和 Constructor.newInstance()。官方文檔建議開發者使用後面這種方法,下面是原因。

Class.newInstance() 只能調用無參的構造方法,而 Constructor.newInstance() 則可以調用任意的構造方法。
Class.newInstance() 通過構造方法直接拋出異常,而 Constructor.newInstance() 會把拋出來的異常包裝到 InvocationTargetException 裏面去,這個和 Method 行爲一致。
Class.newInstance() 要求構造方法能夠被訪問,而 Constructor.newInstance() 卻能夠訪問 private 修飾的構造器。
還是通過代碼來驗證。

public class TestConstructor {
	
	private String self;

	public TestConstructor() {
		self = " Frank ";
	}

	public TestConstructor(String self) {
		this.self = self;
	}

	@Override
	public String toString() {
		return "TestConstructor [self=" + self + "]";
	}
	

}

上面的類中有 2 個構造方法,一個無參,一個有參數。編寫測試代碼:

public class NewInstanceTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Class clz = TestConstructor.class;
		
		try {
			TestConstructor test1 = (TestConstructor) clz.newInstance();
			
			System.out.println(test1.toString());
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			Constructor con = clz.getConstructor(String.class);
			
			TestConstructor test2 = (TestConstructor) con.newInstance("Zhao");
			
			System.out.println(test2.toString());
			
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

分別用 Class.newInstance() 和 Constructor.newInstance() 方法來創建類的實例,打印結果如下:

TestConstructor [self= Frank ]
TestConstructor [self=Zhao]

可以看到通過 Class.newInstance() 方法調用的構造方法確實是無參的那個。

現在,我們學習了 Class 對象的獲取,也能夠獲取它內部成員 Filed、Method 和 Constructor 並且能夠操作它們。在這個基礎上,我們已經能夠應付普通的反射開發了。

但是,Java 反射機制還另外細分了兩個概念:數組和枚舉。

反射中的數組

數組本質上是一個 Class,而在 Class 中存在一個方法用來識別它是否爲一個數組。
Class.java

public native boolean isArray();

爲了便於測試,我們創建一個新的類

public class Shuzu {
	
	private int[] array;
	
	private Car[] cars;
}

其中有一個 int 型的數組屬性,它的名字叫做 array。還有一個 cars 數組,它的類型是 Car,是之前定義好的類。 當然,array 和 cars 是 Shuzu 這個類的 Field,對於 Field 的角度來說,它是數組類型,我們可以這樣理解數組可以同 int、char 這些基本類型一樣成爲一個 Field 的類別。

我們可能通過一系列的 API 來獲取它的具體信息,剛剛有提到它本質上還是一個 Class 而已。

getName();

getComponentType();

第二個方法是獲取數組的裏面的元素的類型,比如 int[] 數組的 componentType 自然就是 int。

按照慣例,寫代碼驗證。

public class ArraysTest {

	public static void main(String[] args) {
		Class clz = Shuzu.class;
		
		Field[] fields = clz.getDeclaredFields();
		
		for ( Field f : fields ) {
			// 獲取 Field 的類型
			Class c = f.getType();
			// 判斷這個類型是不是數組類型
			if ( c.isArray()) {
				System.out.println("Type is "+c.getName());
				System.out.println("ComponentType type is :"+c.getComponentType());
			}
		}
	}

}

打印結果如下:

Type is [I
ComponentType type is :int
Type is [Lcom.frank.test.Car;
ComponentType type is :class com.frank.test.Car

反射中動態創建數組

反射創建數組是通過 Array.newInstance() 這個方法。
Array.java

public static Object newInstance(Class<?> componentType, int... dimensions)
        throws IllegalArgumentException, NegativeArraySizeException {}

第一個參數指定的是數組內的元素類型,後面的是可變參數,表示的是相應維度的數組長度限制。

比如,我要創建一個 int[2][3] 的數組。

Array.newInstance(int.class,2,3);

Array 的讀取與賦值

首先,對於 Array 整體的讀取與賦值,把它作爲一個普通的 Field,根據 Class 中相應獲取和設置就好了。調用的是 Field 中對應的方法。

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


public Object get(Object obj)
           throws IllegalArgumentException,
                  IllegalAccessException;

還需要處理的情況是對於數組中指定位置的元素進行讀取與賦值,這要涉及到 Array 提供的一系列 setXXX() 和 getXXX() 方法。因爲和之前 Field 相應的 set 、get 方法類似,所以我在下面只摘抄典型的幾種,大家很容易知曉其它類型的怎麼操作。

public static void set(Object array,
                       int index,
                       Object value)
                throws IllegalArgumentException,
                       ArrayIndexOutOfBoundsException;


public static void setBoolean(Object array,
                              int index,
                              boolean z)
                       throws IllegalArgumentException,
                              ArrayIndexOutOfBoundsException;



public static Object get(Object array,
                         int index)
                  throws IllegalArgumentException,
                         ArrayIndexOutOfBoundsException;


public static short getShort(Object array,
                             int index)
                      throws IllegalArgumentException,
                             ArrayIndexOutOfBoundsException;

進行代碼測試:

public class ArraysTest {

	public static void main(String[] args) {
		Class clz = Shuzu.class;
		
		try {
			Shuzu shu = (Shuzu) clz.newInstance();
			
			Field arrayF = clz.getDeclaredField("array");
			arrayF.setAccessible(true);
			
			Object o = Array.newInstance(int.class, 3);
			Array.set(o, 0, 1);
			Array.set(o, 1, 3);
			Array.set(o, 2, 3);
			
			arrayF.set(shu, o);
			
			int[] array = shu.getArray();
			
			for ( int i = 0;i < array.length;i++) {
				System.out.println("array index "+i+" value:"+array[i]);
			}
			
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}

}

打印結果如下:

array index 0 value:1
array index 1 value:3
array index 2 value:3

反射中的枚舉 Enum

同數組一樣,枚舉本質上也是一個 Class 而已,但反射中還是把它單獨提出來了。

我們來看一般程序開發中枚舉的表現形式。

public enum State {
	IDLE,
	DRIVING,
	STOPPING,
	
	test();
	
	int test1() {
		return 0;
	}
}

枚舉真的跟類很相似,有修飾符、有方法、有屬性字段甚至可以有構造方法。

在 Java 反射中,可以把枚舉看成一般的 Class,但是反射機制也提供了 3 個特別的的 API 用於操控枚舉。

// 用來判定 Class 對象是不是枚舉類型
Class.isEnum()

// 獲取所有的枚舉常量
Class.getEnumConstants()


// 判斷一個 Field 是不是枚舉常量
java.lang.reflect.Field.isEnumConstant()

枚舉的獲取與設定

因爲等同於 Class,所以枚舉的獲取與設定就可以通過 Field 中的 get() 和 set() 方法。

需要注意的是,如果要獲取枚舉裏面的 Field、Method、Constructor 可以調用 Class 的通用 API。

用例子來加深理解吧。

public enum State {
	IDLE,
	DRIVING,
	STOPPING,
	
	test();
	
	int test1() {
		return 0;
	}

}

public class Meiju {
	
	private State state = State.DRIVING;

	public State getState() {
		return state;
	}

	public void setState(State state) {
		this.state = state;
	}
}
public static void main(String[] args) {
		
		Class clz = State.class;
		
		if ( clz.isEnum()){
			System.out.println(clz.getName()+" is Enum");
			
			System.out.println(Arrays.asList(clz.getEnumConstants()));
			// 獲取枚舉中所有的 Field
			Field[] fs = clz.getDeclaredFields();
			
			for ( Field f : fs ) {
				if ( f.isEnumConstant()){
					System.out.println(f.getName()+" is EnumConstant");
				}else {
					System.out.println(f.getName()+" is not EnumConstant");
				}
			}
			
			Class cMeiju = Meiju.class;
			Meiju meiju = new Meiju();
			
			try {
				Field f = cMeiju.getDeclaredField("state");
				f.setAccessible(true);
				
				
				try {
					State state = (State) f.get(meiju);
					
					System.out.println("State current is "+state);
					
					f.set(meiju, State.STOPPING);
					
					
					System.out.println("State current is "+meiju.getState());
					
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			} catch (NoSuchFieldException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (SecurityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}

}

打印結果如下:

com.frank.test.State is Enum
[IDLE, DRIVING, STOPPING, test]
IDLE is EnumConstant
DRIVING is EnumConstant
STOPPING is EnumConstant
test is EnumConstant
ENUM$VALUES is not EnumConstant
State current is DRIVING
State current is STOPPING
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章