Java反射

通过反射查看类信息

通过反射获得一个类

每个类被加载之后,系统会为该类生成对应的Class对象。Java程序中获得Class对象通常有以下三种方式。

  1. 使用Class类的forName(String className)静态方法。该方法需要传入该类的全限定类名(包括包的信息)
  2. 调用某个类的class属性来获得该类对应的Class对象。
  3. 调用某个对象的getClass()方法。该方法是Object类中的方法,所以所有Java对象都可以使用该方法。

建议使用第2种方法去获得一个类,代码更安全。

  • 程序在编译阶段就可以检查需要访问的Class对象是否存在。(使用第1种方法需要捕获ClassNotFoundException异常)
  • 程序性能更好,因为这种方式无须调用方法,所以性能更好。

从Class中获取信息

反射可以从类中直接拿到该类的构造器,属性,方法等信息。

获取构造器

方法名 描述
Connstructor< T> getConstructor( Class<?>… parameterTypes) 返回此 Class 对象对应类的、 带指定形参列表的 public 构造器。
Constructor<?>[] getConstructors(): 返回所有public的构造器
Constructor< T> getDeclaredConstructor( Class<?>… parameterTypes) 返回此 Class 对象对应类的、带指定形参列表的构造器,与构造器的访问权限无关。
Constructor<?>[] getDeclaredConstructors() 返回此 Class 对象对应类的所有构造器,与构造器的访问权限无关。

获取方法

方法名 描述
Method getMethod( String name, Class<?>… parameterTypes) 返回此 Class 对象对应类的、带指定形参列表的 public 方法。
Method[] getMethods() 返回此 Class 对象所表示的类的所有 public 方法。
Method getDeclaredMethod( String name, Class<?>… parameterTypes) 返回此 Class 对象对应类的、带指定形参列表的方法, 与方法的访问权限无关。
Method[] getDeclaredMethods() 返回此 Class 对象对应类的全部方法,与方法的访问权限无关。

获得成员变量

方法名 描述
Field getField( String name) 返回此 Class 对象对应类的、指定名称的 public 成员变量。
Field[] getFields(): 返回此 Class 对象对应类的所有 public 成员变量。
Field getDeclaredField( String name): 返回此 Class 对象对应类的、指定名称的成员变量,与成员变量的访问权限无关。
Field[] getDeclaredFields() 返回此 Class 对象对应类的全部成员变量,与成员变量的访问权限无关。

获得注解

方法名 描述
< A extends Annotation> A getAnnotation( Class< A> annotationClass) 尝试获取该 Class 对象对应类上存在的、指定类型的 Annotation;如果该类型的注解不存在,则返回 null。
< A extends Annotation> A getDeclaredAnnotation( Class< A> annotationClass) 这是 Java 8 新增的方法,该方法尝试获取直接修饰该 Class 对象对应类的、指定类型的 Annotation;如果该类型的注解不存在 ,则返回 null。
Annotation[] getAnnotations(): 返回修饰该 Class 对象对应类上存在的所有 Annotation。
Annotation[] getDeclaredAnnotations() 返回直接修饰该 Class 对应类的所有 Annotation。
< A extends Annotation> A[] getAnnotationsByType( Class< A> annotationClass) 该方法的功能与前面介绍的 getAnnotation() 方法基本相似。但由于 Java 8 增加了重复注解功能,因此需要使用该方法获取修饰该类的、指定类型的多个 Annotation。
< A extends Annotation> A[] getDeclaredAnnotationsByType( Class< A> annotationClass) 该方法的功能与前面介绍的 getDeclaredAnnotations ()方法 基本相似。但由于 Java 8 增加了重复注解功能,因此需要使用该方法获取直接修饰该类的、指定类型的多个Annotation。

获得内部类

方法名 描述
Class<?>[] getDeclaredClasses() 返回该 Class 对象对应类里包含的全部内部类。如下方法用于访问该 Class 对象对应类所在的外部类。
Class<?> getDeclaringClass() 返回该 Class 对象对应类所在的外部类。如下方法用于访问该 Class 对象对应类所实现的接口。
Class<?>[] getInterfaces() 返回该 Class 对象对应类所实现的全部接口。如下几个方法用于访问该 Class 对象对应类所继承的父类。
Class<? super T > getSuperclass() 返回该 Class 对象对应类的超类的 Class 对象。

获取Class对象对应的修饰符、所在包、类名等基本信息

方法名 描述
int getModifiers() 返回此类或接口的所有修饰符。修饰符由 public、 protected、 private、 final、 static、 abstract 等对应的常量组成,返回的整数应使用 Modifier 工具类的方法来解码,才可以获取真实的修饰符。
Package getPackage() 获取此类的包。
String getName() 以字符串形式返回此 Class 对象所表示的类的名称。
String getSimpleName() 以字符串形式返回此 Class 对象所表示的类的简称。

判断该类是否为接口、枚举、注解类型

方法名 描述
boolean isAnnotation() 返回此 Class 对象是否表示一个注解类型(由@ interface 定义)。
boolean isAnnotationPresent( Class<? extends Annotation> annotationClass) 判断此 Class 对象是否使用了 Annotation 修饰。
boolean isAnonymousClass() 返回此 Class 对象是否是一个匿名类。
boolean isArray() 返回此 Class 对象是否表示 一个数组类。
boolean isEnum() 返回此 Class 对象是否表示一个枚举(由 enum 关键字定义)。
boolean isInterface() 返回此 Class 对象是否表示 一个接口(使用 interface 定义)。
boolean isInstance( Object obj) 判断 obj 是否是此Class 对象的实例,该方法可以完全代替 instanceof 操作符。

下面是示例程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;

//定义可重复注解
@Repeatable(Annos.class)
@interface Anno{}
@Retention(value = RetentionPolicy.RUNTIME)
@interface Annos{
Anno[] value();
}

//使用4个注解修饰该类
@SuppressWarnings(value = "unchecked")
@Deprecated
//使用重复注解修饰该类
@Anno
@Anno
public class ClassTest {

//为该类定义一个私有的构造器
private ClassTest() {

}

//定义一个有参数的构造器
public ClassTest(String name) {
System.out.println("执行有参数的构造器");
}

//定义一个无参数的info方法
public void info() {

}

//定义一个有参数的info方法
public void info(String str) {
System.out.println("执行有参数的info方法"
+ ",其str参数值为:"+str);
}

//定义一个测试用的内部类
class Inner{

}

public static void main(String[] args) throws NoSuchMethodException, SecurityException, ClassNotFoundException {

//下面的代码可以获取ClassTest对应的Class
Class<ClassTest> clazz = ClassTest.class;

//获取该Class对象所对应类的全部构造器
Constructor[] ctors = clazz.getDeclaredConstructors();
System.out.println("ClassTest的全部构造器如下:");
for (Constructor c:ctors) {
System.out.println(c);
}

//获取该Class对象所对应类的全部public构造器
Constructor[] publicCtors = clazz.getConstructors();
for (Constructor c:publicCtors) {
System.out.println(c);
}

//获取该Class对象所应类的全部public方法
Method[] mtds = clazz.getMethods();
System.out.println("ClassTest的全部public方法如下:");
for(Method md:mtds) {
System.out.println(md);
}

//获取该Class对象所对应类的指定方法
System.out.println("ClassTest里带一个字符串参数的info方法为:"+clazz.getMethod("info", String.class));

//获取该Class对象对应类的全部注解
Annotation[] anns = clazz.getAnnotations();
System.out.println("ClassTest的全部Annotation如下:");
for(Annotation an:anns) {
System.out.println(an);
}
System.out.println("该Class元素上的@SuppressWarnings注解为:"+Arrays.toString(clazz.getAnnotationsByType(Anno.class)));

//获取该Class对象所对应类的全部内部类
Class<?>[] inners = clazz.getDeclaredClasses();
System.out.println("ClassTest的全部内部类如下");
for(Class c:inners) {
System.out.println(c);
}

//使用Class.forName()方法加载ClassTest的Inner内部类
Class inClazz = Class.forName("clazz.ClassTest$Inner");

//通过getDeclaredClasses访问该类所在的外部类
System.out.println("inClazz对应类的外部类为:"+inClazz.getDeclaredClasses());
System.out.println("ClassTest的包为:"+clazz.getPackage());
System.out.println("ClassTest的父类为:"+clazz.getSuperclass());
}
}

方法参数反射

Java8在反射包下新增了一个Executable抽象基类,该对象代表可执行得类成员,该类派生了Constructor、Method两个子类。

Executable类提供了如下两个方法来获取该方法或参数得形参个数及其形参名

方法名 描述
int getParameterCount() 获取该构造器或方法的形参个数。
Parameter[] getParameters() 获取该构造器或方法的所有形参。

Parameter类的方法

方法名 描述
getModifiers() 获取修饰该形参的修饰符。
String getName() 获取形参名。
Type getParameterizedType() 获取带泛型的形参类型。
Class<?> getType() 获取形参类型。
boolean isNamePresent() 该方法返回该类的 class 文件中是否包含了方法的形参名信息。
boolean isVarArgs() 该方法用于判断该参数是否为个数可变的形参。

使用javac命令编译Java源文件时,默认生成的class文件并不包含方法的形参名信息,因为调用isNamePresent()方法将会返回false。如果希望javac命令编译Java源文件时可以保留形参信息,则需要为该命令指定-parameters选项。

下面的程序就示范了参数反射功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;

class Test{
public void replace(String str,List<String> list) {}
}

public class MethodParameterTest {

public static void main(String[] args) throws NoSuchMethodException, SecurityException {
//获取String的类
Class<Test> clazz = Test.class;

//获取String类的带两个参数的replace()方法
Method replace = clazz.getMethod("replace",String.class,List.class);

//获取指定方法的参数个数
System.out.println("replace方法参数个数:"+replace.getParameterCount());

//获取replace的所有参数信息
Parameter[] parameters = replace.getParameters();
int index = 1;

//遍历所有参数
for(Parameter p : parameters) {
if(p.isNamePresent()) {
System.out.println("---第"+index++ +"个参数信息---");
System.out.println("参数名"+p.getName());
System.out.println("形参类型:"+p.getType());
System.out.println("泛型类型:"+p.getParameterizedType());
}
}
}
}

该程序只有当你使用正确的方式生成class文件才能正常显示

1
javac -parameters -d . MethodParameterTest.java

使用反射生成并操作对象

创建对象

通过反射创建对象,需要先通过Class对象获取指定的 Constructor 对象(构造器对象),然后通过该对象的newInstance()方法来创建实例。

下面是一个demo程序(实现了一个简单的对象池,该对象池会根据配置文件的key-value对,然后创建这些对象,并将这些对象放入一个HashMap中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package clazz;

import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ObjectPoolFactory {

//定义一个对象池,前面是对象名,后面是实际对象
private Map<String, Object>objectPool = new HashMap<String, Object>();

//定义一个创建对象的方法
//该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject(String clazzName) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {

//根据字符串来获取对应的Class对象
Class<?> clazz = Class.forName(clazzName);

//使用clazz对应类的默认构造器构建实例
return clazz.getConstructor().newInstance();
}

//该方法根据指定文件来初始化对象池
//它会根据配置文件来创建对象
public void initPool(String fileName) {
try(FileInputStream fis = new FileInputStream(fileName)) {
Properties props = new Properties();
props.load(fis);
for(String name : props.stringPropertyNames()) {

//每取出一对key-value对,就根据value创建一个对象
//调用createObject创建对象,并将对象添加到对象池中
objectPool.put(name, createObject(props.getProperty(name)));
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("读取"+fileName+"异常");
}
}
public Object getObject(String name) {

//从objectPool中取出指定name对应的对象
return objectPool.get(name);
}
public static void main(String[] args) {
ObjectPoolFactory pf = new ObjectPoolFactory();
pf.initPool("obj.txt");
System.out.println(pf.getObject("a"));
System.out.println(pf.getObject("b"));
}
}

为该程序提供如下的属性文件

obj.txt文件

1
2
a=java.util.Date
b=javax.swing.JFrame

总结下来使用反射创建实例对象的话有三个步骤

  1. 获取该类的Class对象
  2. 利用Class对象的getConstructor()方法来获取指定的构造器
  3. 调用Constructor的newInstance()方法来创建Java对象

下面程序利用反射来创建,而且使用了指定的构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class CreateJframe {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

//获取Jframe对应的class对象
Class<?> jframeClazz = Class.forName("javax.swing.JFrame");

//获取JFrame中带一个字符串参数的构造器
Constructor ctor = jframeClazz.getConstructor(String.class);

//调用Constructor的newInstance方法创建对象
Object obj = ctor.newInstance("测试窗口");

//输出
System.out.println(obj);
}
}

调用方法

获得Class对象后,通过getMethods()方法或者getMethod()来获取全部方法或指定方法。他们的返回值都是Method数组或者对象。

在Mehod中包含一个invoke()方法

Object invoke(Object obj,Object... args)该方法中的obj是执行该方法的主要调用对象,args是该方法的实参。

如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的setAccessible(boole flag)方法,flag为true时,指示该Method在使用时应该取消Java语言的访问权限检查;false则代表要实施访问权限检查,默认为false。该方法属于它的父类AccessibleObject,所以Method、Constructor、Field都可以调用该方法,从而忽视private的效果

下面程序对之前程序是一个加强,允许在配置文件中增加配置对象的成员变量的值,并使用方法为其赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ExtendedObjectPoolFactory {

//定义一个对象池,前面是对象名,后面是实际对象
private Map<String,Object> objectPool = new HashMap<>();
private Properties config = new Properties();

//从指定属性文件中初始化Properties对象
public void init(String fileName) {
try(FileInputStream fis = new FileInputStream(fileName))
{
config.load(fis);
} catch (IOException e) {
e.printStackTrace();
System.out.println("读取"+fileName+"异常");
}
}

//定义一个创建对象的方法
//该方法只要传入一个字符串类名,程序可以根据该类生成Java对象
private Object createObject(String clazzName) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {

//根据字符串来获取对应的Class对象
Class<?> clazz = Class.forName(clazzName);

//使用clazz对应类的默认构造器构建实例
return clazz.getConstructor().newInstance();
}

//该方法根据指定文件来初始化对象池
//它会根据配置文件来创建对象
public void initPool() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
for(String name:config.stringPropertyNames()) {

//每取出一个kv对,如果key中不包含百分号(%)
//这就表明是根据value来创建一个对象
//调用createObject创建对象,并将对象添加到对象池中
if(!name.contains("%")) {
objectPool.put(name, createObject(config.getProperty(name)));
}
}
}

//该方法将会根据属性文件来调用指定对象的setter方法
public void initProperty() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
for(String name:config.stringPropertyNames()) {

//每取出一堆kv对,如果key中包含百分号%
//即可认为该key用于控制调用对象的setter方法设置值
//%前半为对象名字,后半控制setter方法名
if(name.contains("%")) {

//将配置文件中的key按%分割
String[] objAndProp = name.split("%");

//取出调用setter方法的参数值
Object target = getObject(objAndProp[0]);

//获取setter方法名:set+"首字母大写"+剩下部分
String mtdName = "set"
+objAndProp[1].substring(0,1).toUpperCase()
+objAndProp[1].substring(1);

//通过target的getClass获取它的实现类所对应的class对象
Class<?> targetClass = target.getClass();

//获取希望调用的setter方法
Method mtd = targetClass.getMethod(mtdName, String.class);

//通过Method的invoke方法执行setter方法
//将config.getProperty(name)的值作为调用setter方法的参数
mtd.invoke(target, config.getProperty(name));
}
}
}
public Object getObject(String name) {

//从objectPool中取出指定name对应的对象
return objectPool.get(name);
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
epf.init("extObj.txt");
epf.initPool();
epf.initProperty();
System.out.println(epf.getObject("a"));
}
}

该程序还是需要一个txt文件

1
2
3
4
a=javax.swing.JFrame
b=javax.swing.JLabel
#这是注释 set the title of a
a%title=Test Title

最后一句a%title行表明希望调用a对象的setTitle()方法,Test Title是参数。

访问成员变量值

通过Class对象的的 getFields() 或 getField() 方法可以获取该类所包括的全部成员变量或指定成员变量。

Field提供了如下两组方法来读取和设置成员变量值

注意:此处的 Xxx 对应 8 种基本类型,如果该成员变量的类型是引用类型,则取消 get 后面的Xxx。

方法名 描述
getXxx( Object obj) 获取 obj 对象的该成员变量的值。
setXxx( Object obj, Xxx val) 将 obj 对象的该成员变量设置成 val 值。

下面是示例程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.lang.reflect.Field;

class Person{
private String name;
private int age;
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}

}
public class FieldTest {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {

//创建一个Person对象
Person p = new Person();

//获取Person类对应的Class对象
Class<Person> personClazz = Person.class;

//获取person的名为name的成员变量
//使用getDeclaredField方法表明可获得各种访问控制符的成员变量
Field nameField = personClazz.getDeclaredField("name");

//设置通过反射访问该成员变量时取消访问权限检查
nameField.setAccessible(true);

//调用set方法为p对象的name成员变量设置值
nameField.set(p, "Yeeku.H.Lee");

//获取Person类名age的成员变量
Field ageField = personClazz.getDeclaredField("age");

//设置通过反射访问该成员变量时取消访问权限检查
ageField.setAccessible(true);

//调用setInt方法为p对象的age成员变量设置值
ageField.setInt(p, 30);
System.out.println(p);
}
}

操作数组

在反射包下还提供了Array类,该类可以代表所有的数组。

注意:此处的 Xxx 对应 8 种基本类型,如果该成员变量的类型是引用类型,则取消 get 后面的Xxx。

方法名 描述
static Object newInstance( Class<?> componentType, int… length) 创建一个具有指定的元素类型、指定维度的新数组。
static xxx getXxx( Object array, int index) 返回 array 数组中第 index 个元素。
static void setXxx( Object array, int index, xxx val) 将 array 数组中第 index 个元素的值设为 val。

下面程序讲了如何使用Array来生成数组,并赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.lang.reflect.Array;

public class ArrayTest1 {
public static void main(String[] args) {
try {

//创建一个元素类型为String,长度为10的数组
Object arr = Array.newInstance(String.class, 10);

//一次为arr数组中的index为5,6的元素赋值
Array.set(arr, 5, "苹果手机");
Array.set(arr, 6, "安卓手机");

//依次取出arr数组中index为5、6的元素的值
Object book1 = Array.get(arr, 5);
Object book2 = Array.get(arr, 6);

System.out.println(book1);
System.out.println(book2);
} catch (Exception e) {
e.printStackTrace();
}
}
}

下面的程序使用该类创建一个三维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.lang.reflect.Array;

public class ArrayTest2 {

public static void main(String[] args) {
/*
* 创建一个三维数组
* 根据前面介绍数组时讲的:三维数组也是一维数组
* 是数组元素是二维数组的一维数组
* 因此可以任务arr是长度为3的一维数组
*/
Object arr = Array.newInstance(String.class, 3,4,10);

//获取arr数组中的index为2的元素,该元素应该是二维数组
Object arrObj = Array.get(arr, 2);

//使用Array为二维数组的数组元素赋值,二维数组的数组元素是一维数组
//所以传入Array的set方法的第三个参数是一维数组
Array.set(arrObj, 2, new String[] {
"苹果手机",
"安卓手机"
});

//获取arrObj数组中index为3的元素,该元素应该是一维数组
Object anArr = Array.get(arrObj, 3);
Array.set(anArr, 8, "塞班手机");

//将arr强制类型转换为三维数组
String[][][] cast = (String[][][])arr;

//获取cst三维数组中指定元素的值
System.out.println(cast[2][3][8]);
System.out.println(cast[2][2][0]);
System.out.println(cast[2][2][1]);
}

}

反射和泛型

使用Class<?>可以避免使用反射生成的对象需要强制类型转换

泛型和Class类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BenObjectFactory {
public static Object getInstance(String clsName) {
try {

//创建指定类对应的class对象
Class cls = Class.forName(clsName);

//返回使用该Class对象创建的实例
return cls.getConstructor().newInstance();

} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

上面的程序可以创建一个新对象,但是这个对象是Object,所以当使用特定对象时候必须要强转,这就有风险。

如果改进为这样的代码的话就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.Date;

import javax.xml.crypto.Data;

public class BenObjectFactory2 {
public static<T> T getInstance(Class<T> cls) {
try {
return cls.getConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {

//获取实例后无需类型转换
Date d = BenObjectFactory2.getInstance(Date.class);
}
}

这样获取实例以后就没有风险了,同样的道理,对于数组也是同样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.lang.reflect.Array;

public class BenArray {

//对Array的newInstance方法进行包装
@SuppressWarnings("unchecked")
public static<T> T[] newInstance(Class<T> componentType,int length) {
return (T[])Array.newInstance(componentType, length);
}

public static void main(String[] args) {

//使用BenArray的newInstance()创建一维数组
String[] arr = BenArray.newInstance(String.class, 10);

//创建二维数组,在这种情况下,只要设置数组元素的类型是int[]即可
int[][] intArr = BenArray.newInstance(int[].class, 5);
arr[5] = "苹果手机";

//intArr是二维数组,初始化该数组的第二个数组元素
//二维数组的元素必须是一维数组
intArr[1] = new int[] {23,12};
System.out.println(arr[5]);
System.out.println(intArr[1][1]);
}
}

使用反射来获取泛型信息

前面说到可以用Field来获得成员变量的类型。Class<?> a = f.getType()这个方法可以获得成员变量f的类型。但是这种方式只对普通类型有效,假如该类型是Map<String,Integer>类型,则不能准确地获取成员变量地泛型参数。

如果想要获得其泛型类型。需要使用Type gtype = f.getGenericType(),然后把Type对象强制转换为为 ParameterizedType 对象, ParameterizedType 代表被参数化的类型,也就是增加了泛 型限制的类型。 ParameterizedType 类提供了如下两个方法。

方法名 描述
getRawType() 返回没有泛型信息的原始类型。
getActualTypeArguments() 返回泛型参数的类型。

下面是一个获取泛型的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;

public class GenericTest {
private Map<String,Integer> score;
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
Class<GenericTest> clazz = GenericTest.class;
Field f = clazz.getDeclaredField("score");

//直接使用getType()取出类型只对普通类型的成员变量有效
Class<?> a = f.getType();

//下面将看到仅输出java.util.Map
System.out.println("score的类型是"+a);

//获得成员变量f的泛型类型
Type gType = f.getGenericType();

//如果gType类型是ParameterizedType对象
if(gType instanceof ParameterizedType) {

//强制类型转换
ParameterizedType pType = (ParameterizedType)gType;

//获取原始类型
Type rType = pType.getRawType();
System.out.println("原始类型是"+rType);

//取得泛型类型的泛型参数
Type[] tArgs = pType.getActualTypeArguments();
System.out.println("泛型信息是:");
for(int i=0;i<tArgs.length;i++) {
System.out.println("第"+i+"个泛型类型是:"+tArgs[i]);
}
}else {
System.out.println("获取泛型类型出错!");
}
}
}

完结撒花!!!


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!