Java—反射

目录

1.需求引出反射

2.反射机制原理图

3.反射的优缺点

4.Class类

4.1 Class类常用方法

4.2 获取Class对象 【六种】

4.3 那些类型有Class对象

5. 类加载

(1)加载阶段

(2.1)连接阶段-验证

(2.2)连接阶段-准备

(2.3)连接阶段-解析

(3)初始化

6. 通过反射获取类的结构信息

7. 通过反射创建对象

8. 通过反射访问类中成员

9. 通过反射访问类中方法 与上面一样


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");

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值