目录
1.需求引出反射
以前的方法是,通过 使用 Properties 类, 可以读写配置文件,然后创建对象,不行
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat"
String methodName = properties.get("method").toString();//"hi"
System.out.println("classfullpath=" + classfullpath);
System.out.println("method=" + methodName);
现在 使用反射机制解决
//(1) 加载类, 返回 Class 类型的对象 cls
Class cls = Class.forName(classfullpath);
//System.out.println(cls); 显示cls对象,是哪个类的Class对象 cat
//System.out.println(cls.getClass()); 输出cls的运行类型 java.lang.Class
//(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance(); //生成一个对象实例(Cat类型的对象) //Object可转为Cat类型
System.out.println("o 的运行类型=" + o.getClass()); //运行类型 Cat
//(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4) 通过 method1 调用方法: 即通过方法对象来实现调用方法
System.out.println("=============================");
method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象
2.反射机制原理图
类加载器部分,还有堆内对象知道自己属于哪个Class对象 体现了反射。
通过反射机制,可以完成:
(1)在运行时判断任意一个对象所属的类 堆内对象知道自己属于哪个Class对象
(2)在运行时构造任意一个类的对象
(3)在运行时得到任意一个类所具有的成员变量和方法 (类所具有的成员变量和方法 ) 存放在方法区里面
(4)在运行时调用任意一个对象的成员变量和方法
(5)生成动态代理 (多态)
反射主要类:
注意:Field 的getField方法不能得到私有的属性
Field nameField = cls.getField("age") //age 不是私有属性 是变量名 cls【某对象的Class对象】
cls.getField 拿到某对象的属性对象nameField — 该对象统筹所有属性,只要在括号中输入你想要的属性名
就可以返回该属性(并不能显示属性值),需要get(该对象 对象名)才得到属性值
例如:public int age = 10; 定义在Cat的age属性
Cat o1 = (Cat) clas.newInstance();Field field = cla.getField("age"); System.out.println(field); 结果:public int enum01.Cat.age System.out.println(field.get(o1)); 结果:10
//java.lang.reflect.Constructor: 代表类的构造方法, Constructor 对象表示构造器
Constructor constructor = cls.getConstructor();
//()中可以指定构造器参数类型, 返回无参构造器
有参构造器:
Constructor constructor2 = cls.getConstructor(String.class);
//String.class 就是 String 类的 Class 对象
3.反射的优缺点
优点:可以动态的创建和使用对象(框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
缺点:使用反射基本是解释执行,对执行速度有影响
如何优化???【关闭访问检查】 但是改进不大
例如:
Class cls = Class.forName("com.hspedu.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
hi.setAccessible(true);//在反射调用方法时,取消访问检查 false则表示执行访问检查
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++)
hi.invoke(o);//反射调用方法
long end = System.currentTimeMillis();
System.out.println("m3() 耗时=" + (end - start));
}
4.Class类
(1)Class也是类,因此继承Object类
(2)Class类对象不是new出来的,是系统创建的(ClassLoader 类)
(3)对于某个类的Class对象,在内存中(堆中)只有一份,只加载一次 【为什么???因为使用了线程同步】
(4)每个类的实例对象都会记得自己是由那个Class实例生成
例如:
//获取到 Car 类 对应的 Class 对象
Class cls = Class.forName(classAllPath); cls Class对象
// 通过 cls 创建对象实例
Car car = (Car) cls.newInstance() car 每个类的实例对象
(5)通过Class对象可以完整得到一个类的完整结构 API
(6)类的字节码二进制数据,存放在方法区中,有的地方叫类的元数据(方法代码,变量名,方法名,访问权限等等)
4.1 Class类常用方法
通过反射获取属性 brand
Field brand = cls.getField("brand")
通过反射给属性赋值
brand.set(car, "奔驰");
得到所有属性 【 返回一个属性数组 】
Field[] fields = cls.getFields 【私有属性不能得到】
4.2 获取Class对象 【六种】
(1)【代码阶段】前提是已知一个类的全类名,且该类在类路径下,可通过Class的静态方法forName()获取
多用于配置文件,加载类,Class.forName("路径")
(2)【加载阶段】已知具体的类,通过类的class获取。该方法最安全可靠,程序性能高,多用于参数传递
Class cls2 = Cat.class
(3)【运行阶段】已知某个类的实例,调用该实例的getClass()方法
Class clazz = 对象.getClass() //运行类型 真正加载进内存的类
(4)通过类加载器,来获取 【绕了一下】
ClassLoader cl = 具体对象.getClass().getClassLoader(); //先得到类加载器
Class clazz4 = cl.loadClass(“类的全类名”)//通过类加载器得到Class对象
(5)基本数据类型(int char float double long short byte)
Class cls = 基本数据类型.class
例如 int对应Integer
(6)基本数据类型对应的包装类
Class cls = 包装类.TYPE
4.3 那些类型有Class对象
(1)外部类,内部类 (2)接口
(3)数组 (4)枚举 (5)注解
(6)基本数据类型 (7)void (8)Class类
5. 类加载
类加载时机:
(1)创建对象时(new) 静态加载
(2)当子类加载时,父类也加载 静态加载
(3)调用类中静态成员 静态加载
(4)通过反射 动态加载
类加载过程图:
(1)加载阶段
将类的class文件读入到内存,并为之创建一个Java.lang.Class对象。【由类加载器完成】
详细:JVM在该阶段的主要目的将字节码从不同数据源(class文件、jar包,网络)转化为二进制字节流加载到内存中,创建一个Java.lang.Class对象
(2.1)连接阶段-验证
目的是确保Class文件中的字节流包含的信息符号当前虚拟机的要求,并且不会危害虚拟机自身的安全
包括(文件格式验证、元数据验证、字节码验证、符号引用验证)
可以考虑使用 -Xverify:none 参数关闭大部分的类验证措施,缩短虚拟机加载时间
(2.2)连接阶段-准备
JVM会在此阶段对静态变量,分配内存默认初始化(0,0L,null,false等等),这些变量的内存在方法区进行分配
例如:
class A {
//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
//2. n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是 20
//3. n3 是 static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}
(2.3)连接阶段-解析
虚拟机将常量池的符号引用替换为直接引用的过程
(3)初始化
1.初始化阶段才真正开始执行类中定义的Java程序代码 ,是执行<clinit>()方法的过程
2.该方法是根据 由编译器按语句在源文件中出现的顺序,依次收集类中所有静态变量的赋值动作和静态代码块中的语句,然后进行合并【取最后的结果】
例如
1. 加载 B 类,并生成 B 的 class 对象
2. 链接-准备 num = 0
3.初始化
clinit() {
System.out.println("B 静态代码块被执行");
num = 300;
num = 100;
}
合并: num = 100
3.虚拟机会保证一个类的<clinit>()方法在多线程被正确加锁,同步 【这也解释了为什么类加载只有一次】
6. 通过反射获取类的结构信息
第一组 :java.lang.Class类
注:所有表示包括公有和非公有
第二组:java.lang.reflect.Field类
(1)getModifiers():以int的形式返回修饰符
【说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16】
(2)getType():以Class形式返回类型
(3)getName():返回属性名
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName()
+ " 该属性的修饰符值=" + declaredField.getModifiers()
+ " 该属性的类型=" + declaredField.getType());
}
第三组: java.lang.reflect.Method类
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法=" + declaredMethod.getName()
+ " 该方法的访问修饰符值=" + declaredMethod.getModifiers()
+ " 该方法返回类型" + declaredMethod.getReturnType());
//输出当前这个方法的形参数组情况
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的形参类型=" + parameterType);
}
}
第四组: java.lang.reflect.Constructor类
//getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("====================");
System.out.println("本类中所有构造器=" + declaredConstructor.getName());
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该构造器的形参类型=" + parameterType);
}
7. 通过反射创建对象
class Person{
public String name = "cc";
private int age = 20 ;
private static int sal =10; //类变量只加载一次,内存中是类的公有属性
public Person(){
}
private Person(String name,int age) {
this.name = name;
this.age = age;
}
public Person(String name){
this.name = name;
}
public void m1(){
System.out.println("m1 hi");
}
private void m2(String name){
System.out.println(name + "hi");
}
public void m3(String name){
System.out.println(name + "在叫");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +'\''+"sal"+ sal +
'}';
}
}
(1)法一:调用类中public修饰的无参构造器创建对象
Class cls = Class.forName("reaction.Person"); // System.out.println(cls); //获取无参的构造器 并创建了一个对象实例 Object o = cls.newInstance();
(2)法二:调用类中指定构造器
//这样会报错,因为得不到私有属性 // Constructor constructor1 = cls.getConstructor(String.class,int.class); //爆破【暴力破解】 获取私有属性 getDeclaredConstructor()可以得到类中所有构造器 Constructor constructor1 = cls.getDeclaredConstructor(String.class,int.class); //先拿到具体某个构造器对象 constructor1.setAccessible(true); //爆破 Object o2 = constructor1.newInstance("ss",10 ); //创建实例时 赋值 System.out.println(o2);
8. 通过反射访问类中成员
公有属性!!! Class cls = Class.forName("reaction.Person"); Field name = cls.getField("name"); //获取name的属性 name.set(o,"fa"); //可赋值 System.out.println(age.get(o));
获取私有属性 爆破
Class cls = Class.forName("reaction.Person");
Field sal = cls.getDeclaredField("sal"); sal.setAccessible(true); //爆破 [不检查] // sal.set(o,"sda"); sal.set(null,5000); //只有静态的属性才能置空,否则报错 不推荐 System.out.println(o); System.out.println("============"); System.out.println("o的"+sal.get(o)); //这两个值一样,因为sal是静态属性,在类中公有 System.out.println("o1的"+sal.get(o1));
9. 通过反射访问类中方法 与上面一样
System.out.println("==========访问方法========="); //与上面类似 //获取方法对象 Method m1 = cls.getMethod("m1"); m1.invoke(o); System.out.println("访问私有带参方法"); Method m2 = cls.getDeclaredMethod("m2", String.class); m2.setAccessible(true); m2.invoke(o,"da"); System.out.println("获取公有带参方法"); Method m3 = cls.getMethod("m3", String.class); m3.invoke(o,"fds");