什么是反射
反射简单来说就是根据配置文件生成相应的对象实例,在不修改源码的情况下,仅通过更改配置文件生成不同的对象。在反射中方法也视为对象。
假设有如下的配置文件:
ClassPath = com.cat
通过properties读取的配置文件,得到的返回类型是字符串,这里假设用变量A来接收字符串。此时A就是“com.Cat”这个字符串。但我们无法用new A();的方式创建cat对象,因为new后面需要跟类名而A只是一个保存了字符串的变量。所以通过properties读取配置文件我们只能隐性的得到类名对应的字符串,而无法明确类名是什么,也就无法创建对象。
同样的在调用方法时,并不知道方法名,只有一个保存了方法名的字符串变量。而反射机制就提供了可以通过“保存了方法名的变量”实例化方法的渠道(把方法看做对象),然后再通过方法的对象来调用方法。
代码写完先进行编译,然后在类加载时会自动生成一个class类型的类(类名就叫class)对应上图中间的。这个class类型的类保存了初始化类的所有完整信息,每个信息又可以看成一个单独的类。反射主要的类有:
java.long.class :此类实例化后代表,class类对应的类加载后的对象。
java.long.reflect.Methord:此类实例化后代表该反射对应类的某个方法。
java.long.reflect.Filed:此类实例化后代表反射对应类的成员变量。
java.long.reflect.Constructor:此类实例化后代表反射对应类的构造器。
class类相当于一面镜子,通过这个镜子可以看到原类的所有结构,然后通过它来创建对象、调用方法。一个类无论实例化多少个对象都只会产生一个class类型的类。
class类
class类不是new出来的是系统自动创建的。
每个对象实例都知道自己是哪个class类关联的。
class常用方法
其中创建对象实例方法在JDK18后弃用,变成cls.getDeclaredConstructor().newInstance();
获取对应class对象的方法(不同阶段不同方法)
方法一:多用于从配置文件读取了类的全路径后加载类
直接用class的静态方法forName()获取:Class cls = Class.forName("全类名”);
方法二:已知具体类,直接通过类的Class获取,多用于参数传递,效率最高最安全可靠
已知类Cat获取它的Class类:Class cls = Cat.class;
方法三:已知某个类的实例,直接调用该类的getClass()方法获取Class对象
通过创建好的对象获取Class:Class cls = 对象.getClass();
方法四:通过类加载器获得Class对象,也需要类的全路径
Class cls = classLoader.loadClass("类的全路径”)
无论获取多少个Class对象其实都是同一个Class对象。
通过反射获取类相关信息的方法
通过反射创建对象
第一步先获取类对应的Class对象。
法一:调用类中public修饰的无参构造器。
法二:调用其他构造器
主要通过Class类相关方法以及Constructor类相关方法实现:
爆破:暴力破解,使用反射可以访问private修饰的东西。XXXX.setAccessible(true);
通过反射操作属性
首先根据属性名获取Field对象:
Field f = class对象.getDeclaredField(属性名); //可以得到所有类型的属性
如果是private类型的可以用爆破:f.setAccessible(true);
然后用拿到的属性类去访问/设置值:
f.set(O,值) 其中O是通过反射创建的对象
syso(f.get(O));
如果是静态属性,则set和get中的O可以写成null。
通过反射操作方法
根据方法名和参数列表获取Methord方法对象
Methord m = class.getDeclaredMethord(方法名,XXX.class);得到本类所有方法,XXX是形参类型
获取对象
Object o = class.newInstance();
爆破
m.setAccessible(true);
访问
Object returnValue = m.invoke(O,实参列表);如果是静态方法O可以是null
应该运用实例:通过反射修改已知类的属性并调用方法输出
public class Homework16 {
public static void main(String[] args) throws Exception {
//拿到class对象
Class aClass = Class.forName("Reflect.PrivateTest");
//通过调用默认构造器class对象实例化
Constructor constructor = aClass.getDeclaredConstructor();
Object pt = constructor.newInstance();
//通过class对象得到属性对象
Field name = aClass.getDeclaredField("name");
//爆破
name.setAccessible(true);
//设置属性
name.set(pt,"abc");
//通过class对象得到方法
Method getName = aClass.getDeclaredMethod("getName");
//实现方法
Object invoke = getName.invoke(pt);
System.out.println(invoke);
}
}
class PrivateTest {
private String name = "Helllo Kitty" ;
public String getName(){
return name;
}
}
类加载
类加载分为静态加载和动态加载
一般的类,只要在代码中写了,无论运行代码时会不会用到该类在编译时都会加载,如果相关类有问题就会报错。这称为静态加载。如在switch某个分支写了new dog();这个语句但是没有定义dog这个类时,即使运行时不会走该分支在编译时也会报错。静态加载依赖性比较强
如果使用反射创建类而不是直接new,则会动态加载。即在代码运行时如果用到这个类了才会加载,编译时并不会直接加载。同样在switch语句某一个分支用反射实例化了一个dog类,只要运行时没走到这个分支也就不会用到dog类,也就不会报错。