反射引入
根据现有的条件,创建一个类对象并使用该对象的方法(类给了文件路径)
以往的方法是new对象
实战需求:在不修改程序本身源码的情况,通过外部文件配置,控制程序,符合设计模式OCP原则(不修改源码扩展功能)
反射快速入门
//从配置文件中读取需要的对象以及方法
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classFullPath = properties.get("Class").toString();
String methodName = properties.get("Method").toString();
Class aClass = Class.forName(classFullPath);
//得到一个指定类的Class对象
Object o = aClass.newInstance();
//通过这个Class对象得到指定类的实例对象
Method method = aClass.getMethod(methodName);
//通过这个指定类的Class对象得到配置文件中指定的方法
method.invoke(o);
//调用这个方法,基于传入的o类
Java Reflection
允许程序在执行期借助ReflectionAPI取得任何类的内部信息
- 当加载完类之后,在堆中就产生一个Class类型的对象(一个类对应一个对象),对象包含了类的完整结构信息,通过这个对象得到类的结构,就像一个蓝图,通过这个Class类对象看到类的结构即反射
个人理解:
类加载阶段,将 java类 编译后的 Class文件 加载到 方法区,此时是Class类的实例对象 (一种数据结构,存放Cat类的 成员变量,构造器等…)
运行阶段,实例化一个Cat类,是从方法区中的 存放Cat类的Class对象中,得到一个Cat类的对象,存放在堆区,其对象引用存放在对应线程的栈区
反射各种类和方法
Class aClass = Class.forName(classFullPath);
Object o = aClass.newInstance();
Method method = aClass.getMethod(methodName);
method.invoke(o);
//得到成员对象
//getField不能得到私有属性
Field age = aClass.getField("age");
System.out.println(age.get(o));
//得到构造器
Constructor constructor = aClass.getConstructor();
System.out.println(constructor);
//有参构造器需要写参数的class类的字节码文件
Constructor constructor1 = aClass.getConstructor(String.class);
System.out.println(constructor1);
反射的优缺点
优点:动态创建和使用对象(框架核心),使用灵活
缺点:使用反射后是解释执行,影响执行速度
想要优化速度,则需要设置setAccessible,方法/成员变量/构造函数 都有这个方法
true表关闭检查,优化就一点点…
类加载器
- Class类不是用户创建,是由系统创建
当第一次使用new去一个类时,首先调用类加载器ClassLoader类的构造器
使用反射实例化一个类,第一次同样会进行类加载
同一个类对象只有一次!
Class aClass = Class.forName(classFullPath);
Class aClass1 = Class.forName(classFullPath);
System.out.println(aClass1.equals(aClass));
//同一个Class,只是引用不同,实际上都一样
- 通过Class 类对象,可以通过各种api方法得到类的完整结构
几种方式获取Class对象
//得到 指定类 的 Class类对象
//1. 已知全类名,并在该类路径,通过CLass类静态方法
Class<?> aClass = Class.forName("com.lgj.Cat");
System.out.println(aClass.getName());
//2. 类名.class,多用于参数传参
System.out.println(Cat.class.getName());
//3.通过已知实例对象反向拿到其Class对象
Cat cat = new Cat();
Class<? extends Cat> aClass1 = cat.getClass();
System.out.println(aClass1.getName());
//4.通过 四种 类加载器获取(在加载阶段)ClassLoader
ClassLoader classLoader = cat.getClass().getClassLoader();
Class<?> aClass2 = classLoader.loadClass("com.lgj.Cat");
System.out.println(aClass2.getName());
//基本数据类型直接通过.class得到
Class<Integer> integerClass = int.class;
System.out.println(integerClass.getName());
//基本数据类型的 包装类! 通过.TYPE得到
Class<Boolean> type = Boolean.TYPE;
System.out.println(type.getName());
类的静态/动态加载
类加载被激活的时间
1.当创建对象
2.子类对象被加载
3.调用类中静态成员
4.反射(动态)
举例:在有些让用户选项的的地方,部分的类是不需要被加载的,比如开始游戏时选择游戏类,有些时候用户只会选择一个类,其他类用不上,若是写死了new对象,则编译时就必须要编译通过
动态加载(反射),只有在真正用到的时候才需要加载类
类加载
类加载分为五个阶段
下面是对各阶段步骤的讲解
首先一部就是加载阶段,很好理解,将磁盘中的字节码文件 载入 内存中
连接阶段 验证 会加载一个SecurityManager管理,对引用文件的验证
连接阶段 - 准备 仅仅只给 静态 遍历赋值,进行第一次初始化 基本数据类型将会变成其最原始初始值(常量则直接赋真实设定值)
实例属性不会分配空间
连接阶段 - 解析
常量池落地,此时引用全变为直接引用(从符号引用 落地 内存地址)
Initialization初始化
此时还在类加载,所以实例属性仍然不操作
此时clinit会收集类中的所有静态属性/方法,并运行(按照代码写的顺序)
并且jvm对clinit方法设定了同步机制,正因如此,多线程操作类加载也不会出现问题,也只有一个Class
通过反射获取类结构信息
第一组:Class类
第二组 属性
第三组 Method类
补充:
- 返回修饰符的等级的组合关系是相加
- 返回的数据类型,是方法返回值的对应的Class类对象
第四组 构造器
反射创建对象
public static void main(String[] args) throws Exception {
Class<?> catClass = Class.forName("com.lgj.Cat");
//1 通过Class类对象的方法,直接使用无参构造器创建实例
Object cat1 = catClass.newInstance();
System.out.println(cat1);
//2. 通过public有参构造器创建实例对象
Constructor<?> constructor = catClass.getConstructor(int.class);
Object cat2 = constructor.newInstance(520);
System.out.println(cat2);
//3.通过得到 所有的构造器,传参筛选出需要的那一个构造器
//得到构造器后需要设置setAccessible(true)爆破使用
Constructor<?> declaredConstructor = catClass.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
Object cat3 = declaredConstructor.newInstance("暴力破解猫", 9999);
System.out.println(cat3);
}
public static void main(String[] args) throws Exception {
Class<?> catClass = Class.forName("com.lgj.Cat");
//通过反射获得 实例变量
Cat cat1 = (Cat) catClass.newInstance();
//通过反射获得属性
Field field = catClass.getField("age");
field.set(cat1,999);
System.out.println(field.get(cat1));
//4.反射得到 静态私有属性
//操作 私有 成员,创建对象时需要使用getDeclared,并且设置setAccessible
Field field1 = catClass.getDeclaredField("secrete");
field1.setAccessible(true);
System.out.println(field1.get(null));//因为是静态属性才使用这个
//反射拿私有方法并调用
Method hi = catClass.getDeclaredMethod("Hi", String.class);
hi.setAccessible(true);
hi.invoke(cat1,"黄明俊");
}
- 在反射中,方法有返回值,统一是Object
反射的两道作业
Class<PrivateTest> privateTestClass = PrivateTest.class;
PrivateTest privateTest = privateTestClass.newInstance();
Field name = privateTestClass.getDeclaredField("name");
name.setAccessible(true);
name.set(privateTest,"黄明俊");
System.out.println(privateTest.getName());
Class<?> fileClass = Class.forName("java.io.File");
Constructor<?>[] declaredConstructors = fileClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
Constructor<?> constructor = fileClass.getConstructor(String.class);
Object o = constructor.newInstance("d:\\test.txt");
//千万别强转...失去反射的意义了
Method method = fileClass.getMethod("createNewFile", null);
method.invoke(o,null);
System.out.println("文件创建成功");