能够分析类能力的程序称为反射(reflective)。反射机制的功能非常强大,主要提供了如下功能:
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意方法和属性;
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。然而, 可以通过专门的Java类访问这些信息,这个类为java.lang.Class 。
类Class的实例表示正在运行的Java应用程序中的类和接口。 枚举是一种类,一个注释是一种接口。每个数组也属于一个类,它被反映为具有相同元素类型和维数的所有数组共享的Class对象。 原始Java类型(boolean,byte,char,short,int,long,float和double)和关键字void也表示为Class对象。
Class类没有公共构造函数。Class对象由Java虚拟机自动构建,因为加载了类,并且通过调用类加载器中的defineClass方法。
虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Class对象,共有如下三种方式:
1、Class类的forName方法
Class<?> clazz = Class.forName("com.codersm.study.jdk.reflect.Person");
2、通过一个类的对象的getClass()方法
Class<?> clazz = new Person().getClass();
3、Class字面常量
Class<Person> clazz = Person.Class;
小结:
注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。
Class字面常量这种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高, 因为通过字面量的方法获取Class对象的引用不会自动初始化该类?。 更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型。
Class类提供了大量的实例方法来获取该Class对象所对应的详细信息,Class类大致包含如下方法,其中每个方法都包含多个重载版本,因此我们只是做简单的介绍,详细请参考JDK文档。
获取类信息
获取内容 | 方法签名 |
---|---|
构造器 |
Constructor<T> getConstructor(Class<?>... parameterTypes)
|
方法 |
Method getMethod(String name, Class<?>... parameterTypes)
|
属性 |
Field getField(String name)
|
Annotation |
<A extends Annotation> A getAnnotation(Class<A> annotationClass)
|
内部类 |
Class<?>[] getDeclaredClasses()
|
外部类 |
Class<?> getDeclaringClass()
|
实现的接口 |
Class<?>[] getInterfaces()
|
修饰符 |
int getModifiers()
|
所在包 |
Package getPackage()
|
类名 |
String getName()
|
简称 |
String getSimpleName()
|
注: getDeclaredXxx方法可以获取所有的Xxx,无论private/public。
判断类本身信息的方法
判断内容 | 方法签名 |
---|---|
注解类型? |
boolean isAnnotation()
|
使用了该Annotation修饰? |
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
|
匿名类? |
boolean isAnonymousClass()
|
数组? |
boolean isArray()
|
枚举? |
boolean isEnum()
|
原始类型? |
boolean isPrimitive()
|
接口? |
boolean isInterface()
|
obj是否是该Class的实例 |
boolean isInstance(Object obj)
|
使用反射生成并操作对
程序可以通过Method对象来执行相应的方法;
通过Constructor对象来调用对应的构造器创建实例;
通过Filed对象直接访问和修改对象的成员变量值。
通过反射来生成对象的方式有两种:
使用Class对象的newInstance()方法来创建该Class对象对应类的实例(这种方式要求该Class对象的对应类有默认构造器) 。
先使用Class对象获取指定的Constructor对象, 再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例(通过这种方式可以选择指定的构造器来创建实例) 。
class Person { private String name; private Integer age; public Person() { this.name = "system"; this.age = 99; } public Person(String name, Integer age) { this.name = name; this.age = age; } public Integer getAge() { return age; } public String getName() { return name; } @Override public String toString() { return "Person{" + "name='" + name + '/'' + ", age=" + age + '}'; } } public class Test { public static void main(String[] args) throws Exception { Class<Person> pClass = Person.class; // 通过第1种方式创建对象 Person p = pClass.newInstance(); System.out.println(p); // 通过第2种方式创建对象 Constructor<Person> constructor = pClass.getDeclaredConstructor( String.class, Integer.class); Person person2 = constructor.newInstance("zhangsan",20); System.out.println(person2); } }
当获取到某个类对应的Class对象之后, 就可以通过该Class对象的getMethod来获取一个Method数组或Method对象.每个Method对象对应一个方法,在获得Method对象之后,就可以通过调用invoke方法来调用该Method对象对应的方法。
Person person = new Person(); // 获取getAge方法 Method getAgeMethod = person.getClass().getMethod("getAge",null); // 调用invoke方法来调用getAge方法 Integer age = (Integer) getAgeMethod.invoke(person,null); System.out.println(age);
通过Class对象的的getField()方法可以获取该类所包含的全部或指定的成员变量Field,Filed提供了如下两组方法来读取和设置成员变量值.
getXxx(Object obj): 获取obj对象的该成员变量的值, 此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型, 则取消get后面的Xxx;
setXxx(Object obj, Xxx val): 将obj对象的该成员变量值设置成val值.此处的Xxx对应8种基本类型, 如果该成员类型是引用类型, 则取消set后面的Xxx;
Person person = new Person(); // 获取name成员变量Field Field nameField = person.getClass().getDeclaredField("name"); // 启用访问控制权限 nameField.setAccessible(true); // 获取person对象的成员变量name的值 String name = (String) nameField.get(person); System.out.println("name = " + name); // 设置person对象的成员变量name的值 nameField.set(person, "lisi"); System.out.println(person);
在Java的java.lang.reflect包中存在着一个可以动态操作数组的类,Array提供了动态创建和访问Java数组的方法。Array允许在执行get或set操作进行取值和赋值。在Class类中与数组关联的方法是:
方法 | 说明 |
---|---|
public native Class<?> getComponentType() | 返回表示数组元素类型的Class,即数组的类型 |
public native boolean isArray() | 判定此Class对象是否表示一个数组类 |
java.lang.reflect.Array中的常用静态方法如下:
newInstance(Class<?> componentType, int length)
newInstance(Class<?> componentType, int... dimensions)
int getLength(Object array)
Object get(Object array, int index)
void set(Object array, int index, Object value)
实现通用数组复制功能,其代码如下:
public class GenericityArray { public static <T> T[] copy(T[] clazz) { return (T[]) Array.newInstance( clazz.getClass().getComponentType(), clazz.length); } public static void main(String[] args) { Integer[] array = {1, 2, 3}; Integer[] copyArray = GenericityArray.copy(array); System.out.println(copyArray.length); } }
为了通过反射操作泛型以迎合实际开发的需要, Java新增了 java.lang.reflect.ParameterizedType
、 java.lang.reflect.GenericArrayType
、 java.lang.reflect.TypeVariable
、 java.lang.reflect.WildcardType
。
类型 | 含义 |
---|---|
ParameterizedType | 一种参数化类型, 比如Collection |
GenericArrayType | 一种元素类型是参数化类型或者类型变量的数组类型 |
TypeVariable | 各种类型变量的公共接口 |
WildcardType | 一种通配符类型表达式, 如? extends Number |
其中, 我们可以使用ParameterizedType来获取泛型信息.
public class Client { private Map<String, Object> objectMap; public void test(Map<String, User> map, String string) { } public Map<User, Bean> test() { return null; } /** * 测试属性类型 * * @throws NoSuchFieldException */ @Test public void testFieldType() throws NoSuchFieldException { Field field = Client.class.getDeclaredField("objectMap"); Type gType = field.getGenericType(); // 打印type与generic type的区别 System.out.println(field.getType()); System.out.println(gType); System.out.println("**************"); if (gType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) gType; Type[] types = pType.getActualTypeArguments(); for (Type type : types) { System.out.println(type.toString()); } } } /** * 测试参数类型 * * @throws NoSuchMethodException */ @Test public void testParamType() throws NoSuchMethodException { Method testMethod = Client.class.getMethod("test", Map.class, String.class); Type[] parameterTypes = testMethod.getGenericParameterTypes(); for (Type type : parameterTypes) { System.out.println("type -> " + type); if (type instanceof ParameterizedType) { Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments(); for (Type actualType : actualTypes) { System.out.println("/tactual type -> " + actualType); } } } } /** * 测试返回值类型 * * @throws NoSuchMethodException */ @Test public void testReturnType() throws NoSuchMethodException { Method testMethod = Client.class.getMethod("test"); Type returnType = testMethod.getGenericReturnType(); System.out.println("return type -> " + returnType); if (returnType instanceof ParameterizedType) { Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments(); for (Type actualType : actualTypes) { System.out.println("/tactual type -> " + actualType); } } } }