java反射教程(最全)

1.反射


在軟件中,反射的概念意味着在運行時檢查,分析和修改其它代碼的能力。例如想象一個應用程序把輸入文件當做包含的源代碼(我們不關心源代碼到底是什麼)。這個應用程序的目的是統計在每個被傳遞的類中包含的芳芳的數量。這能通過使用反射的分析代碼和統計它正真的方法,忽略其它類型的元素,像屬性,接口等等,並且按照類別分組。


純粹來說,這個例子不是真正的反射,因爲代碼沒有必要的在運行時被分析並且這個任務在任何階段能被做,但是它也能在運行時被做然後我們就能真的考慮反射了。


另一個例子將是一個應用程序分析被給的類的內容並且執行註解@Test。這的確是Junit做的事情;而且它使用的是反射。


2.介紹java中的反射


在Java中,在運行時它可能檢查域,類,方法,註解,接口等等。你不需要知道類或者方法如何被調用,也不需要參數,在運行時所有能被獲取的都用反射。它也可能初始化新類,創建新實例並且執行他們的方法,也都是用反射。


java的反射是本世紀初通過反射API被提出來的。類Class包含所有反射的相關方法,這些方法能被應用到類和對象,像這些是允許程序獲取類名,獲取類的公共方法等等。其它重要的類是Method,Field和Type,這些包含了我們將接下來看的指定的反射方法。


儘管反射在許多場景下是非常有用的,但是它不應當用來做每件事情。如果一些操作在沒有使用反射的情況下能被執行,那麼我們應當使用它。這是一些原因:

通過使用反射會影響性能,因爲所有的編譯最佳化不能被應用:反射在運行時被解決而不是編譯階段。


由於使用反射,安全缺陷必須考慮,因爲它也許不能運行在安全的上下文中,像Applets。


另一個重要的缺點是對代碼維護提出的好處。如果你的代碼大量使用反射那麼它將維護起來更困難。類和方法在代碼中不是直接被暴露的並且也許非常動態以至於它會在改變參數數量方面變的困難,如果代碼調用這個方法被調用通過反射。


當許多反射被呈現時自動反射器或者分析代碼工具也許會有麻煩。


3.用例



儘管有上面那些限制,但是反射在java中還是非常有用的工具,在下面情形下可考慮。


通常來說,反射能被用於觀察和修改運行時程序的行爲。這是最多情況下使用的案例列表:


IDEs爲了提供解決自動完成特性,動態類型,層級結構等會嚴重的利用反射。例如,像Eclipse或者PHP Storm的IDEs提供一種機制去動態的回收想要回收的參數給這個在被給的實例中以get開頭的被給的方法或者公共方法列表。所有那些都是用反射實現。

Debuggers是用反射動態的檢查正在被執行的代碼。

像Junit或者Mockito的測試工具是用反射,爲了調用期望的方法,包含了指定語法或者模仿指定類,接口和方法。

依賴注入框架是用反射在應用程序上下文的運行時和初始化注入beans和properties。

像PMD或者Findbugs這樣的代碼分析工具使用反射爲了分析衝突的代碼,這種違背是當前被配置的。

擴展工具利用動態的代碼也許也使用反射。


這這個教程中,我們將看幾個在java中使用反射的例子。我們將看到怎麼樣從被給的實例,在不知道這個實例的類的類型的情況下獲取所有方法並且我們將依賴他們的語法調用不同的方法。


我們不僅僅是展示其它教程,但是我們將一步步的向前驗證當用generics,註解,數組,集合以及其它對象類型使用反射時怎麼樣處理。最後我們將解釋在java 8中關於這個話題的主要的新特性。


4.反射組件和原理


爲了開始使用java反射寫代碼我們必須解釋一些相關的概念。


    

java中的接口是應用程序使用的一種契約。接口包含一個方法列表,這些方法被暴露並且必須被實現這個接口的子類實現。接口不能被初始化。從java8開始,他們包含了默認的方法實現景觀這不是經常使用。

Class是一系列方法的實現和一系列屬性的容器。它能被初始化。

Object是被給類的一個實例(PS:作者的這個描述好像不怎麼通順)。

Method是一些功能的實現。他們返回輸出類型和輸入參數。

Field是類的屬性

Enums是包含了一組預先定義的常量的元素。

Private是僅僅在本類中可見的元素並且不能從外部訪問。它會是一個方法,一個域...

Static元素是屬於類並且不是一個指定的實例。靜態元素可能是通過所有被給類的實例訪問的域,方法,這些在不需要初始化類的情況下可以被調用。當你使用反射時,這是非常有趣的,因爲調用一個靜態方法和調用一個非靜態方法是不同的,非靜態方法你需要一個類的實例執行它。

Annotation是代碼元數據的自我描述。

Collection是一組元素,可能是List,Map,Queue等等。

Array是包含固定值數量的一個對象。它的長度是固定的,並且在創建時被指定。

Dynamic proxy是在運行時一組列表的實現。他們使用類java.lang.reflect.Proxy。我們將看到更詳細的描述在下一章。

Class loader是負責載入被給的類名的一個對象。在java中,每個類提供方法去取回這個類載入器:Class.getClassLoader()。

Generics在java5中被介紹。他們提供編譯時安全通過表明一個集合的類型或者子類型將用於什麼。例如使用泛型你能保護一個應用程序在運行時使用包含字符串的列表嘗試添加Double到列表中。


那些組件的不同時重要的以至於在他們內部使用反射。調用私有方法和公共方法是不同的;獲取一個註解名字或者一個接口也是不同的。我們將在下一章給出所有的例子。


5.類


java中的每件事情都是關於類,還有反射的。類當我們討論反射的開始點。類java.lang.Class包含幾個方法,這些方法允許程序員去獲取在運行時的關於類和對象的信息。


爲了從一個單例中獲取類信息我們能寫(在這個例子中,是String類):


Class<? extends String> stringGetClass = stringer.getClass();


或者在沒有初始化情況直接從類名:

Class<String> stringclass = String.class;


或者使用java.lang.Class.forName(String)方法:

Class.forName("java.lang.String");


從一個類對象中我們能獲取所有種類的信息,像被定義的方法,構造器,可見域,註解,類型...在這個教程中所有那些將在下面章節被解釋。


檢查給定類的屬性也是可能的,像例如如果一個類是原始的或者一個實例:


stringGetClass.isInstance("dani");

stringGetClass.isPrimitive();


使用方法java.lang.Class.newInstance()傳遞正確的參數創建給定類的實例也是可能的:


String newInstanceStringClass = stringclass.newInstance();

String otherInstance = (String)Class.forName("java.lang.String").newInstance();


當類包含一個公共默認的構造器或者一個沒有參數的構造器時,java.lang.Class.newInstance()方法能被使用,如果這不是那樣的,這個方法不能被使用。在這個例子中java.lang.Class.newInstance()方法不能被使用來在運行時解決獲取一個合適的構造器和使用這個帶有參數的構造器創建一個它所期望的實例。我們將在下一章看到相關的構造器。


6.接口


接口是不能被初始化並且包含了應當有他們的子類實現的暴露的方法的元素。與反射有關的沒有相關的接口。


接口能像一個類使用他們的描述的名字一樣被訪問。所有方法對類是有效的,對接口也是。這是個例子怎麼樣在運行時訪問接口類的信息。


System.out.println( "interface name: " + InterfaceExample.class.getName() );


假設InterfaceExample原始是一個接口。


在類和接口之間一個明顯的不同是接口不能使用反射通過newInstance()方法被實例化:

//cannot be instantiated: java.lang.InstantiationException

InterfaceExample.class.newInstance();


上面的代碼段將在運行時拋出異常。在編譯時沒有錯誤出現。


7.枚舉


枚舉是java類型中的特殊類型,它允許變量被設置爲常量。那些常量在枚舉中被預先定義:


enum ExampleEnum

{

ONE, TWO, THREE, FOUR

};


java包含幾個枚舉特別方法:


java.lang.Class.isEnum():如果元素是枚舉類型,則返回true,否則返回false

java.lang.Class.getEnumConstants():獲取給定元素(它是枚舉)的所有常量。假如元素不是枚舉,則將拋出異常。

java.lang.reflect.Field.isEnumConstant():假如使用的域是個枚舉常量,則返回true,否則返回false。僅僅用於域。


我們將看到一個怎麼樣使用相關反射的主要的枚舉方法的例子。首先我們創建一個枚舉實例:

ExampleEnum value = ExampleEnum.FOUR;


如果元素師一個枚舉,我們能使用isEnum()方法檢查:


System.out.println( "isEnum " + value.getClass().isEnum() );


爲了獲取所有枚舉常量,我們能像下面使用方法getEnumConstants()做一些事情:

ExampleEnum[] enumConstants = value.getClass().getEnumConstants();

for( ExampleEnum exampleEnum : enumConstants )

{

        System.out.println( "enum constant " + exampleEnum );

}


最後,我們能檢查怎麼樣使用域的相關的isEnumConstants()方法。首先我們從給定類中獲取所有被定義的域(我們將在下一章節中看到所有有關反射功能更詳細的描述)接着如果這個域是一個枚舉常量或者不是,我們能檢查:

Field[] flds = value.getClass().getDeclaredFields();

for( Field f : flds )

{

// check for each field if it is an enum constant or not

System.out.println( f.getName() + " " + f.isEnumConstant() );

}


所有代碼的輸出將是下面信息:


isEnum true

enum constant ONE

enum constant TWO

enum constant THREE

enum constant FOUR

ONE true

TWO true

THREE true

FOUR true

ENUM$VALUES false


字符串ENUM$VALUES false是內部枚舉值域。想要了解更多的枚舉信息,請訪問

http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html.


8.原始類型


在java中,有一組類型被持有由於它的性質和行爲:當我們在談論反射至,原始類型像int,float,double等幾乎像任何其它類一樣能被訪問和使用。這是一組當我們使用原始類型工作時怎麼樣使用反射的例子:


跟其它的非原始類型一樣,從一個原始類型中獲得一個類對象是可能的:


Class<Integer> intClass = int.class;


但是不能使用反射給原始類型創建實例:

Integer intInstance = intClass.newInstance();


使用方法java.lang.Class.isPrimitive()可以檢查是否給定類屬於原始類型:


System.out.println( "is primitive: " + intClass.isPrimitive() );


在這個例子中一個類型異常java.lang.InstantiationException將被拋出。


9.域


Class域使用反射能在運行時被持有。類提供幾個方法在運行時來訪問他們的域。最重要的幾個是:

java.lang.Class.getDeclaredFields():它返回這個類的所有被定義的域數組。它也返回所有的私有域。

java.lang.Class.getFields():它返回這個類的所有可訪問域的數組。

java.lang.Class.getField(String):它通過傳遞一個名稱參數來返回一個域。如果域不存在或者不可訪問,它會拋出一個異常。


這是一個使用這些功能的類:

String stringer = "this is a String called stringer";

	Class<? extends String> stringGetClass = stringer.getClass();

	Class<String> stringclass = String.class;

	Field[] fields = stringclass.getDeclaredFields();

	for( Field field : fields )
	{
	System.out.println( "*************************" );
	System.out.println( "Name: " + field.getName() );
	System.out.println( "Type: " + field.getType() );

	// values
	if( field.isAccessible() )
	{
		System.out.println( "Get: " + field.get( stringer ) );
		// depending on the type we can access the fields using these methods
		// System.out.println( "Get boolean: " + field.getBoolean( stringer ) );
		// System.out.println( "Get short: " + field.getShort( stringer ) );
		// ...
	}
	System.out.println( "Modifiers:" + field.getModifiers() );
	System.out.println( "isAccesible: " + field.isAccessible() );

	}

	// stringclass.getField( "hashCode" );//exception

	Field fieldHashCode = stringclass.getDeclaredField( "hash" );// all fields can be
															 // accessed this way

	// fieldHashCode.get( stringer ); // this produces an java.lang.IllegalAccessException

	// we change the visibility
	fieldHashCode.setAccessible( true );

	// and we can access it
	Object value = fieldHashCode.get( stringer );
	int valueInt = fieldHashCode.getInt( stringer );
	System.out.println( value );
	System.out.println( valueInt );


在這個上面的片段中你能看到,Field包含幾個方法獲取給定域的值,像get()或者指定類型的getInt()。我們也能看到在代碼中我們能改變一個給定域的可見性通過使用方法setAccessible()。


這也不總是可能的,並且在特定情況和環境下也許被保護。然後通過反射允許我們來做一個私有域訪問並且訪問它的值和屬性。這在測試框架像Mockito或者PowerMock中時非常有用的。


輸出或者程序將是:

    

*************************
	Name: value
	Type: class [C
	Modifiers:18
	isAccesible: false
	*************************
	Name: hash
	Type: int
	Modifiers:2
	isAccesible: false
	*************************
	Name: serialVersionUID
	Type: long
	Modifiers:26
	isAccesible: false
	*************************
	Name: serialPersistentFields
	Type: class [Ljava.io.ObjectStreamField;
	Modifiers:26
	isAccesible: false
	*************************
	Name: CASE_INSENSITIVE_ORDER
	Type: interface java.util.Comparator
	Modifiers:25
	isAccesible: false
	0
	0


10.方法


爲了獲取給定類的所有可見方法,我們能做下面的事情:

Class<String> stringclass = String.class;

Method[] methods = stringclass.getMethods();


使用方法java.lang.Class.getMethods()給定類所有可見或者可訪問的方法被獲取。我們也能獲取一個指定方法使用它的名字和它期望接收的參數類型,下面一個例子:


Method methodIndexOf = stringclass.getMethod( "indexOf", String.class );


對於一個給定的方法(java.lang.reflect.Method類型的實例),我們能訪問所有它的屬性。下面的片段展示了一組它們像名字,默認值,返回類型,修改,參數,參數類型或者異常,我們也能檢查一個方法是否是可訪問的:

	// All methods for the String class
for( Method method : methods )
{
System.out.println( "****************************************************" );
System.out.println( "name: " + method.getName() );
System.out.println( "defaultValue: " + method.getDefaultValue() );

System.out.println( "generic return type: " + method.getGenericReturnType() );
System.out.println( "return type: " + method.getReturnType() );

System.out.println( "modifiers: " + method.getModifiers() );

// Parameters
Parameter[] parameters = method.getParameters();
System.out.println( parameters.length + " parameters:" );
// also method.getParameterCount() is possible
for( Parameter parameter : parameters )
{
	System.out.println( "parameter name: " + parameter.getName() );
	System.out.println( "parameter type: " + parameter.getType() );
}
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.println( parameterTypes.length + " parameters:" );
for( Class<?> parameterType : parameterTypes )
{
	System.out.println( "parameter type name: " + parameterType.getName() );
}

// Exceptions
Class<?>[] exceptionTypes = method.getExceptionTypes();
System.out.println( exceptionTypes.length + " exception types: " );
for( Class<?> exceptionType : exceptionTypes )
{
	System.out.println( "exception name " + exceptionType.getName() );
}

System.out.println( "is accesible: " + method.isAccessible() );
System.out.println( "is varArgs: " + method.isVarArgs() );

}


給指定的對象傳遞我們想傳遞的參數實例化方法也是可以的,我們應當確保參數的數量和類型是正確的:


Object indexOf = methodIndexOf.invoke( stringer, "called" );


這個最後的特性是非常有趣的,當我們想要在運行時在特別的環境執行特定方法。還有在爲動態代理創建回調句柄也是非常有用的,我們將在教程的末尾看到這個點。


11.構造器


構造器也能通過反射使用。像其它的類方法一樣他們能在運行時被獲取,一些屬性能被分析,還有檢查可訪問性,參數數量,它們的類型等。


爲了從一個類中獲取所有可見構造器,我們能做像:

// get all visible constructors
	Constructor<?>[] constructors = stringGetClass.getConstructors();


在上面的片段中,我們獲取所有可見的構造器。如果我們想得到包含私有構造器的所有構造器,我們能做像:

//all constructors
	Constructor<?>[] declaredConstructors =   stringclass.getDeclaredConstructors();


用下面的方式可以獲取生成的關於構造器的像參數,類型,名字,可見性,相關注解等信息:

	for( Constructor<?> constructor : constructors )
{
	int numberParams = constructor.getParameterCount() ;
	System.out.println( "constructor " + constructor.getName() );
	System.out.println( "number of arguments " + numberParams);
	// public, private, etc.
	int modifiersConstructor = constructor.getModifiers();
	System.out.println( "modifiers " + modifiersConstructor );
	// array of parameters, more info in the methods section
	Parameter[] parameters = constructor.getParameters();
	// annotations array, more info in the annotations section
	Annotation[] annotations = constructor.getAnnotations();
}


構造器也能被用於創建實例。爲了訪問私有或者不可見構造器,這也許是非常有用的。這應當僅僅在非常特殊的環境下才被做並且依賴於操作系統的正在運行的應用程序也許不工作了,由於在教程開始出被解釋的安全原因。


爲了使用一個特定構造器創建一個類的實例,我們能做下面的事情:

// can be used to create new instances (no params in this case)
	String danibuizaString = (String)constructor.newInstance(  );


在必須考慮參數數量和它們的類型上,應當匹配構造器實例。還有構造器的可訪問性必須設置爲true爲了調用它(如果它不是可訪問的)。當我們操作給定類的方法和域的時候這可能用相同的方式被做。


12.Getters和Setters(我這裏就叫讀取器和設置器吧)


讀取器和設置器與其它類在類內部的方法是不同的。主要的不同是他們是一個標準的方式去訪問私有域。


這是兩個描述:

讀取器被用來取得一個類內部的私有域的值。它的名字以get開始,並且以屬性名的駝峯式結尾。他們不獲取任何參數並且他們返回相同類型的屬性。他們是公有的。

設置器被用於修改一個類內部的私有域的值。它的名字以set開始,並且以屬性名的駝峯式結尾。它們接收一個相同屬性類型的參數,而且他們不會返回任何值,他們是公共的。


例如,對於下面的私有屬性private int count;我們能設置getter和setter方法:

public int getCount(){
		return this.count;
	}

	public void setCount(int count){
		this.count = count;
	}


下面那些標準我們能使用反射在運行時訪問(讀和修改)一個類通過getter和setter暴露的所有私有屬性。這個機制被用於幾個唄知道的庫,像Spring Framework或者Hibernate,他們希望類使用這種方式暴露它們的屬性。


這是一個如何使用反射使用getters和setters的例子:

Car car = new Car( "vw touran", "2010", "12000" );

Method[] methods = car.getClass().getDeclaredMethods();

// all getters, original values
for( Method method : methods )
{
	if( method.getName().startsWith( "get" ) )
	{
		System.out.println( method.invoke( car ) );
	}
}

// setting values
for( Method method : methods )
{

	if( method.getName().startsWith( "set" ) )
	{
		method.invoke( car, "destroyed" );
	}
}

// get new values
for( Method method : methods )
{
	if( method.getName().startsWith( "get" ) )
	{
		System.out.println( method.invoke( car ) );
	}
}


我們的類Car是下面的樣子:

public class Car
{

	private String name;
	private Object price;
	private Object year;

	public Car( String name, String year, String price )
	{
	this.name = name;
	this.price = price;
	this.year = year;
	}

	public String getName()
	{
	return name;
	}

	public void setName( String name )
	{
	this.name = name;
	}

	public Object getPrice()
	{
	return price;
	}

	public void setPrice( Object price )
	{
	this.price = price;
	}

	public Object getYear()
	{
	return year;
	}

	public void setYear( Object year )
	{
	this.year = year;
	}

}


輸出會是:

vw touran
2010
12000
destroyed
destroyed
destroyed


13.靜態元素


靜態類,方法和域行爲與實例完全不同。主要原因是他們不需要在被調用以前被實例化或者被創建。他們能在沒有初始化以前被使用。


這個事實是改變每件事:在沒有初始化它的容器類情況下靜態方法被調用,靜態類的域是有狀態的(因此線程安全),創建單例和工廠靜態元素是非常有用的...總體來說,在java中靜態元素是非常重要的機制。


在這一章我們將展示關於反射在靜態和實例元素之間主要的不同:怎麼樣在運行時創建靜態元素並且怎麼樣調用它們。


例如,下面的靜態內部類:

public class StaticReflection
{

   	static class StaticExample
   	{
       int counter;
   	}
...


爲了獲取靜態內部類我們有下面的建議:

// 1 access static class
System.out.println( "directly " + StaticExample.class.getName() );
//2 using for name directly throws an exception
Class<?> forname = Class.forName("com.danibuiza.javacodegeeks.reflection.StaticReflection.StaticExample" );
//3 using $ would work but is not that nice    
Class<?> forname = Class.forName("com.danibuiza.javacodegeeks.reflection.StaticReflection$StaticExample" );
// 4 another way iterating through all classes declared inside this class
Class<?>[] classes = StaticReflection.class.getDeclaredClasses();
for( Class<?> class1 : classes )
{
	System.out.println( "iterating through declared classes " + class1.getName() );
}


主要的不同是類被包含在另一個類中;除了使用內聯類,反射也做不了什麼事情。


爲了從一個類中得到靜態方法,跟訪問實例是沒有什麼不同的(這個也應用與域):

// access static methods in the same way as instance ones
Method mathMethod = Math.class.getDeclaredMethod( "round", double.class );


爲了調用靜態方法或者域,我們不需要創建或者指定類實例,因爲方法(或者域)屬於類自己,而不是單個的實例:

// methods: object instance passed can be null since method is static
Object result = mathMethod.invoke( null, new Double( 12.4 ) );

// static field access, instance can be null
Field counterField = Counter.class.getDeclaredField( "counter" );
System.out.println( counterField.get( null ) );


14.數組


這個類java.lang.reflect.Array爲句柄arrays提供幾個功能;它包含多個靜態反射的方法:

java.lang.reflect.Array.newInstance(Class,int):創建一個實例,第一個參數是數組類型,第二個參數是數組長度。它跟java.lang.Class的名字是相似的,除了它包含參數允許程序員設置數組類型和它的長度。

java.lang.reflect.Array.set(Object, int, Object):設置給定數組的元素(傳遞索引)用object作爲傳遞參數。

java.lang.reflect.Array.getLength(Object):返回數組的整數長度

java.lang.reflect.Array.get(Object, int):返回指定數組下標的元素。返回一個對象

java.lang.reflect.Array.getInt(Object, int):與原始類型int相同的方法。返回一個整數。那些方法對所有的原始類型是有效的。


這是一個我們如何使用所有那些方法的例子:

// using the Array class it is possible to create new arrays passing the type and the length via reflection
String[] strArrayOne = (String[])Array.newInstance( String.class, 10 );

// it contains utility methods for setting values
Array.set( strArrayOne, 0, "member0" );
Array.set( strArrayOne, 1, "member1" );
Array.set( strArrayOne, 9, "member9" );

// and for getting values as well
System.out.println( "strArrayOne[0] : " + Array.get( strArrayOne, 0 ) );
System.out.println( "strArrayOne[1] : " + Array.get( strArrayOne, 1 ) );
System.out.println( "strArrayOne[3] (not initialized) : " + Array.get( strArrayOne, 3 ) );
System.out.println( "strArrayOne[9] : " + Array.get( strArrayOne, 9 ) );

// also methods to retrieve the lenght of the array
System.out.println( "lenght strArrayOne: " + Array.getLength( strArrayOne ) );

// primitive types work as well
int[] intArrayOne = (int[])Array.newInstance( int.class, 10 );

Array.set( intArrayOne, 0, 1 );
Array.set( intArrayOne, 1, 2 );
Array.set( intArrayOne, 9, 10 );

// and specific getters and setters for primitive types
for( int i = 0; i < Array.getLength( intArrayOne ); ++i )
{
	System.out.println( "intArrayOne[" + i + "] : " + Array.getInt( intArrayOne, i ) );
}


// retrieve the class from an instance
Class<String[]> stringArrayClassUsingInstance = String[].class;
System.out.println( "stringArrayClassUsingInstance is array: " + stringArrayClassUsingInstance.isArray() );

// using class for name and passing [I
Class<?> intArrayUsingClassForName = Class.forName( "[I" );
System.out.println( "intArrayUsingClassForName is array: " + intArrayUsingClassForName.isArray() );

// or [Ljava.lang.String
Class<?> stringArrayClassUsingClassForName = Class.forName( "[Ljava.lang.String;" );
System.out.println( "stringArrayClassUsingClassForName is array: "
	+ stringArrayClassUsingClassForName.isArray() );

// this has no much sense in my opinion since we are creating an array at runtime and
// getting the class to create a new one...
Class<? extends Object> stringArrayClassUsingDoubleLoop = Array.newInstance( String.class, 0 ).getClass();
System.out.println( "stringArrayClassUsingClassForName is array: " + stringArrayClassUsingDoubleLoop.isArray() );

  

上面程序的輸出:

stringArrayClassUsingInstance is array: true
intArrayUsingClassForName is array: true
stringArrayClassUsingClassForName is array: true
stringArrayClassUsingClassForName is array: true


15.集合


集合沒有許多可標記的明確的與反射相關的特點。這是一個我們如何能持有集合基礎元素的例子。正如已經說的,對任何其它java類型有許多不同。


下面的方法打印一個集合的所有元素的所有類名,如果會提前檢查傳遞的元素是否是一個集合實例:

private static void reflectionCollections( Object ref )
{
//check is collection	
	if( ref instanceof Collection )
	{
		System.out.println( "A collection:  " + ref.getClass().getName() );
		@SuppressWarnings( "rawtypes" )
		// not nice
		Iterator items = ( (Collection)ref ).iterator();
		while( items != null && items.hasNext() )
		{
			Object item = items.next();
			System.out.println( "Element of the collection:  " + item.getClass().getName() );
		}
	}
	else
	{
		System.out.println( "Not a collection:  " + ref.getClass().getName() );
	}
}


在代碼展示中,反射僅僅被用於檢查被傳遞的實例類型參數並且獲取元素的類名在集合內部。我們能調用這個方法在下面的方法中使用不同的元素(一些集合基於,一些不是):

Map<String, String> map = new HashMap<String, String>();
map.put( "1", "a" );
map.put( "2", "b" );
map.put( "3", "c" );
map.put( "4", "d" );

reflectionCollections( map );
reflectionCollections( map.keySet() );
reflectionCollections( map.values() );

List<String> list = new ArrayList<String>();
list.add( "10" );
list.add( "20" );
list.add( "30" );
list.add( "40" );

reflectionCollections( list );
reflectionCollections( "this is an string" );


並且我們將得到下面的輸出:

Not a collection:  java.util.HashMap
A collection:  java.util.HashMap$KeySet
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
A collection:  java.util.HashMap$Values
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
A collection:  java.util.ArrayList
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Not a collection:  java.lang.String


16.註解


一個類,包,方法,域的所有註解使用反射都能被獲得。註解在運行時對能被獲取的每個元素和它們的值都能被評估。下面的片段展示了怎麼樣從給定類中獲取所有註解和怎麼樣打印它們的屬性和值:

Class<ReflectableClass> object = ReflectableClass.class;
// Retrieve all annotations from the class
Annotation[] annotations = object.getAnnotations();
for( Annotation annotation : annotations )
{
System.out.println( annotation );
}


下面的例子解釋了怎麼樣檢查是否一個元素(域,方法,類)用指定的註解標記了:

// Checks if an annotation is present
if( object.isAnnotationPresent( Reflectable.class ) )
{
	// Gets the desired annotation
	Annotation annotation = object.getAnnotation( Reflectable.class );

	System.out.println( annotation + " present in class " + 			object.getClass() );// java.lang.class
	System.out.println( annotation + " present in class " + 	object.getTypeName() );// 	com.danibuiza.javacodegeeks.reflection.ReflectableClass

}


那些片段也許可應用於方法,域以及所有能被註解的元素。


你能找到一個非常好的文章和可擴展的教程關於java註解的域反射相關的幾個例子:

http://www.javacodegeeks.com/2014/11/java-annotations-tutorial.html.


17.泛型


泛型在java5中被介紹並且從那時起,它就是一個非常重要的特性寶珠維護代碼清理並且更有用的。參數化元素與其它java中的元素是不同的,因此在這個教程中解釋的所有話題也用於那些元素。


java也包含指定反射機制去持有泛型。

在運行時會檢查是否一個指定的元素(類,方法,域)被參數化了。

也可以使用反射獲取參數類型。


這是我們該怎麼做的例子:

Method getInternalListMethod = GenericsClass.class.getMethod( "getInternalList", null );

// we get the return type
Type getInternalListMethodGenericReturnType = getInternalListMethod.getGenericReturnType();

// we can check if the return type is parameterized (using ParameterizedType)
if( getInternalListMethodGenericReturnType instanceof ParameterizedType )
{
	ParameterizedType parameterizedType = (ParameterizedType)getInternalListMethodGenericReturnType;
	// we get the type of the arguments for the parameterized type
	Type[] typeArguments = parameterizedType.getActualTypeArguments();
	for( Type typeArgument : typeArguments )
	{
		// warning not nice
		// we can work with that now
		Class typeClass = (Class)typeArgument;
		System.out.println( "typeArgument = " + typeArgument );
		System.out.println( "typeClass = " + typeClass );
	}
}

在這個代碼列表中我們能看到域反射相關的主類和泛型是java.lang.reflect.ParameterizedType並且其中它最重要的方法是java.lang.reflect.ParameterizedType.getActualTypeArguments()。


PS:後面還有類載入器和動態代理以及java8的新特性描述,不再翻譯,如果感興趣,可以點擊原文鏈接。翻譯了三天,雖然翻譯的不怎麼好,但是給個鼓勵點個贊吧


原文:http://www.javacodegeeks.com/2014/11/java-reflection-api-tutorial.html

發佈了108 篇原創文章 · 獲贊 22 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章