七、反射
反射机制是在运行态中,对于任何一个类/对象,通过反射都能知道任何一个属性和方法。这样动态获取新的以及动态调用对象方法的功能叫做反射。
1、Class类
Class可以说是反射能够实现的基础,
class关键字是在声明java类时使用的;而Class 是java JDK提供的一个类,完整路径为 java.lang.Class
对于每一种类,Java虚拟机都会初始化出一个Class类型的实例,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象,并且这个Class对象会被保存在同名.class文件里。
当我们new一个新对象或者引用静态成员变量时,java(JVM)虚拟机中的类加载器系统会将对应的Class对象加载到虚拟机中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。
note:每个class类不管创建多少实例对象,在JVM中都对用同一个Class对象。
java反射包java.lang.reflect中所有的类都没有public构造方法,要想获取这些类的实例,只能通过Class类来获取,如果想使用反射,就必须获得Class对象
1.1通过对象实例获取对应Class对象Object.getClass()----基本类型不能使用这样的方法。
//Returns the Class for String
Class c = "foo".getClass();
enum E { A, B }
//Returns the Class corresponding to the enumeration type E.
Class c = A.getClass();
byte[] bytes = new byte[1024];
//Returns the Class corresponding to an array with component type byte.
Class c = bytes.getClass();
Set<String> s = new HashSet<String>();
//Returns the Class corresponding to java.util.HashSet.
Class c = s.getClass();
1.2通过类的类型获取Class对象,基本类型也可以使用这种方法
//The `.class` syntax returns the Class corresponding to the type `boolean`.
Class c = boolean.class;
//Returns the Class for String
Class c = String.class;
1.3通过类的全限定名获取Class对象,基本类型无法使用此方法
Class c = Class.forName("java.lang.String");//通过Class.forName()方法加载的类,采用的是系统类加载器
//对于数组比较特殊
Class cDoubleArray = Class.forName("[D"); //相当于double[].class
Class cStringArray = Class.forName("[[Ljava.lang.String;"); //相当于String[][].class
1.4 基本类型和void类型的包装类可以使用Type字段来获取
Class c = Double.TYPE; //等价于 double.class.
Class c = Void.TYPE;
1.5通过Class获取类修饰符和类型
二、Member
反射定义了接口java.lang.reflect.Member,这个接口被java.lang.reflect.Field,java.lang.reflect.Method和java.lang.reflect.Constructor实现
类成员主要就包括构造函数、类变量和方法,而Member的这三个实现类就分别对应它们
java.lang.reflect.Field :对应类变量
java.lang.reflect.Method :对应类方法
java.lang.reflect.Constructor :对应类构造函数
反射就是通过这三个类才能在运行时改变对象状态。
1、突破java的权限检测
java运行时会进行访问权限检查,private类型的变量无法进行直接访问。
java.lang.reflect.AccessibleObject
AccessibleObject为我们提供了一个方法 setAccessible(boolean flag),该方法的作用就是可以取消 Java 语言访问权限检查。所以任何继承AccessibleObject的类的对象都可以使用该方法取消 Java 语言访问权限检查。
所以任何继承AccessibleObject的类的对象都可以使用该方法取消java语言的访问权限检查(final类型的变量也可以通过这种办法访问)
public final class Field extends AccessibleObject implements Member
Field、Method和Constructor都是继承AccessibleObject
2.Field
通过Field你可以访问给定对象的类变量,包括获取变量的类型、修饰符、注解、变量名、变量的值或者重新设置变量值,即使变量是private的。
2.1获取Field
Class提供了4种方法获得给定类的Field。
获取指定的变量(只要是声明的变量都能获得,包括private)
获取指定的变量(只能获得public的)
获取所有声明的变量(包括private)
获取所有的public变量
获取变量类型、修饰符、注解
public void testField(){
Class c = Cat.class;
Field[] fields = c.getDeclaredFields();
for(Field f : fields){
StringBuilder builder = new StringBuilder();
//获取名称
builder.append("filed name = ");
builder.append(f.getName());
//获取类型
builder.append(" type = ");
builder.append(f.getType());
//获取修饰符
builder.append(" modifiers = ");
builder.append(Modifier.toString(f.getModifiers()));
//获取注解
Annotation[] ann = f.getAnnotations();
if (ann.length != 0) {
builder.append(" annotations = ");
for (Annotation a : ann){
builder.append(a.toString());
builder.append(" ");
}
} else {
builder.append(" -- No Annotations --");
}
Log.d(TAG, builder.toString());
}
}
获取、设置变量值
public void testField(){
Cat cat = new Cat("Tom", 2);
Class c = cat.getClass();
try {
//注意获取private变量时,需要用getDeclaredField
Field fieldName = c.getDeclaredField("name");
Field fieldAge = c.getField("age");
fieldName.setAccessible(true);
//反射获取名字, 年龄
String name = (String) fieldName.get(cat);
int age = fieldAge.getInt(cat);
Log.d(TAG, "before set, Cat name = " + name + " age = " + age);
//反射重新set名字和年龄
fieldName.set(cat, "Timmy");
fieldAge.setInt(cat, 3);
Log.d(TAG, "after set, Cat " + cat.toString());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
2.2 获取Method
①获取Method
Class依然提供了4种方法获取Method:
getDeclaredMethod(String name, Class<?>... parameterTypes)
根据方法名获得指定的方法, 参数name为方法名,参数parameterTypes为方法的参数类型,如 getDeclaredMethod(“eat”, String.class)
getMethod(String name, Class<?>... parameterTypes)
根据方法名获取指定的public方法,其它同上
获取所有声明的方法
获取所有的public方法
Note:获取带参数的方法时,如果参数类型错误会报NoSuchMethodException,对于参数是泛型的情况,泛型须当成Object处理(Object.class)
②获取方法的返回类型
2.3 Constructor
获取构造方法
和Method一样,Class也为Constructor提供了4种方法获取
getDeclaredConstructor(Class<?>... parameterTypes)
获取指定构造函数,参数parameterTypes为构造方法的参数类型
getConstructor(Class<?>... parameterTypes)
获取指定public构造函数,参数parameterTypes为构造方法的参数类型
获取所有声明的构造方法
获取所有的public构造方法
创建对象
通过反射有两种方法可以创建对象:
java.lang.reflect.Constructor.newInstance()
一般来讲,我们优先使用第一种方法;那么这两种方法有何异同呢?
- Class.newInstance()仅可用来调用无参的构造方法;Constructor.newInstance()可以调用任意参数的构造方法
- Class.newInstance()会将构造方法中抛出的异常不作处理原样抛出;Constructor.newInstance()会将构造方法中抛出的异常都包装成InvocationTargetException抛出。
- Class.newInstance()需要拥有构造方法的访问权限;Constructor.newInstance()可以通过setAccessible(true)方法绕过访问权限访问private构造方法。