反射机制(2)---反射的使用

849 阅读8分钟

前言

在我们使用java开发Android项目的过程中,会发现大量的框架都使用了反射。反射机制允许我们在运行态中对任意一个类进行实例化、调用方法、得到或者设置变量值。这种动态获取信息以及动态调用对象方法的能力称为java语言的反射机制。反射是一种与类进行动态交互的机制,java虽然是静态语言,但却可以利用反射实现一些动态语言才有的功能(程序运行时,允许改变程序结构或者变量类型,这种语言被称为动态语言)。

在Android的框架层也能见到反射的身影,比如AMS启动Activity的核心就是使用反射机制,使用的Activity对象就是通过反射来构建的。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        ...

最终调用instantiateActivity,通过cl.loadClass(className).newInstance()反射创建实例

public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
            @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (Activity) cl.loadClass(className).newInstance();
    }

反射能实现哪些功能

  • 获取一个对象的类信息
  • 在运行时构造任意一个类对象
  • 在运行时判断任意一个对象所属的类
  • 在运行时获取和调用任意一个类所具有的成员方法和变量
  • 在运行时获取和调用任意一个对象的成员变量和方法
  • 获取一个类的访问修饰符(public、private)、成员、方法、注解、超类等信息
  • 获取属于一个接口的常量和方法声明
  • 利用反射动态加载类,支持热修复和插件化
  • 通过动态代理进行hook方法的执行

反射相关的主要类

  • java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  • java.lang.reflect.Method:代表类的方法,Method对象就表示某个类的方法
  • java.lang.reflect.Field:代表类的成员变量,Field对象就表示某个类的成员变量
  • java.lang.reflect.Constructor:代表类的构造方法

image.png

得到class对象

什么是Class对象,如何获取

  • Class也是类,也是继承自Object

  • Class类对象不是new出来的,而是系统创建的。

    传统方式new Cat() 使用classLoader加载

    使用Class.forName(),仍然通过ClassLoader进行加载

    @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName(className, true, ClassLoader.getClassLoader(caller));
    }
  • 对于某个类的Class对象,在内存中只能有一份,因为类只加载一次
  • 每个对象实例都会记得自己是由哪个Class实例所生成的
  • 通过Class可以完整的得到一个类的完整的结构
  • class对象是存放在堆上的
  • 类的字节码二进制数据,是放在方法区的,也称为类的元数据(方法代码、变量名、方法名、访问权限等)

JVM在加载类的时候,会为每个类生成一个独一无二的class对象。

  • Class.forName()---编译阶段获取
  • 类名.class ---- 加载阶段获取
  • 对象.getClass()--- 运行阶段获取
  • classLoader.loadClass()---类加载器得到class对象
  • 基本数据类型,可以通过基本数据类型.class获取
  • 基本数据类型的包装类,可以通过.type得到class类对象包装类.TYPE

1.已知一个类的全类名,并且该类在类路径下,可通过class类的静态方法forName获取,多用于配置文件来读取类全路径,再加载类

Class.forName(String className);

2.若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高Class cls = A.class,多用于参数传递,比如通过反射得到对应构造器对象

Class c = Person.class;

3.已知某个类的实例,调用该实例的getClass方法获取class对象Class cls = 对象.getClass()

Person p = new Person();
Class clazz = p.getClass();

4.通过类加载器获取到类的Class对象

哪些类型有Class对象

  • 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  • interface 接口
  • 数据
  • enum 枚举
  • annotation 注解
  • 基本数据类型
  • void

通过反射获取类的结构信息

可以获取到哪些结构信息?

  • getName---获取全类名
  • getSimpleName---获取简单类名
  • getFields---获取所有public修饰的属性,包含本类以及父类
  • getDeclaredFields---获取本类中所有的属性
  • getMethods---获取所有public修饰的方法,包含本类以及父类
  • getDeclaredMethods---获取本类中所有的方法
  • getConstructors---获取本类所有public修饰的构造器
  • getDeclaredConstructors---获取本类中所有的构造器
  • getPackage---以package形式返回包信息
  • getSuperClass---以Class形式返回父类信息
  • getInterfaces---以Class[]形式返回接口信息
  • getAnnotations---以Annotation[]形式返回所有注解信息

获取Field信息

java.lang.reflect.Field

  • getModifiers:以int形式返回修饰符(默认修饰符是0,public是1,private是2,protected是4,static是8,final是16)
  • getType:以Class形式返回类型
  • getName:返回属性名

获取Method

java.lang.reflect.Method

  • getModifiers:以int形式返回修饰符(默认修饰符是0,public是1,private是2,protected是4,static是8,final是16)
  • getReturnType:以Class形式获取返回类型
  • getName:返回方法名
  • getParameterTypes:以Class[]返回参数类型数组

通过反射创建对象

创建的方式

  1. 方式1:调用类中的public修饰的无参构造器
  • 获取类的Class对象
  • 调用获取到的Class对象的newInstance()来获取对象,返回一个Object类型的对象,需要强转。
//获取user的Class对象
Class<?> userClass = Class.forName("fanshe.User");
//通过无参构造创建对象
User newInstance = (User)userClass.newInstance();
  1. 方式2:调用类中的指定构造器
  • 获取类的Class对象
  • 调用getConstructor()获取一个Constructor对象
  • 通过Constructor的newInstance()方法,创建对象
//获取user的Class对象
Class<?> userClass = Class.forName("fanshe.User");
//通过public有参构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
User newInstance2 = (User)constructor.newInstance("abc");

第一种方式只能使用默认的构造方法,不能通过带有参数的构造方法创建对象。第二种方式则可以通过指定参数的构造函数来创建对象。

通过反射修改属性Field

//拿到private的name的属性值
Field name = userClass.getDeclaredField("name");
//可以改变私有属性值
name.setAccessible(true);
name.set(obj, "zhangsan");
String n = (String)name.get(obj);
System.out.println(n);

通过反射调用方法Method

 //1. 获取类中带有方法签名的方法,getMethod第一个参数为方法名,第二个参数为方法的参数类型数组
Method method = cls.getMethod("staticMethodName", new Class[] {double.class,String.class});
//invoke 方法的第一个参数是被调用的对象,这里静态方法传null,第二个参数为给将被调用的方法传入的参数
Object invoke = method.invoke(null, new Object[] {1.0,"abc"});
		
//如果方法是私有的private方法,按照上面的方法去调用则会产生异常NoSuchMethodException,这时必须改变其访问属性
//method.setAccessible(true);//私有的方法通过发射可以修改其访问权限
		
//2.非静态方法
Method method2 = cls.getMethod("methodName", new Class[] {double.class,String.class});
Object invoke2 = method2.invoke(new Cat(), new Object[] {2.0,"abcd"});
		
//3. 获取没有方法签名的方法print
Method method_3 = cls.getMethod("print", new Class[]{});
//result_3为null,该方法不返回结果
Object result_3 = method_3.invoke(new Cat(), null); 

使用发射的注意事项

性能问题

在编译的时候代码都会被编译器优化一次,这种优化会让代码性能更好,减少更多的开销。而反射的性能问题主要来自没法享受到编译器做的优化,这点在移动设备上更为明显。除此之外,反射在使用中会遍历类的Field和Method,这种遍历本身就有性能损耗,而且传参和调用的时候也会有语言层面的拆箱和装箱,这也是性能损耗的原因之一。

  • 优点:可以动态的创建和使用对象,使用灵活,没有反射机制,框架就失去低层支持
  • 缺点:使用反射基本是解释执行,对执行速度有影响

	public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
		
		m1();
		m2();
	}
	
	//普通调用
	public static void m1() {
		Cat cat = new Cat();
		long start = System.currentTimeMillis();
		for(int i = 0;i<900000;i++) {
			cat.hi();
		}

		long end = System.currentTimeMillis();
		System.out.println("普通调用"+(end-start));
	}
	
	//反射调用
	public static void m2() throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
		Class cat = Class.forName("reflect.Cat");
		Object newCat = cat.newInstance();
		Method method = cat.getMethod("hi");
		
		long start = System.currentTimeMillis();
		for(int i = 0;i<900000;i++) {
			method.invoke(newCat);
		}

		long end = System.currentTimeMillis();
		System.out.println("反射调用"+(end-start));
	}
普通调用4
反射调用26

优化:关闭访问检查

  • Method和Field、Constructor对象都有setAccessible方法
  • 作用是启动和禁用访问安全检查的开关
  • 设置为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查

结语

反射是一种非常实用的机制,在很多的框架中有广泛的使用,了解了反射的原理和API才能打开框架的大门,同样这也是Java/Android程序员必须要掌握的技能。接下来我还会深入反射的实际使用,比如动态代理的原理和解析。