您的位置:首页 > 编程语言 > Java开发

Java反射机制

2016-01-23 23:33 489 查看
前言

本文是我整理的Java反射的一些知识,其中大部分内容是翻译http://tutorials.jenkov.com/java-reflection/index.html的。

1.Java反射简介

J***A反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

Java反射机制是Java语言被视为“准动态”语言的关键性质。Java反射机制的核心就是允许在运行时通过JavaReflectionAPIs来取得已知名字的class类的内部信息(包括其modifiers(诸如public,static等等)、superclass(例如Object)、实现interfaces(例如Serializable),也包括fields和methods的所有信息),动态地生成此类,并调用其方法或修改其域(甚至是本身声明为private的域或方法)。

Java反射机制主要提供了以下功能:在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。

2.Class对象

Class对象是Java反射的基础,它包含了与类相关的信息,事实上,Class对象就是用来创建类的所有对象的。Class对象是java.lang.Class<T>这个类生成的对象,其中类型参数T表示由此
Class对象建模的类的类型。例如,String.class的类型是Class<String>;如果将被建模的类未知,则使用Class<?>。以下是
JavaAPI
的描述:


Class
类的实例表示正在运行的Java应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为Class对象的一个类,所有具有相同元素类型和维数的数组都共享该
Class
对象。基本的Java类型(
boolean
byte
char
short
int
long
float
double
)和关键字
void
也表示为
Class
对象。

Class
没有公共构造方法。
Class
对象是在加载类时由Java虚拟机以及通过调用类加载器中的
defineClass
方法自动构造的。

实际上,每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中)。如果我们想生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用类加载器子系统,类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件,并将其载入,一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

获取Class对象有三种方式:

(1)通过实例变量的getClass()方法。例如:

Classc1=newString("abc").getClass();

(2)通过Class类的静态方法——forName()来实现,例如:

Classclass=Class.forName(className);

注意:当使用Class.forName()方法时,你必须提供完全限定类名。即类名要包括所有包

。例如,如果MyObject是位于包com.jenkov.myapp下,那么类的完全限定名称是com.jenkov.myapp.MyObject。如果在运行时类路径上找不到类,Class.forName()方法会抛出一个ClassNotFoundException。

(3)使用类字面常量或TYPE字段,例如:

ClassmyObjectClass=MyObject.class;(类字面常量不仅可以应用于普通的类,也可以应用

于接口、数组以及基本数据类型),这种方式不仅更简单,而且更安全,因为它在编译时就会受到检查,并且根除了对forName方法的调用,所以也更高效,建议使用“.class”的形式

Classc=Integer.TYPE;TYPE是基本数据类型的包装类型的一个标准字段,它是一

个引用,指向对应的基本数据类型的Class对象),附表如下,两边等价:
boolean.class
Boolean.TYPE
char.class
Character.TYPE
byte.class
Byte.TYPE
short.class
Short.TYPE
int.class
Integer.TYPE
long.class
Long.TYPE
float.class
Float.TYPE
double.class
Double.TYPE
void.class
Void.TYPE

3.类

使用Java反射,你可以在运行时检查Java类。检查类是使用反射时经常做的第一件事情。从类中可以获取以下信息:

(1)类名

(2)类修饰符(public,private,synchronized等)

(3)包信息

(4)父类

(5)实现的接口

(6)构造函数

(7)方法

(8)字段

(9)注解

3.1类名

从Class对象中可以获取两个不同的类名。完全限定类名(包括包名)可以使用getName()或getCanonicalName()方法获取,例如:

ClassaClass=MyObject.class;

StringclassName=aClass.getName();

StringclassName1=aClass.getCanonicalName();


如果想要获取不含包名的类名可以使用
getSimpleName()
方法,如下:

ClassaClass=MyObject.class;

StringsimpleClassName=aClass.getSimpleName();


3.2修饰符

使用Class对象可以获取一个类的修饰符.类的修饰符即关键字"public","private","static"等.如下:

ClassaClass=MyObject.class;

intmodifiers=aClass.getModifiers();


修饰符被包装进一个int
内,
每一个修饰符都是一个标志位(置位或清零)。可以使用
java.lang.reflect.Modifier
中的以下方法来检验修饰符:

Modifier.isAbstract(intmodifiers)

Modifier.isFinal(intmodifiers)

Modifier.isInterface(intmodifiers)

Modifier.isNative(intmodifiers)

Modifier.isPrivate(intmodifiers)

Modifier.isProtected(intmodifiers)

Modifier.isPublic(intmodifiers)

Modifier.isStatic(intmodifiers)

Modifier.isStrict(intmodifiers)

Modifier.isSynchronized(intmodifiers)

Modifier.isTransient(intmodifiers)

Modifier.isVolatile(intmodifiers)


3.3包信息

使用Class对象可以获取包信息,如下:

ClassaClass=MyObject.class;

Packagepackage=aClass.getPackage();

StringpackageName=package.getname();


从Package对象中你可以访问诸如名字等包信息。您还可以访问类路径上这个包位于JAR文件中Manifest这个文件中指定的信息。例如,你可以在Manifest文件中指定包的版本号。可以在java.lang.Package中了解更多包类信息。

3.4父类

通过
Class
对象可以获取类的父类
,如下:

ClassaClass=MyObject.class;

Classsuperclass=aClass.getSuperclass();


父类的Class对象和其它Class对象一样是一个Class对象,可以继续使用反射.

3.5实现的接口

通过给定的类可以获取这个类所实现的接口列表,如下:

ClassaClass=MyObject.class;

Class[]interfaces=aClass.getInterfaces();


一个类可以实现多个接口。因此返回一个Class数组。在Java反射机制中,接口也由Class对象表示。

注意:只有给定类声明实现的接口才会返回。例如,如果类A的父类B实现了一个接口C,但类A并没有声明它也实现了C,那么C不会被返回到数组中。即使类A实际上实现了接口C,因为它的父类B实现了C。

为了得到一个给定的类实现接口的完整列表,需要递归访问类和其超类。

3.6构造函数

使用Class对象可以获取类的构造函数,如下:

ClassaClass=MyObject.class;

Constructor[]constructors=aClass.getConstructors();


关于构造函数更详细信息参见构造函数这节。

3.7方法

使用Class对象可以获取类的方法,如下:

ClassaClass=MyObject.class;

Method[]methods=aClass.getMethods();


关于方法更详细信息参见方法这节.

3.8字段

使用Class对象可以获取类的字段(成员变量),如下:

ClassaClass=MyObject.class;

Field[]fields=aClass.getFields();


关于字段更详细信息参见字段这节.

3.9注解

使用Class对象可以获取类的注解,如下:

ClassaClass=MyObject.class;

Annotation[]annotations=aClass.getAnnotations();


关于注解更详细信息参见注解这节.

4.构造函数

使用Java反射可以在运行时检查类的构造函数并实例化对象。这是通过Java类java.lang.reflect.Constructor来实现的。以下是JavaConstrucor对象的更多细节:

(1)获取Constructor对象

(2)构造函数参数

(3)使用Constructor对象实例化对象

4.1获取Constructor对象

Constructor
类是从Class对象获取的,举例:

ClassaClass=MyObject.class;

Constructor[]constructors=aClass.getConstructors();


Constructor
数组为每一个在类中声明的
public
构造函数保存一个
Constructor
实例


如果知道要访问的构造函数确切的参数类型,可以不获取构造函数数组。本示例将返回给定类中接受一个字符串作为参数的公共构造函数。

ClassaClass=MyObject.class;//MyObject有一个参数为字符串的公共构造函数

Constructorconstructor=aClass.getConstructor(newClass[]{String.class});


如果没有匹配给定的构造函数参数,在这个例子当中是
String.class
,会抛出
NoSuchMethodException
异常.

4.2构造函数参数

可以知道给定的构造函数接受什么参数,如下:

ClassaClass=MyObject.class;//MyObject有一个参数为字符串的公共构造函数

Constructorconstructor=aClass.getConstructor(newClass[]{String.class});

Class[]parameterTypes=constructor.getParameterTypes();


4.3使用Constructor对象实例化对象

可以像这样实例化对象:

//获取使用字符串作为参数的constructor

Constructorconstructor=MyObject.class.getConstructor(String.class);

MyObjectmyObject=(MyObject)constructor.newInstance("constructor-arg1");


Constructor.newInstance()
方法使用可变长度的参数,但是在调用构造函数时必须为每一个参数提供一个准确的参量.在这个例子中构造函数接受一个字符串作为参数,所以必须要提供一个字符串。

5.字段

使用Java反射你可以在运行时检查类的字段(成员变量)并get/set它们.这是通过Java类
java.lang.reflect.Field
来完成的
.以下是Java
Field
对象更多细节:

(1)获取Field对象

(2)字段名称

(3)字段类型

(4)获取和设置字段值

5.1获取Field对象

Field
类是从
Class
对象获取的.举例:

ClassaClass=MyObject.class;

Field[]methods=aClass.getFields();


Field
数组为类中声明的每一个
public
字段
保存一个
Field
实例。

如果知道要访问的字段名称,可以这样获取它:

ClassaClass=MyObject.class

Fieldfield=aClass.getField("someField");


根据下面
MyObject
声明的
someField
字段,
以上的例子将返回
Field
实例:

publicclassMyObject{

publicStringsomeField=null;

}


如果不存在getField()方法中所给参数名字对应的字段,会抛出
NoSuchFieldException
异常
.

5.2字段名称

一旦获得一个
Field
实例,可以使用
Field.getName()
方法获取它的字段名字,
如下:

Fieldfield=...//获取field对象

StringfieldName=field.getName();


5.3字段类型

你可以使用
Field.getType()
方法确定一个字段的类型
(
String,int等
)
:

Fieldfield=aClass.getField("someField");

ObjectfieldType=field.getType();


5.4获取和设置字段值

一旦获得一个Field引用,可以使用
Field.get()
Field.set()
方法获取和设置
它的值,如下:

ClassaClass=MyObject.class

Fieldfield=aClass.getField("someField");

MyObjectobjectInstance=newMyObject();

Objectvalue=field.get(objectInstance);

field.set(objetInstance,value);


传递给get和set方法的对象实例参数应该是拥有该字段的类的一个实例。在上述的例子中使用了MyObject的一个实例,因为someField是MyObject类的一个实例成员。静态字段(publicstatic)给get和set方法传递null作为参数,而不是以上传递的objectInstance参数。

6.方法

使用Java反射你可以在运行时检查类的方法并调用它们.这是通过Java类
java.lang.reflect.Method
来实现的
.本章将会更详细的介绍Java
Method
对象.下面是本章所涵盖的主题:

(1)获取Method对象

(2)方法的参数和返回值类型

(3)使用Method对象调用方法

6.1获取Method对象

Method
类是从
Class
对象中获取的.举例:

ClassaClass=MyObject.class;

Method[]methods=aClass.getMethods();


Method
数组将为类中声明的每一个
public
的方法保存一个
Method
实例
.

如果你知道要访问方法的确切的参数类型,可以不必获取方法数组。本示例返回给定类中接受一个字符串作为参数的公共方法”doSomething”。

ClassaClass=MyObject.class;

Methodmethod=aClass.getMethod("doSomething",newClass[]{String.class});


如果没有方法匹配所给的方法名和参数,在这个例子中是
String.class
,将抛出
NoSuchMethodException
异常.

如果你想访问的方法没有参数,传递
null
作为参数类型数组,如下:

ClassaClass=MyObject.class;

Methodmethod=aClass.getMethod("doSomething",null);


6.2方法的参数和返回值类型

使用Method对象可以获取方法的参数,如下:

Methodmethod=...//获取method–如上

Class[]parameterTypes=method.getParameterTypes();


亦可以获取方法的返回值类型,如下:

Methodmethod=...//obtainmethod-seeabove

ClassreturnType=method.getReturnType();


6.3使用Method对象调用方法

可以像这样调用方法:

//getmethodthattakesaStringasargument

Methodmethod=MyObject.class.getMethod("doSomething",String.class);

ObjectreturnValue=method.invoke(null,"parameter-value1");


空参数是你想要调用该方法的对象。如果该方法是静态的,使用null,而不是一个对象实例。在这个例子中,如果doSomething(String.class)不是静态的,你需要提供有效的MyObject实例而不是null作为参数;

Method.invoke(Objecttarget,Object...parameters)
方法接受可变长度的参数,但是你在调用时必须为每一个参数提供一个准确的参量。在这个例子中,方法以字符串作为参数的,所以必须提供一个字符串。

7.get和set方法

使用Java反射可以在运行时检查类的方法并调用它们。这可以用来检测一个给定的类有哪些get和set方法。可以通过扫描一个类的所有方法并检查每个方法是否是get或set方法。

下面是一段用来找到类的get和set方法的代码:

publicstaticvoidprintGettersSetters(ClassaClass){

Method[]methods=aClass.getMethods();

for(Methodmethod:methods){

if(isGetter(method))System.out.println("getter:"+method);

if(isSetter(method))System.out.println("setter:"+method);

}

}

publicstaticbooleanisGetter(Methodmethod){

if(!method.getName().startsWith("get"))returnfalse;

if(method.getParameterTypes().length!=0)returnfalse;

if(void.class.equals(method.getReturnType())returnfalse;

returntrue;

}

publicstaticbooleanisSetter(Methodmethod){

if(!method.getName().startsWith("set"))returnfalse;

if(method.getParameterTypes().length!=1)returnfalse;

returntrue;

}

8.私有字段和方法

可以通过Java反射访问类的私有字段和方法。

8.1访问私有字段

要想访问私有字段你需要调用
Class.getDeclaredField(Stringname)
Class.getDeclaredFields()
方法.
Class.getField(Stringname)
Class.getFields()
方法仅返回public字段。下面是一个简单的示例,类有一个私有字段,代码通过反射来访问这个私有字段:

publicclassPrivateObject{

privateStringprivateString=null;

publicPrivateObject(StringprivateString){

this.privateString=privateString;

}

}


PrivateObjectprivateObject=newPrivateObject("ThePrivateValue");

FieldprivateStringField=PrivateObject.class.getDeclaredField("privateString");

privateStringField.setAccessible(true);

StringfieldValue=(String)privateStringField.get(privateObject);

System.out.println("fieldValue="+fieldValue);


代码示例输出"fieldValue=ThePrivateValue",即代码示例最开始创建的
PrivateObject
实例的私有字段
privateString
的值。

注意
PrivateObject.class.getDeclaredField("privateString")
的使用
。这个方法仅仅返回特定类声明的字段,而不包括任何父类中声明的字段。

注意粗体代码.通过调用Field.setAcessible(true)方法关闭了特定Field实例的访问检查,现在通过反射可以访问它,即使它是私有的,保护的或包范围,甚至调用者不属于这些范围。但编译器不允许使用普通的代码该字段,因为仅适用于反射。

8.2访问私有方法

想要访问私有方法需要调用
Class.getDeclaredMethod(Stringname,Class[]parameterTypes)
Class.getDeclaredMethods()
方法.
Class.getMethod(Stringname,Class[]parameterTypes)
Class.getMethods()
方法仅返回public方法。下面是一个简单的示例,类有一个私有方法,代码通过反射来访问这个私有方法:

publicclassPrivateObject{

privateStringprivateString=null;

publicPrivateObject(StringprivateString){

this.privateString=privateString;

}

privateStringgetPrivateString(){

returnthis.privateString;

}

}

PrivateObjectprivateObject=newPrivateObject("ThePrivateValue");

MethodprivateStringMethod=PrivateObject.class.getDeclaredMethod("getPrivateString",null);

privateStringMethod.setAccessible(true);

StringreturnValue=(String)privateStringMethod.invoke(privateObject,null);

System.out.println("returnValue="+returnValue);


代码示例输出"returnValue=ThePrivateValue",即代码示例最开始创建的
PrivateObject
实例调用的私有方法
getPrivateString()
的返回值。

注意
PrivateObject.class.getDeclaredMethod("privateString")
方法的使用
。这个方法仅仅返回特定类声明的方法,而不包括任何父类中声明的方法。

注意粗体代码.通过调用
Method
.setAcessible(true)方法关闭了特定
Method
实例的访问检查。现在通过反射可以访问它,即使它是私有的,保护的或包范围,甚至调用者不属于这些范围。但编译器不允许使用普通的代码访问该方法,因为仅适用于反射。

9.Array

在Java反射中使用数组有时候有点棘手,特别是如果你想获得某种类型的数组的Class对象,如int[]等。本节将讨论如何通过Java反射创建数组和Class对象。以下是本章涵盖的主题:

(1)java.lang.reflect.Array

(2)创建数组

(3)访问数组

(4)获取数组的Class对象

(5)获取数组的组件类型

9.1java.lang.reflect.Array

通过Java反射机制使用数组是由thejava.lang.reflect.Array类完成的。不要把这个类和Java集合框架中的java.util.Arrays类相混淆,java.util.Arrays类包含数组排序,将它们转换为集合等公共方法。

9.2创建数组

通过Java反射机制创建数组是由
java.lang.reflect.Array
类来完成的,如下:

int[]intArray=(int[])Array.newInstance(int.class,3);


这行代码创建了一个int数组。
Array.newInstance()
方法的第一个参数告诉我们数组中的元素类型。
第二个参数声明了数组需要为多少个元素分配空间。

9.3访问数组

使用Java反射机制可以访问数组的元素。这是通过
Array.get(...)
Array.set(...)
方法实现的,如下:

int[]intArray=(int[])Array.newInstance(int.class,3);

Array.set(intArray,0,123);

Array.set(intArray,1,456);

Array.set(intArray,2,789);

System.out.println("intArray[0]="+Array.get(intArray,0));

System.out.println("intArray[1]="+Array.get(intArray,1));

System.out.println("intArray[2]="+Array.get(intArray,2));


这段代码输出:

intArray[0]=123

intArray[1]=456

intArray[2]=789


9.4获取数组的Class对象

获取数组的Class对象可以使用无反射的代码,如下:

ClassstringArrayClass=String[].class;


使用
Class.forName()
方法
并不易懂。例如,你可以像下面这样获取int数组的Class对象:

ClassintArray=Class.forName("[I");


字母
I
在JVM中代表一个。左边的
[
表示它是一个int数组的class。这对其它的基本类型也有效。

对于对象,你需要使用一个稍微不同的记号:

ClassstringArrayClass=Class.forName("[Ljava.lang.String;");


注意到左边的”
[L
”和右边的”
;
”之间的类名。它代表对象数组的给定类型。

另外一个需要注意的是你不能够使用
Class.forName()
方法获取基本类型的
Class
对象
。下面两个例子都会抛出
ClassNotFoundException
异常
:

ClassintClass1=Class.forName("I");

ClassintClass2=Class.forName("int");


可以像下面这样获取基本数据类型和对象的Class名:

publicClassgetClass(StringclassName){

if("int".equals(className))returnint.class;

if("long".equals(className))returnlong.class;

...

returnClass.forName(className);

}


一旦你获得某个类型的Class对象,有一个简单的方法来获得该类型的数组的Class对象。解决方案是创建所需类型的空数组并从这个空数组获取Class对象,如下:

ClasstheClass=getClass(theClassName);

ClassstringArrayClass=Array.newInstance(theClass,0).getClass();


这提供了一个单一的、统一的方法来访问任何类型的数组的Class对象。

要确保
Class
对象是一个数组,可以使用
Class.isArray()
方法来校验:

ClassstringArrayClass=Array.newInstance(String.class,0).getClass();

System.out.println("isarray:"+stringArrayClass.isArray());


9.5获取数组的组件类型

一旦获取到某个数组的Class对象可以使用
Class.getComponentType()
方法来获取它的组件类型。组件类型就是数组中元素的类型。例如,int数组的组件类型是
int.classClass
对象.
String[]
数组的组件类型是
java.lang.String
Class
对象。

下面是获取数组的组件类型的示例:

String[]strings=newString[3];

ClassstringArrayClass=strings.getClass();

ClassstringArrayComponentType=stringArrayClass.getComponentType();

System.out.println(stringArrayComponentType);


输出"classjava.lang.String"即为String数组的组件类型.

10.注解

使用Java反射你可以在运行时访问Java类中的注解.下面是本章涵盖的主题:

(1)什么是Java注解?

(2)类注解

(3)方法注解

(4)参数注解

(5)字段注解

10.1什么是Java注解?

注解是Java5的一个新功能。注解是一种可以在Java代码中插入的注释或元数据。这些注解可以在编译时由预编译工具进行处理,也可以在运行时通过Java反射机制来处理。下面是一个类注解的示例:

@MyAnnotation(name="someName",value="HelloWorld")

publicclassTheClass{

}


TheClass
类的上面有
@MyAnnotation
注解.注解像接口那样定义。下面是
MyAnnotation
注解
的定义:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public@interfaceMyAnnotation{

publicStringname();

publicStringvalue();

}


interface
前面的”
@
”标志它是一个注解。一旦你定义了那个注解你就可以在代码中使用它,就像上面的例子那样。

在注解中定义的两个指令
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
表明注解是如何使用的

@Retention(RetentionPolicy.RUNTIME)
表明在运行时可以使用反射来访问这个注解
。如果没有设置这个指令,在运行时这个注解将不会被保存,因此通过反射访问不到。

@Target(ElementType.TYPE)
表明注解仅能用于类、接口(包括注释类型)或枚举声明等类型上。你也可以指定
METHOD
FIELD
,或者@Target什么都不指定,这样它可以用在任何程序元素上。

10.2类注解

可以在运行时获取类、方法或字段的注解。下面是获取类注解的示例:

ClassaClass=TheClass.class;

Annotation[]annotations=aClass.getAnnotations();

for(Annotationannotation:annotations){

if(annotationinstanceofMyAnnotation){

MyAnnotationmyAnnotation=(MyAnnotation)annotation;

System.out.println("name:"+myAnnotation.name());

System.out.println("value:"+myAnnotation.value());

}

}


也可以获取指定的类注解,如下:

ClassaClass=TheClass.class;

Annotationannotation=aClass.getAnnotation(MyAnnotation.class);


if(annotationinstanceofMyAnnotation){

MyAnnotationmyAnnotation=(MyAnnotation)annotation;

System.out.println("name:"+myAnnotation.name());

System.out.println("value:"+myAnnotation.value());

}


10.3方法注解

下面是方法注解的示例:

publicclassTheClass{

@MyAnnotation(name="someName",value="HelloWorld")

publicvoiddoSomething(){}

}


获取方法的所有注解,如下:

Methodmethod=...//obtainmethodobject

Annotation[]annotations=method.getDeclaredAnnotations();

for(Annotationannotation:annotations){

if(annotationinstanceofMyAnnotation){

MyAnnotationmyAnnotation=(MyAnnotation)annotation;

System.out.println("name:"+myAnnotation.name());

System.out.println("value:"+myAnnotation.value());

}

}


获取方法的特定注解:

Methodmethod=...//obtainmethodobject

Annotationannotation=method.getAnnotation(MyAnnotation.class);

if(annotationinstanceofMyAnnotation){

MyAnnotationmyAnnotation=(MyAnnotation)annotation;

System.out.println("name:"+myAnnotation.name());

System.out.println("value:"+myAnnotation.value());

}


10.4参数注解

也可以给方法声明的参数添加注解,如下:

publicclassTheClass{

publicstaticvoiddoSomethingElse(

@MyAnnotation(name="aName",value="aValue")Stringparameter){

}

}


从Method对象获取参数注解:

Methodmethod=...//obtainmethodobject

Annotation[][]parameterAnnotations=method.getParameterAnnotations();

Class[]parameterTypes=method.getParameterTypes();

inti=0;

for(Annotation[]annotations:parameterAnnotations){

ClassparameterType=parameterTypes[i++];

for(Annotationannotation:annotations){

if(annotationinstanceofMyAnnotation){

MyAnnotationmyAnnotation=(MyAnnotation)annotation;

System.out.println("param:"+parameterType.getName());

System.out.println("name:"+myAnnotation.name());

System.out.println("value:"+myAnnotation.value());

}

}

}


注意
Method.getParameterAnnotations()
方法返回的是二维的
Annotation
数组,每个方法参数都有一个一维的
Annotation
数组

10.5字段注解

下面是字段注解示例:

publicclassTheClass{

@MyAnnotation(name="someName",value="HelloWorld")

publicStringmyField=null;

}


获取字段的所有注解,如下:

Fieldfield=...//obtainfieldobject

Annotation[]annotations=field.getDeclaredAnnotations();

for(Annotationannotation:annotations){

if(annotationinstanceofMyAnnotation){

MyAnnotationmyAnnotation=(MyAnnotation)annotation;

System.out.println("name:"+myAnnotation.name());

System.out.println("value:"+myAnnotation.value());

}

}


获取指定字段的注解,如下:

Fieldfield=...//obtainmethodobject

Annotationannotation=field.getAnnotation(MyAnnotation.class);

if(annotationinstanceofMyAnnotation){

MyAnnotationmyAnnotation=(MyAnnotation)annotation;

System.out.println("name:"+myAnnotation.name());

System.out.println("value:"+myAnnotation.value());

}


11.泛型

经常在文章和论坛里读到说所有Java泛型信息在编译的时候被擦除了所以在运行时访问不到任何泛型信息。这并不完全正确。在极少数的情况下,在运行时是可以访问泛型信息的。这些情况实际上涵盖一些我们需要的Java泛型信息。本章将解释这些情况。下面是本章涵盖的主题:

(1)泛型反射的经验法则

(2)泛型方法的返回值类型

(3)泛型方法的参数类型

(4)泛型字段类型

11.1泛型反射的经验法则

使用Java的泛型通常分为两种不同的情况:

(1)声明一个可参数化的类/接口。

(2)使用参数化的类。

当写一个类或接口时,可以指定它应该是可参数化的。就像
java.util.List
接口那样,可以参数化
java.util.List
来创建一个String列表而不是创建Object列表。

java.util.List在运行时会检查参数化的类型,但是没有办法知道参数化的是什么类型。这是因为在相同的应用程序中类型可以被参数化为所有的类型。但是,当你检查方法或字段所声明使用的参数化类型,你可以在运行时看到可参数化的类型被参数化为什么类型。总之,你不能在运行时看到类型本身被参数化为什么类型,但你可以在使用和参数化它的字段和方法中看到它。

11.2泛型方法的返回值类型

如果你获取到一个
java.lang.reflect.Method
对象,可以获取它的返回值类型信息。下面是一个示例,一个类有一个有参数化返回值类型的方法:

publicclassMyClass{

protectedList<String>stringList=...;

publicList<String>getStringList(){

returnthis.stringList;

}

}


在这个类中可以获取
getStringList()
方法的泛型返回值类型
。换句话说,可以探测到
getStringList()
返回的是
List<String>
而不仅是一个
List
。如下:

Methodmethod=MyClass.class.getMethod("getStringList",null);

TypereturnType=method.getGenericReturnType();

if(returnTypeinstanceofParameterizedType){

ParameterizedTypetype=(ParameterizedType)returnType;

Type[]typeArguments=type.getActualTypeArguments();

for(TypetypeArgument:typeArguments){

ClasstypeArgClass=(Class)typeArgument;

System.out.println("typeArgClass="+typeArgClass);

}

}


代码输出"typeArgClass=classjava.lang.String"。

Type[]
数组
typeArguments
包含一项-一个代表类
java.lang.String
Class
实例
Class
实现了
Type
接口。

11.3泛型方法的参数类型

通过Java反射可以在运行时访问参数类型的泛型类型。下面的示例中,类有一个使用参数化的List作为参数的方法:

publicclassMyClass{

protectedList<String>stringList=...;


publicvoidsetStringList(List<String>list){

this.stringList=list;

}

}


可以访问方法参数的泛型参数类型,如下:

Methodmethod=Myclass.class.getMethod("setStringList",List.class);

Type[]genericParameterTypes=method.getGenericParameterTypes();

for(TypegenericParameterType:genericParameterTypes){

if(genericParameterTypeinstanceofParameterizedType){

ParameterizedTypeaType=(ParameterizedType)genericParameterType;

Type[]parameterArgTypes=aType.getActualTypeArguments();

for(TypeparameterArgType:parameterArgTypes){

ClassparameterArgClass=(Class)parameterArgType;

System.out.println("parameterArgClass="+parameterArgClass);

}

}

}


代码输出"parameterArgType=classjava.lang.String"。

Type[]
数组
parameterArgTypes
包含一项-一个代表类
java.lang.String
Class
实例
Class
实现了
Type
接口。

11.4泛型字段类型

可以访问public字段的泛型类型。字段即类的成员变量-静态的或实例变量。下面是一个例子,类有一个实例变量
stringList
.

publicclassMyClass{

publicList<String>stringList=...;

}

Fieldfield=MyClass.class.getField("stringList");

TypegenericFieldType=field.getGenericType();

if(genericFieldTypeinstanceofParameterizedType){

ParameterizedTypeaType=(ParameterizedType)genericFieldType;

Type[]fieldArgTypes=aType.getActualTypeArguments();

for(TypefieldArgType:fieldArgTypes){

ClassfieldArgClass=(Class)fieldArgType;

System.out.println("fieldArgClass="+fieldArgClass);

}

}


代码将输出"fieldArgClass=classjava.lang.String"。

Type
数组
fieldArgTypes
包含一项–一个代表类
java.lang.String
Class
实例
Class
实现了
Type
接口。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: