通过反射,Java代码可以发现有关已加载类的字段,方法和构造函数的信息,并可以在安全限制内对这些字段,方法和构造函数进行操作。
简而言之,你可以在运行状态中通过反射机制做到:
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
使用Java反射主要涉及两个类(接口)Class, Member,如果把这两个类搞清楚了,反射基本就可以了。
比如创建编译一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class实例,该Class实例保存了Shapes类相关的类型信息,包括属性,方法,构造方法等等,通过这个Class实例可以在运行时访问Shapes对象的属性和方法等。另外通过Class类还可以创建出一个新的Shapes对象。这就是反射能够实现的原因,可以说Class是反射操作的基础。
需要特别注意的是,每个class(注意class是小写,代表普通类)类,无论创建多少个实例对象,在JVM中都对应同一个Class对象。
简单一个例子:
Animal.java
public class Animal {
private String name;
private int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Animal : name = " + name + " age = " + age;
}
}
测试类:TestReflection.java
public class TestReflection {
public static void main(String[] args) {
// 获取Animal类的Class对象
Class clazz = Animal.class;
try {
// 通过Class对象反射获取Animal类的构造方法
Constructor constructor = clazz.getConstructor(String.class, int.class);
// 调用构造方法获取Animal实例
Animal animal = (Animal) constructor.newInstance("jsai",3);
// 将构造出来的Animal对象打印出来
System.err.println(animal.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
说Class是反射能够实现的基础的另一个原因是:Java反射包java.lang.reflect中的所有类都没有public构造方法,要想获得这些类实例,只能通过Class类获取。所以说如果想使用反射,必须得获得Class对象。
(常用)
// 返回字符串的Class对象
Class c = "str".getClass();
// 返回与枚举类型E对应的类
enum E { A, B }
Class c = A.getClass();
// 返回与组件类型为byte的数组对应的类.
byte[] bytes = new byte[1024];
Class c = bytes.getClass();
// 返回与java.util.HashSet对应的类.
Set<String> s = new HashSet<String>();
Class c = s.getClass();
但是,然而对于基本类型无法使用这种方法。
boolean b;
Class c = b.getClass(); // 编译出错
(常用)
Class c = boolean.class;
Class c = String.class;
(常用)
通过类的全限定名获取Class对象, 基本类型无法使用此方法
Class c = Class.forName("java.lang.String");
Class c = Double.TYPE; //等价于 double.class.
Class c = Void.TYPE;
有一些反射方法可以获取Class对象,但前提是你已经获取了一个Class对象。比如说你已经获取了一个类的Class对象,就可以通过反射方法获取这个类的父类的Class对象。
例如:
Class.getSuperclass()
Class.getClasses()
Class.getDeclaredClasses()
Class.getDeclaringClass()
Class.getEnclosingClass()
java.lang.reflect.Field.getDeclaringClass()
java.lang.reflect.Method.getDeclaringClass()
java.lang.reflect.Constructor.getDeclaringClass()
下面我们就以HashMap为例,通过一个Demo来说明如何获取这些信息
public static void testGetMapInfo(){
Class<?> clazz = HashMap.class;
// 获取类名
System.out.println("Class: "+clazz.getCanonicalName());
// 获取类限定符
System.out.println("Modifiers: "+ Modifier.toString(clazz.getModifiers()));
// 获取类泛型信息
TypeVariable[] typeParameters = clazz.getTypeParameters();
if (typeParameters.length != 0){
StringBuffer param = new StringBuffer("Parameters: ");
for (TypeVariable tv : typeParameters){
param.append(tv.getName());
param.append(" ");
}
System.out.println(param.toString());
}else {
System.out.println("-- No Type Parameters --");
}
// 获取类实现的所有接口
Type[] intfs = clazz.getGenericInterfaces();
if (intfs.length != 0) {
StringBuilder interfaces = new StringBuilder("Implemented Interfaces: ");
for (Type intf : intfs){
interfaces.append(intf.toString());
interfaces.append(" ");
}
System.out.println(interfaces.toString());
} else {
System.out.println("-- No Implemented Interfaces --");
}
// 获取类继承数上的所有父类
List<Class> superClass = new ArrayList<>();
printAncestor(clazz, superClass);
if (superClass.size() != 0){
StringBuffer inheritance = new StringBuffer("Inheritance Path: ");
for (Class<?> c : superClass){
inheritance.append(c.getCanonicalName());
inheritance.append(" ");
}
System.out.println(inheritance.toString());
}else {
System.out.println("-- No Super Classes --%n%n");
}
// 获取类的注解(只能获取到 RUNTIME 类型的注解)
Annotation[] ann = clazz.getAnnotations();
if (ann.length!=0){
StringBuilder annotation = new StringBuilder("Annotations : ");
for (Annotation a : ann){
annotation.append(a.toString());
annotation.append(" ");
}
System.out.println(annotation.toString());
}else {
System.out.println("-- No Annotations --%n%n");
}
}
// 递归向上查找父类
private static void printAncestor(Class<?> c, List<Class> l) {
Class<?> ancestor = c.getSuperclass();
if (ancestor != null) {
l.add(ancestor);
printAncestor(ancestor, l);
}
}
对于Member接口可能会有人不清楚是干什么的,但如果提到实现它的三个实现类,估计用过反射的人都能知道。我们知道类成员主要包括构造函数
,变量
和方法
,Java中的操作基本都和这三者相关,而Member的这三个实现类就分别对应他们。
java.lang.reflect.Field
:对应类变量
java.lang.reflect.Method
:对应类方法
java.lang.reflect.Constructor
:对应类构造函数
反射就是通过这三个类才能在运行时改变对象状态。下面就让我们通过一些例子来说明如何通过反射操作它们。
首先写一个测试类:
public class Cat {
public static final String TAG = Cat.class.getSimpleName();
private String name;
@Deprecated
public int age;
public Cat(String name, int age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public void eat(String food){
System.err.println("eat food " + food);
}
public void eat(String... foods){
StringBuilder s = new StringBuilder();
for(String food : foods){
s.append(food);
s.append(" ");
}
System.err.println("eat food " + s.toString());
}
public void sleep(){
System.err.println("sleep");
}
@Override
public String toString() {
return "name = " + name + " age = " + age;
}
}
通过Field你可以访问给定对象的类变量,包括获取变量的类型
、修饰符
、注解
、变量名
、变量的值
或者重新设置变量值
,即使变量是private的。
Class提供了4种方法获得给定类的Field:
方法 | 解释 |
---|---|
getFields() |
获取所有的public变量 |
getDeclaredFields() |
获取所有声明的变量(包括private) |
getField(String name) |
获取指定的变量(只能获得public的) |
getDeclaredField(String name) |
获取指定的变量(包括private) |
public static void testField1(){
Class clazz = Cat.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field:fields){
StringBuilder builder = new StringBuilder();
// 获取名称
builder.append("filed name = "+field.getName());
// 获取类型
builder.append(" type = "+field.getType());
// 获取修饰符
builder.append(" modifiers = "+ Modifier.toString(field.getModifiers()));
// 获取注解
Annotation[] ann = field.getAnnotations();
if (ann.length != 0) {
builder.append(" annotations = ");
for (Annotation a : ann){
builder.append(a.toString());
builder.append(" ");
}
} else {
builder.append(" -- No Annotations --");
}
System.err.println(builder.toString());
}
}
public static void testField2(){
Cat cat = new Cat("Tom", 2);
Class clazz = cat.getClass();
try {
// 注意获取private变量时,需要用getDeclaredField
Field fieldName = clazz.getDeclaredField("name");
Field fieldAge = clazz.getField("age");
System.err.println(fieldName+" --- "+fieldAge);
// fieldName.setAccessible(true);
// 反射获取名字, 年龄
String name = (String) fieldName.get(cat);
int age = fieldAge.getInt(cat);
System.out.println("before set, Cat name = " + name + " age = " + age);
// 反射重新set名字和年龄
fieldName.set(cat, "Timmy");
fieldAge.setInt(cat, 3);
System.err.println("after set, Cat " + cat.toString());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
如果直接运行上述代码,会出现java.lang.IllegalAccessException
异常。说我们没有权限操作变量name;
原来name变量是private,Java运行时会进行访问权限检查,private类型的变量无法进行直接访问,刚刚进行的反射操作并没有打破这种封装,所以我们依然没有权限对private属性进行直接访问。
解决办法:
java.lang.reflect.AccessibleObject
:AccessibleObject
为我们提供了一个方法 setAccessible(boolean flag)
,该方法的作用就是可以取消 Java 语言访问权限检查。所以任何继承AccessibleObject的类的对象都可以使用该方法取消 Java 语言访问权限检查。(final类型变量也可以通过这种办法访问)
Field正是AccessibleObject的子类,因此,只要在访问私有变量前调用field.setAccessible(true)就可以了,即加上
fieldName.setAccessible(true);
// 反射获取名字, 年龄
String name = (String) fieldName.get(cat);
int age = fieldAge.getInt(cat);
结果:
注意:
Field
,Method
和Constructor
都是继承AccessibleObject,所以如果遇到私有方法和私有构造函数无法访问,记得处理方法一样。
通过反射访问对象的方法。
Class依然提供了4种方法获取Method
方法 | 解释 |
---|---|
getMethods() |
获取所有的public方法 |
getDeclaredMethods() |
获取所有声明的方法 |
getMethod(String name, Class>... parameterTypes) |
根据方法名获取指定的public方法 |
getDeclaredMethod(String name, Class>... parameterTypes) |
根据方法名获得指定的方法 |
参数name:方法名,
参数parameterTypes:方法的参数类型
如 getDeclaredMethod(“eat”, String.class)
获取带参数方法时,如果参数类型错误会报NoSuchMethodException,对于参数是泛型的情况,泛型须当成Object处理(Object.class)
方法 | 解释 |
---|---|
getReturnType() |
获取目标方法返回类型对应的Class对象 |
getGenericReturnType() |
获取目标方法返回类型对应的Type对象 |
这两个方法的区别?
getReturnType()
返回类型为Class,getGenericReturnType()
返回类型为Type; Class实现Type。例如:
public String function1()
那么各自返回值为:
getReturnType() : class java.lang.String
getGenericReturnType() : class java.lang.String
例如:public T function2()
那么各自返回值为:
getReturnType() : class java.lang.Object
getGenericReturnType() : T
例如:public Class
function3()
那么各自返回值为:
getReturnType() : class java.lang.Class
getGenericReturnType() : java.lang.Class
反射中所有形如getGenericXXX()的方法规则都与上面所述类似。
方法 | 解释 |
---|---|
getParameterTypes() |
获取目标方法各参数类型对应的Class对象 |
getGenericParameterTypes() |
获取目标方法各参数类型对应的Type对象 |
方法 | 解释 |
---|---|
getExceptionTypes() |
获取目标方法抛出的异常类型对应的Class对象 |
getGenericExceptionTypes() |
获取目标方法抛出的异常类型对应的Type对象 |
.class文件中默认不存储方法参数名称,如果想要获取方法参数名称,需要在编译的时候加上-parameters
参数。(构造方法的参数获取方法同样)
方法与Field获取属性权限等类似
method.getModifiers();
反射通过Method的invoke()
方法来调用目标方法。
public native Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
注意:如果方法是private的,可以使用
method.setAccessible(true)
方法绕过权限检查
public static void testField3(){
Class<?> clazz = Cat.class;
try {
// 构造Cat实例
Constructor constructor = clazz.getConstructor(String.class, int.class);
Object cat = constructor.newInstance("jsai",23);
// 调用无参方法
Method sleep = clazz.getDeclaredMethod("sleep");
sleep.invoke(cat);
// 调用定项参数方法
Method eat = clazz.getDeclaredMethod("eat", String.class);
eat.invoke(cat, "testString");
// 调用不定项参数方法,不定项参数可以当成数组来处理
Method varargsEat = clazz.getDeclaredMethod("eat", String[].class);
String[] foods = new String[]{"str1","str2","str3"};
varargsEat.invoke(cat, (Object) foods);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
方法 | 解释 |
---|---|
getConstructors() |
获取所有的public构造方法 |
getDeclaredConstructors() |
获取所有声明的构造方法 |
getConstructor(Class>... parameterTypes) |
获取指定public构造函数 |
getDeclaredConstructor(Class>... parameterTypes) |
获取指定构造函数 |
参数parameterTypes为构造方法的参数类型
构造方法的名称、限定符、参数、声明的异常等获取方法都与Method类似,请参照Method
通过反射有两种方法可以创建对象:
java.lang.reflect.Constructor.newInstance()
Class.newInstance()
一般来讲,我们优先使用第一种方法;那么这两种方法有何异同呢?
Class.newInstance()
仅可用来调用无参的构造方法;Constructor.newInstance()
可以调用任意参数的构造方法。Class.newInstance()
会将构造方法中抛出的异常不作处理原样抛出;Constructor.newInstance()
会将构造方法中抛出的异常都包装成InvocationTargetException抛出。Class.newInstance()
需要拥有构造方法的访问权限;Constructor.newInstance()
可以通过setAccessible(true)方法绕过访问权限访问private构造方法。注意:反射不支持自动封箱,传入参数时要小心(自动封箱是在编译期间的,而反射在运行期间)
数组和枚举也是对象,但是在反射中,对数组和枚举的创建、访问和普通对象有一些的不同,所以Java反射为数组和枚举提供了一些特定的API接口。
数组类型:数组本质是一个对象,所以它也有自己的类型。
例如对于int[] intArray
,数组类型为class [I
。数组类型中的[
个数代表数组的维度,例如[
代表一维数组,[[
代表二维数组;[
后面的字母代表数组元素类型,I
代表int
,一般为类型的首字母大写(long类型例外,为J)。
class [B //byte类型一维数组
class [S //short类型一维数组
class [I //int类型一维数组
class [C //char类型一维数组
class [J //long类型一维数组,J代表long类型,因为L被引用对象类型占用了
class [F //float类型一维数组
class [D //double类型一维数组
class [Lcom.dada.Season //引用类型一维数组
class [[Ljava.lang.String //引用类型二维数组
//获取一个变量的类型
Class<?> c = field.getType();
//判断该变量是否为数组
if (c.isArray()) {
//获取数组的元素类型
c.getComponentType()
}
Java反射为我们提供了java.lang.reflect.Array类
用来创建和初始化数组。
//创建数组, 参数componentType为数组元素的类型,后面不定项参数的个数代表数组的维度,参数值为数组长度
Array.newInstance(Class<?> componentType, int... dimensions)
//设置数组值,array为数组对象,index为数组的下标,value为需要设置的值
Array.set(Object array, int index, int value)
//获取数组的值,array为数组对象,index为数组的下标
Array.get(Object array, int index)
例如:用反射创建 int[] array = new int[]{1, 2}
Object array = Array.newInstance(int.class, 2);
Array.setInt(array , 0, 1);
Array.setInt(array , 1, 2);
注意:反射支持对数据自动加宽,但不允许数据narrowing(变窄?真难翻译)。意思是对于上述set方法,你可以在int类型数组中 set short类型数据,但不可以set long类型数据,否则会报IllegalArgumentException。
Java反射没有提供能够直接访问多维数组元素的API,但你可以把多维数组当成数组的数组处理。
Object matrix = Array.newInstance(int.class, 2, 2);
Object row0 = Array.get(matrix, 0);
Object row1 = Array.get(matrix, 1);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
Object matrix = Array.newInstance(int.class, 2);
Object row0 = Array.newInstance(int.class, 2);
Object row1 = Array.newInstance(int.class, 2);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
Array.set(matrix, 0, row0);
Array.set(matrix, 1, row1);
枚举隐式继承自java.lang.Enum
,Enum继承自Object,所以枚举本质也是一个类,也可以有成员变量,构造方法,方法等;对于普通类所能使用的反射方法,枚举都能使用;另外java反射额外提供了几个方法为枚举服务。
方法 | 解释 |
---|---|
Class.isEnum() |
指示此类是否表示枚举类型 |
Class.getEnumConstants() |
检索由枚举定义的枚举常量的列表,这些常量的声明顺序为 |
java.lang.reflect.Field.isEnumConstant() |
指示此字段是否表示枚举类型的元素 |
使用反射的一个原则:如果使用常规方法能够实现,那么就不要用反射。
转载:https://www.jianshu.com/p/607ff4e79a13