反射总结
慕课网 反射的视频
什么是反射
反射是能够让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()
- 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();
}
}
}
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版本的不断升级,性能差距在不断的缩小。
反射打破了我们代码的封装性,增加了维护成本。