java 反射全面总结

反射总结
慕课网 反射的视频

什么是反射

反射是能够让java代码访问一个已经加载的类的字段,变量,方法和构造器等信息,并能够访问他们不受访问权限的控制,即能访问私有构造方法属性等

什么是Class

Class
因为 Java 是面向对象的语言,基本上是以类为基础构造了整个程序系统,反射中要求提供的规格说明书其实就是一个类的规格说明书,它就是 Class。
注意的是 Class 是首字母大写,不同于 class 小写,class 是定义类的关键字,而 Class 的本质也是一个类,因为在 Java 中一切都是对象。

Class 就是一个对象,它用来代表运行在 Java 虚拟机中的类和接口。
把 Java 虚拟机类似于高速公路,那么 Class 就是用来描述公路上飞驰的汽车,也就是我前面提到的规格说明书。

获取反射对象的三种方法

反射的入口是 Class,但是反射中 Class 是没有公开的构造方法的,所以就没有办法像创建一个类一样通过 new 关键字来获取一个 Class 对象

任何一个类都是Class类的实例对象,这个实例对象有三种表示方式:(我们新建一个Student类)
Class c1 = Student.class;//实际告诉我们任何一个类都有一个隐含的静态成员变量class(知道类名时用)
Class c2 = stu.getClass();//已知该类的对象通过getClass方法(知道对象时用) Student是一个类,stu是它的对象,通过 stu.getClass() 就获取到了 Car 这个类的 Class 对象
Class c3 = Class.forName("类的全名");//会有一个ClassNotFoundException异常
有时候,我们没有办法创建一个类的实例,甚至没有办法用 Student.class 这样的方式去获取一个类的 Class 对象。这在 Android 开发领域很常见,因为某种目的,Android 工程师把一些类加上了 @hide 注解,所示这些类就没有出现在 SDK 当中,Java 给我们提供了 Class.forName() 这个方法。
只要给这个方法中传入一个类的全限定名称就好了,那么它就会到 Java 虚拟机中去寻找这个类有没有被加载。
当我们执行System.out.println(c1==c2);语句,结果返回的是true,这是为什么呢?原因是不管c1还是c2都代表了Student类的类类型,一个类可能是Class类的一个实例对象。

我们完全可以通过类的类类型创建该类的对象实例,即通过c1或c2创建Student的实例。
Student stu = (Student)c1.newInstance();//前提是必须要有无参的构造方法,因为该语句会去调用其无参构造方法。该语句会抛出异常。

1.Class clazz = xxx.class 只会触发类的加载,不会触发初始化
2.Class clazz = new xxx().getClass()

  1. Class.forName(“全限定名称com.shadow.Hello”) 2,3方法都同时触发类的加载和初始化。。 全限定名称,它包括包名+类名

动态加载类和静态加载类

①.编译时加载类是静态加载类
new 创建对象是静态加载类,在编译时刻就需要加载所有可用使用到的类,如果有一个用不了,那么整个文件都无法通过编译
②.运行时加载类是动态加载类
Class c = Class.forName("类的全名"),不仅表示了类的类型,还表示了动态加载类,编译不会报错,在运行时才会加载,使用接口标准能更方便动态加载类的实现。
功能性的类尽量使用动态加载,而不用静态加载。
很多软件比如QQ,360的在线升级,并不需要重新编译文件,只是动态的加载新的东西

动态加载小例子

需求:在不知道的情况下,可能只加载World也有可能只加载Excel

package shadow.android.com.lib.reflected;

/**
 * Author : shadow
 * Desc : 动态加载类的基类
 * Date :2018/11/2/002
 */
public interface OfficeBase {
    public void start();
}

//==================================

package shadow.android.com.lib.reflected;

/**
 * Author : shadow
 * Desc :
 * Date :2018/11/2/002
 */
public class World implements OfficeBase {
    @Override
    public void start() {
        System.out.println("hello shadow,this is world");
    }
}

//============================

package shadow.android.com.lib.reflected;

/**
 * Author : shadow
 * Desc :
 * Date :2018/11/2/002
 */
public class Excel implements OfficeBase {
    @Override
    public void start() {
        System.out.println("hello shadow,this is excel!!!");
    }
}

静态加载如下:静态加载要求编译时期World或者Excel必须都在,然后根据情况判断来决定是否要使用,不然编译时期就会出错

package shadow.android.com.lib.reflected;

/**
 * Author : shadow
 * Desc :静态加载测试
 * Date :2018/11/2/002
 */
public class StaticLoaderTest {
    public static void main(String[] args) {
        if ("world".equals(args[0])) {
            World world = new World();
            world.start();
        }

        if ("excel".equals(args[0])) {
            Excel excel = new Excel();
            excel.start();
        }
    }
}

动态加载如下:不要求在编译时刻,World和Excel同时存在,只需要在加载的时候,根据需要去加载需要Class,如果加载不到,会抛出ClassNotFoundException

package shadow.android.com.lib.reflected;

/**
 * Author : shadow
 * Desc :动态加载测试
 * Date :2018/11/2/002
 */
public class DynamicLoaderTest {
    public static void main(String[] args){
        try {
            //动态加载类,
           // Class clazz = Class.forName(args[0]);
            Class clazz = Class.forName("shadow.android.com.lib.reflected.World");
            //由类类型,创建类的实例
            OfficeBase officeBase = (OfficeBase) clazz.newInstance();
            //扩展类
            officeBase.start();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

动态加载类测试.png

class对象的操作

如果你想得到一个类的信息,首先就要获取该类的类类型。
拿到class对象之后,可以操作一下几类对象,比如类的实现接口,类的变量,类的方法,类的构造器等,class类提供了对应的方法来访问
getName() 返回类类型的名字
getSuperClass() 返回继承的父类对象
getInterfaces() — 返回当前类实现的所有接口(不包括从父类继承来的)
getClasses() — 返回当前类和从父类继承来的public内部类
getDeclaredClasses() — 返回当前类的所有内部类(包括private类型,但是不包括从父类继承来的)
getConstructors() — 返回当前类所有的public构造器
getDeclaredConstructors() — 返回当前类所有的构造器(包括private类型)
getConstructor(Class… parameterTypes) — 根据参数,返回最匹配的构造器对象
getMethods() — 返回当前类和从父类继承来的所有public方法
getDeclaredMethods() — 返回当前类所有的Method方法(包括private类型)
getDeclaredMethod(String name, Class… parameterTypes) — 根据参数,返回最匹配的方法
getFields() — 返回当前类和从父类继承来的public字段
getDeclaredFields() — 返回当前类定义的所有字段(包括private)
getDeclaredField(String name) —返回当前类定义的字段通过参数
Class类api 有很多,需要用时可以查看api文档


1.如何获取某个方法
   方法的名称和方法的参数列表才能唯一决定某个方法
   Method m = c.getDeclaredMethod("方法名",可变参数列表(参数类型.class))
2.方法的反射操作
   m.invoke(对象,参数列表)
   方法如果没有返回值,返回null,如果有返回值返回Object类型,然后再强制类型转换为原函数的返回值类型

通过反射,了解泛型只在编译时期有效


ArrayList list1 =newArrayList();
ArrayList list2 =newArrayList();

Class c1 = list1.getClass();
Class c2 = list2.getClass();

System.out.println(c1==c2);//结果为true,为什么??

结果分析:因为反射的操作都是编译之后的操作,也就是运行时的操作,c1==c2返回true,说明编译之后集合的泛型是去泛型化的。
那么我们就可以理解为,Java集合中的泛型,是用于防止错误类型元素输入的,比如在list2中我们add一个int,add(10)就会编译报错,那么这个泛型就可以只在编译阶段有效,通过了编译阶段,泛型就不存在了。可以验证,我们绕过编译,用反射动态的在list2中add一个int是可以成功的,只是这时因为list2中存储了多个不同类型的数据(String型,和int型),就不能用for-each来遍历了,会抛出类型转换错误异常ClassCastException。

例子实现

需求:现在有个Apple类,它继承于Fruit类,Fruit有一个私有方法seal 参数是一个float类型, 现在要求我们通过反射来调用该私有方法

Fruit.class

public class Fruit {
    private int price;

    public Fruit(int price) {
        this.price = price;
    }

    private void sale(int price) {
        System.out.println("hello shadow ,fruit " + price);
    }
}

Apple.class

package shadow.android.com.lib.reflected;
public class Apple extends Fruit{
    public Apple(int price) {
        super(price);
    }
}

反射类

package shadow.android.com.lib.reflected;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Author : shadow
 * Desc :
 * Date :2018/11/2/002
 */
public class TestReflected {
    public static void main(String[] args) {
        invokeFruitSale();
    }

    private static void invokeFruitSale() {
        try {
            //获取Apple类
            Class clazz = Class.forName("shadow.android.com.lib.reflected.Apple");
            //获取Apple的直接父类Fruit
            Class fruitClass = clazz.getSuperclass();

            //获取fruit的私有方法
            Method saleMethod = fruitClass.getDeclaredMethod("sale", int.class);
            saleMethod.setAccessible(true);

            //获取父类的私有构造器
            Constructor constructor = fruitClass.getDeclaredConstructor(int.class);
            //设置私有可访问
            constructor.setAccessible(true);
            //通过私有构造器新建Fruit对象
            Fruit fruit = (Fruit) constructor.newInstance(0);


            //调用方法
            saleMethod.invoke(fruit, 100);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}

例子实现2

这次我们在Fruit类中新建一个Size类,然后新增一个final的Size常量,我们需要利用反射对其进行修改(正常final常量初始化之后是不能够被修改的,但是利用反射能够做到-。-)

fruit类

package shadow.android.com.lib.reflected;

/**
 * Author : shadow
 * Desc :
 * Date :2018/11/2/002
 */
public class Fruit {
    private int price;
    private final Size size = new Size(50);

    public Fruit(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "fruit size is : " + size;
    }

    private void sale(int price) {
        System.out.println("hello shadow ,fruit " + price);
    }

    public static class Size {
        private int count;

        public Size(int count) {
            this.count = count;
        }

        @Override
        public String toString() {
            return "size = " + count;
        }
    }
}

执行反射的类

   private static void modifyFruitSize() {
        try {
            Class clazz = Class.forName("shadow.android.com.lib.reflected.Apple");
            Class fruitClass = clazz.getSuperclass();
            Constructor constructor = fruitClass.getDeclaredConstructor(int.class);
            constructor.setAccessible(true);
            Fruit fruit = (Fruit) constructor.newInstance(0);
            //修改之前打印fruit size
            System.out.println("before modify fruit size" + fruit);

            //获取私有变量
            Field sizeField = fruitClass.getDeclaredField("size");
            sizeField.setAccessible(true);
            //我们利用反射将size改为非final类型
            Field modifierField = sizeField.getClass().getDeclaredField("modifiers");
            modifierField.setAccessible(true);
            //修改类型
            int modifiers = sizeField.getModifiers() & ~Modifier.FINAL;
            modifierField.set(sizeField, modifiers);
            //设置新的size
            Fruit.Size size = new Fruit.Size(500);
            sizeField.set(fruit, size);
            System.out.println("after modify fruit size" + fruit);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

例子3 Android Activity 启动 Hook。

Android Activity 启动 Hook

当然关于Android Activity 启动 Hook的技术网上很多,也有很多很优秀的开源项目,我在这里谈这个有点 关公面前耍大刀的感觉了,但是我在这里谈这个只是为了说明 利用反射 我们可以做很多事情,
所以我就找了这么一个切入点来展示反射能做什么事情,权当抛砖引玉。
我们的需求是,在启动activity时,打印一个Log日志,废话不多说,我们开始。
我们这里只Hook Activity.startActivity 这种方式的启动, 看下android framework的相关源码


public void startActivity(Intent intent) {
    //常用的activity.startActivity方法
    this.startActivity(intent, null);
}
//...

@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
     if (options != null) {
        startActivityForResult(intent, -1, options);
     } else {
         // Note we want to go through this call for compatibility with
         // applications that may have overridden the method.
         startActivityForResult(intent, -1);
     }
}

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
        @Nullable Bundle options) {
     if (mParent == null) {
         options = transferSpringboardActivityOptions(options);
         Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
         //...
     }
     //...
}

private Instrumentation mInstrumentation;
我们看到activity.startActivity方法,最终去执行启动操作会用到mInstrumentation这个私有成员变量,所以自然想到它是一个很好的Hook点,分下面三步来走

第一步,先获取到该Activity的mInstrumentation
第二步,新建一个新的Instrumentation类,重写execStartActivity方法,在执行父类的方法之前加入我们需要的Log日志
第三步,将我们新建的新的Instrumentation对象,设置给activity
第一步


public static void hook(Activity activity) {
    try {
        Field instrumentationField = Activity.class.getDeclaredField("mInstrumentation");
        instrumentationField.setAccessible(true);
        //...
    }
}

第二步


public class HookInstrumention extends Instrumentation{

    private Instrumentation mTarget;

    public HookInstrumention(Instrumentation target) {
        this.mTarget = target;
    }

    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
                                  Intent intent, int requestCode, Bundle options) {
        L.d("before start activity!");
        Class superClass = Instrumentation.class;
        try {
            Method method = superClass.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            method.setAccessible(true);
            return (ActivityResult) method.invoke(this.mTarget, who, contextThread, token, target, intent, requestCode, options);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}

第三步


public static void hook(Activity activity) {
    try {
        Field instrumentationField = Activity.class.getDeclaredField("mInstrumentation");
        instrumentationField.setAccessible(true);
        Instrumentation instrumentation = (Instrumentation) instrumentationField.get(activity);
        HookInstrumention hookInstrumention = new HookInstrumention(instrumentation);
        instrumentationField.set(activity, hookInstrumention);
    } catch (NoSuchFieldException e) {
         e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
}

//测试一下:
HookUtil.hook(this);
startActivity(new Intent(MainActivity.this, SecondActivity.class));

//查看log
08-27 14:43:33.391 10298-10298/com.aliouswang.practice.olympic E/hook: before start activity!
08-27 14:43:33.392 10298-10298/com.aliouswang.practice.olympic I/Timeline: Timeline: Activity_launch_request time:427958941 intent:Intent { cmp=com.aliouswang.practice.olympic/.SecondActivity }
可以看到我们在Activity 启动之前,成功的打印了一条日志!!

总结反射

Java 中的反射是非常规编码方式。
Java 反射机制的操作入口是获取 Class 文件。 有 Class.forName()、 .class 和 Object.getClass() 3 种。
获取 Class 对象后还不够,需要获取它的 Members,包含 Field、Method、Constructor。
Field 操作主要涉及到类别的获取,及数值的读取与赋值。
Method 算是反射机制最核心的内容,通常的反射都是为了调用某个 Method 的 invoke() 方法。
通过 Class.newInstance() 和 Constructor.newInstance() 都可以创建类的对象实例,但推荐后者。因为它适应于任何构造方法,而前者只会调用可见的无参数的构造方法。
数组和枚举可以被看成普通的 Class 对待。

凡事有得必有失,反射也有它的缺点,反射的缺点主要有2点。

我们通过反射获得了灵活性,同时也要付出代价,我们会失去编译器优化我们代码的机会,这样我们的代码执行效率会低一些,但是随着JDK版本的不断升级,性能差距在不断的缩小。
反射打破了我们代码的封装性,增加了维护成本。

你可能感兴趣的:(java 反射全面总结)