反射 (reflective)是可以在运行时加载、使用编译期间完全未知的类。
程序在运行状态中,可以动态加载一个只有名称的类
对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能调用他的任意一个方法和属性
加载完类之后, 在堆内存中会产生一个java.lang.Class类型的对象(一个类只有一个Class对象), 这个对象包含了完整的类的结构信息,通过该Class对象就可以访问到JVM中的这个类
Class
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。
这个信息跟踪着每个对象所属的类。
虚拟机利用运行时类型信息选择相应的方法执行。
我们也可以通过专门的Java类访问这些信息,这个类为java.lang.Class。
类Class的实例表示正在运行的Java应用程序中的类和接口。 (Java有 5种引用类型/对象类型:类 接口 数组 枚举 标注)
Class类没有公共构造函数。Class对象由Java虚拟机自动构建(加载类,并且通过调用类加载器中的defineClass()方法)。
得到一个Class对象
虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Class对象,共有如下四种方式:
1、通过Class的静态方法forName(String className)获取:体现反射的动态性,className需要是类的全限定名
Class> clazz = Class.forName("com.codersm.study.jdk.reflect.Person");
- 注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。
2、通过一个类的对象的getClass()方法
Class> clazz = new Person().getClass();
3、Class字面常量,调用运行时类本身的class属性
Class
- Class字面常量这种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。
- 字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型。
4、通过类的加载器
String className = "java.util.commons";
ClassLoader classLoader = this.getClass().getClassLoader();
Class clazz = classLoader.loadClass(className);
Class类的方法
Class类提供了大量的实例方法来获取该Class对象所对应的详细信息,Class类大致包含如下方法,其中每个方法都包含多个重载版本
- 获得构造器
ConstructorgetConstructor(Class>... parameterTypes)
- 获得方法
Method getMethod(String name, Class>... parameterTypes)
- 获得属性
Field getField(String name)
- 内部类
Class>[] getDeclaredClasses()
- 外部类
Class> getDeclaringClass()
- 实现的接口
Class>[] getInterfaces()
- 修饰符
int getModifiers()
- 所在包
Package getPackage()
- 类名
String getName()
注: getDeclaredXxx方法可以获取所有的Xxx,无论private/public。
- 是否是注解类型
boolean isAnnotation()
- 是否使用了该注解修饰
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对象直接访问和修改对象的成员变量值。
Method Constructor Field这些类都实现了java.lang.reflect.Member接口,程序可以通过Method对象来执行相应的方法,通过Constructor对象来调用对应的构造器创建实例,通过Filed对象直接访问和修改对象的成员变量值.
创建对象
通过反射来生成对象的方式有两种:
1、使用Class对象的newInstance()方法来创建该Class对象对应类的实例
(这种方式要求该Class对象的对应类有默认构造器)
2、先使用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 pClass = Person.class;
// 通过第1种方式创建对象
Person p = pClass.newInstance();
System.out.println(p);
// 通过第2种方式创建对象
Constructor 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);//静态方法invoke的一个参数是null
System.out.println(age);
访问成员变量
通过Class对象的的getField()方法可以获取该类所包含的全部或指定的成员变量Field,Filed提供了如下两组方法来读取和设置成员变量值.
1、getXxx(Object obj): 获取obj对象的该成员变量的值, 此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型, 则取消get后面的Xxx;
2、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类中与数组关联的方法是:
- 返回表示数组元素类型的Class,即数组的类型
public native Class> getComponentType()
- 判定此Class对象是否表示一个数组类
public native boolean isArray()
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[] 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。
- 一种参数化类型, 比如Collection
ParameterizedType
- 一种元素类型是参数化类型或者类型变量的数组类型
GenericArrayType
- 各种类型变量的公共接口
TypeVariable
- 一种通配符类型表达式, 如? extends Number
WildcardType
其中, 我们可以使用ParameterizedType来获取泛型信息.
public class Client {
private Map objectMap;
public void test(Map map, String string) { }
public Map test() {
return null;
}
/**
* 测试属性类型
*
* @throws NoSuchFieldException
*/
@Test
public void testFieldType() throws NoSuchFieldException {
Field field = Client.class.getDeclaredField("objectMap");
Type gType = field.getGenericType();
// 打印getType与getGenericType的区别
//getType() 和 getGenericType()的区别 :
//1.首先是返回的类型不一样,getType返回一个 Class 对象,getGenericType返回一个 Type接口。
//2.如果属性是一个泛型,从getType()只能得到这个属性的接口类型。但从getGenericType()还能得到这个泛型的参数类型。
//3.getGenericType()如果当前属性有签名属性类型就返回,否则就返回 Field.getType()。
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);
}
}
}
}