反射机制
提出一个问题
如何根据外部文件配置,指定对象和方法,在不修改源码的情况下,控制程序(符合开闭原则)
// 使用反射机制解决
//(1) 加载类, 返回 Class 类型的对象 cls
Class cls = Class.forName(classfullpath);
//(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance();
System.out.println("o 的运行类型=" + o.getClass()); //运行类型是Cat
//(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象
//在反射中, 可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4) 通过 method1 调用方法: 即通过方法对象来实现调用方法
method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)
反射机制是什么
反射机制允许程序在执行期间,借助ReflectionAPI获取任何类的内部信息,比如成员变量,构造器,成员方法等,并能操作对象的属性和方法。反射在设计模式和底层框架中都会用到。
反射机制原理
加载完类后,在堆中产生了一个Class类型的对象,一个类对应一个对象。这个对象包含了类的完整结构信息,通过这个对象可以获得类的结构,这个Class对象就像一面镜子,透过镜子可以看到类的完整信息,所以,形象的称为反射。
原理图:
| 编译阶段 | Class类加载阶段 | 运行阶段 |
|---|---|---|
| javac将代码编译成字节码文件 | 类加载器(ClassLoader)将字节码放在堆 | -->new Cat() |
| Cat.java --> Cat.class | --> Class对象 | <-- cat找Class对象 |
反射机制的作用
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员方法
- 在运行时调用任意一个对象的成员变量和方法
反射机制相关类
java.lang.Class:Class对象表示类加载后在堆中的对象java.lang.reflect.Method:Method对象表示类的方法java.lang.reflect.Field:Field对象表示类的成员变量
Field nameField = cls.getField("age"); // 不能得到私有的成员变量
System.out.println(nameField.get(o)); // 传统写法 对象.成员变量 , 反射 : 成员变量对象.get(对象)
java.lang.reflect.Constructor:Constructor对象表示类的构造器
Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
System.out.println(constructor);//Cat()
Constructor constructor2 = cls.getConstructor(String.class);
// String.class是String类的Class 对象
System.out.println(constructor2);//Cat(String name)
这些类在java.lang.reflection
反射机制的优点和缺点
- 优点:可以在运行时动态创建和使用对象,没有反射机制,框架失去底层技术支撑
- 缺点:反射基本是解释执行,所以执行速度比较慢
优化
- 关闭访问检查
- Method\Field\Constructor都有
setAccessible(boolean)方法,调用之前设置为true
优化前:
Class cls = Class.forName("com.hspedu.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
hi.setAccessible(true);//在反射调用方法时,取消访问检查
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);//反射调用方法
} lo
ng end = System.currentTimeMillis();
System.out.println("m3() 耗时=" + (end - start));
Class类
基本概念
- Class类对象不是new出来的,而是系统创建的。对于某个类的Class类对象,在内存中只有一份,因为类只加载一次。
// 反射方式
Class acat = Class.forName("com.hsp.Cat");
// 仍然通过ClassLoader加载对象
// ClassLoader.loadClass(String name) name:"com.hsp.Cat"
Class cat1 = Class.forName("com.hsp.Cat")和Class cat2 = Class.forName("com.hsp.Cat"),cat1和cat2是同一个对象
- 每个实例都会有对应的Class类对象的信息,因此可以利用Class对象,得到一个类的完整结构
- Class类是存放在堆的,类的二进制字节码是存放在方法区的,也称为类的元数据
常用方法
static Class forName(String name)
返回Class类,输出结果如下图
直接输出? 会显示是哪个类的Class对象
cls.getClass()Class com.hsp.Car
如何得到运行类型?
cls.getClass()Class java.lang.Class
如何得到包名?
cls.getPackage().getName()com.hsp
如何得到全类名?
cls.getName()com.hsp.Car
Object newInstance()创建对象
如何得到对象属性,并设置属性值?
getField() 如何获取对象的所有属性?
获取Class类对象
代码/编译阶段: forName()
案例:Class cls = Class.forName("全类名")
应用场景:配置文件,读取全路径加载类
类加载阶段: 类.class
应用场景:参数传递,比如通过反射获得对象的构造器对象
运行阶段 对象.getClass()
应用场景:通过创建好的对象,获取Class对象
特殊情况
- 通过ClassLoader:
ClassLoader cl = 对象.getClass().getClassLoader();
Class cls = cl.loadClass("全类名") - 基本数据类型:
Class<Integer> cls = int.class - 基本数据类型对应的包装类:
Class<Integer> cls = Integer.TYPE
哪些类型有Class对象
1、 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
2、 接口interface
3、 数组
4、 枚举enum
5、 注解annotation
6、 基本数据类型
7、 void
Class<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
//枚举
Class<Thread.State> cls6 = Thread.State.class;
Class<Long> cls7 = long.class;//基本数据类型
Class<Void> cls8 = void.class;//void 数据类型
Class<Class> cls9 = Class.class;
- 输出:sout(cls)