• day43-反射02


    Java反射02

    2.Class类

    2.1基本介绍

    image-20220926201405016

    1. Class类也是类,因此也继承Object类

      image-20220926192116952
    2. Class类对象不是new出来的,而是系统创建的

    3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次

    4. 每个类的实例都会记得自己是由哪个Class实例所生成

    5. 通过Class对象可以得到一个类的完整结构(通过一系列API)

    6. Class对象是存放在堆的

    7. 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等)

      当我们加载完类之后,除了会在堆里生成一个Class类对象,还会在方法区生成一个类的字节码二进制数据(元数据)

    例子:

    package li.reflection.class_;
    import li.reflection.Cat;
    //对Class类的特点的梳理
    public class Class01 {
    public static void main(String[] args) throws ClassNotFoundException {
    //1.Class类对象不是new出来的,而是系统创建的
    //1.1.传统的 new对象
    /**通过ClassLoader类中的loadClass方法:
    * public Class loadClass(String name) throws ClassNotFoundException {
    * return loadClass(name, false);
    * }
    */
    //Cat cat = new Cat();
    //1.2反射的方式
    /**在这里debug,需要先将上面的Cat cat = new Cat();注释掉,因为同一个类只加载一次,否则看不到loadClass方法
    * (这里也验证了:3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次)
    * 仍然是通过 ClassLoader类的loadClass方法加载 Cat类的 Class对象
    * public Class loadClass(String name) throws ClassNotFoundException {
    * return loadClass(name, false);
    * }
    */
    Class cls1 = Class.forName("li.reflection.Cat");
    //2.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
    Class cls2 = Class.forName("li.reflection.Cat");
    //这里输出的hashCode是相同的,说明cls1和cls2是同一个Class类对象
    System.out.println(cls1.hashCode());//1554874502
    System.out.println(cls2.hashCode());//1554874502
    }
    }

    Class类对象不是new出来的,而是系统创建的:

    1. Cat cat = new Cat();处打上断点,点击force step into,可以看到

    image-20220926192820227

    1. 注释Cat cat = new Cat();,在Class cls1 = Class.forName("li.reflection.Cat");处打上断点,可以看到 仍然是通过 ClassLoader类加载 Cat类的 Class对象

    image-20220926200747357

    2.2Class类常用方法

    public static Class forName(String className)//传入完整的“包.类”名称实例化Class对象
    public Constructor[] getContructors() //得到一个类的全部的构造方法
    public Field[] getDeclaredFields()//得到本类中单独定义的全部属性
    public Field[] getFields()//得到本类继承而来的全部属性
    public Method[] getMethods()//得到一个类的全部方法
    public Method getMethod(String name,Class..parameterType)//返回一个Method对象,并设置一个方法中的所有参数类型
    public Class[] getInterfaces() //得到一个类中锁实现的全部接口
    public String getName() //得到一个类完整的“包.类”名称
    public Package getPackage() //得到一个类的包
    public Class getSuperclass() //得到一个类的父类
    public Object newInstance() //根据Class定义的类实例化对象
    public Class getComponentType() //返回表示数组类型的Class
    public boolean isArray() //判断此class是否是一个数组

    应用实例

    Car:

    package li.reflection;
    public class Car {
    public String brand = "宝马";
    public int price = 500000;
    public String color ="白色";
    @Override
    public String toString() {
    return "Car{" +
    "brand='" + brand + '\'' +
    ", price=" + price +
    ", color='" + color + '\'' +
    '}';
    }
    }

    Class02:

    package li.reflection.class_;
    import li.reflection.Car;
    import java.lang.reflect.Field;
    //演示Class类的常用方法
    public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
    String classAllPath = "li.reflection.Car";
    //1.获取到 Car类 对应的 Class对象
    //表示不确定的Java类型
    Class cls = Class.forName(classAllPath);
    //2.输出cls
    System.out.println(cls);//将会显示cls对象是哪个类的Class对象 class li.reflection.Car
    System.out.println(cls.getClass());//输出cls的运行类型 class java.lang.Class
    //3.得到包名
    System.out.println(cls.getPackage().getName());//li.reflection :Class对象对应的类是在哪个包下面
    //4.得到全类的名称
    System.out.println(cls.getName());//li.reflection.Car
    //5.通过cls创建一个对象实例
    Car car = (Car)cls.newInstance();
    System.out.println(car);//调用car.toString()
    //6.通过反射获得属性 如:brand
    Field brand = cls.getField("brand");
    System.out.println(brand.get(car));//宝马
    //7.通过反射给属性设置值
    brand.set(car,"奔驰");
    System.out.println(brand.get(car));//奔驰
    //8.遍历得到所有的属性(字段)
    Field[] fields = cls.getFields();
    for (Field f:fields) {
    System.out.println(f.getName());//依次输出各个属性字段的名称
    }
    }
    }

    2.3获取Class类对象的各种方式

    1. 前提:已经知道一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

      实例:Class cls1 = Class.forName("java.lang.Cat");

      应用场景:多用于配置文件,读取类全路径,加载类

    2. 前提:若已知具体的类,通过 类.class 获取,该方式最为安全可靠,程序性能最高

      实例:Class cls2 = Cat.class;

      应用场景:多用于参数传递,比如通过反射得到对应构造器对象

    3. 前提:已某个类的实例,调用该实例的getClass()方法获取Class对象

      实例:Class cls3 = 对象.getClass();//运行类型

      应用场景:通过创建好的对象,获取Class对象

    4. 其他方式

      ClassLoader cl = 对象.getClass().getClassLoad();

      Class cls4 = cl.loadClass("类的全类名");

    5. 基本数据类型byte,short,int,long,double,float,boolean.char, 按如下方式得到Class类对象

      Class cls = 基本数据类型.class

    6. 基本数据类型对应的包装类,可以通过.TYPE得到Class类对象

      Class cls = 包装类.TYPE

    例子:

    package li.reflection.class_;
    import li.reflection.Car;
    //演示得到Class对象的各种方式
    public class getClass_ {
    public static void main(String[] args) throws ClassNotFoundException {
    //1.Class.forName
    String classAllPath = "li.reflection.Car";//这里一般是通过配置文件获取全路径
    Class cls1 = Class.forName(classAllPath);
    System.out.println(cls1);//class li.reflection.Car
    //2.类名.class ,多用于参数传递
    Class cls2 = Car.class;
    System.out.println(Car.class);//class li.reflection.Car
    //3.对象.getClass() ,应用场景,有对象实例
    Car car = new Car();
    Class cls3 = car.getClass();
    System.out.println(cls3);//class li.reflection.Car
    //4.通过类加载器(4种)来获取到类的 Class对象
    //(1)先得到car对象的类加载器(每个对象都有一个类加载器)
    ClassLoader classLoader = car.getClass().getClassLoader();
    //(2)通过类加载器得到Class对象
    Class cls4 = classLoader.loadClass(classAllPath);
    System.out.println(cls4);//class li.reflection.Car
    //cls1,cls2,cls3,cls4其实是同一个Class对象
    System.out.println(cls1.hashCode());//1554874502
    System.out.println(cls2.hashCode());//1554874502
    System.out.println(cls3.hashCode());//1554874502
    System.out.println(cls4.hashCode());//1554874502
    //5.基本数据类型按如下方式得到Class类对象
    Class integerClass = int.class;
    Class characterClass = char.class;
    Class booleanClass = boolean.class;
    System.out.println(integerClass);//int
    System.out.println(characterClass);//char
    System.out.println(booleanClass);//boolean
    //6.基本数据类型对应的8种包装类,可以通过 .TYPE得到Class类对象
    Class type1 = Integer.TYPE;
    Class type2 = Character.TYPE;
    System.out.println(type1);
    System.out.println(integerClass.hashCode());//1846274136
    System.out.println(type1.hashCode());//1846274136
    }
    }

    2.4哪些类型有Class对象

    1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
    2. interface:接口
    3. 数组
    4. enum:枚举
    5. annotation:注解
    6. 基本数据类型
    7. void

    例子:

    package li.reflection.class_;
    import java.io.Serializable;
    //演示哪些类有Class对象
    public class allTypeClass {
    public static void main(String[] args) {
    Class cls1 = String.class;//外部类
    Class cls2 = Serializable.class;//接口
    Class cls3 = Integer[].class;//数组
    Class<float[][]> cls4 = float[][].class;//二维数组
    Class cls5 = Deprecated.class;//注解
    //Thread类中的枚举State--用来表示线程状态
    Class cls6 = Thread.State.class;//枚举
    Class cls7 = long.class;//基本数据类型
    Class cls8 = void.class;//void类型
    Class cls9 = Class.class;//Class类也有
    System.out.println(cls1);//class java.lang.String
    System.out.println(cls2);//interface java.io.Serializable
    System.out.println(cls3);//class [Ljava.lang.Integer;
    System.out.println(cls4);//class [[F
    System.out.println(cls5);//interface java.lang.Deprecated
    System.out.println(cls6);//class java.lang.Thread$State
    System.out.println(cls7);//long
    System.out.println(cls8);//void
    System.out.println(cls9);//class java.lang.Class
    }
    }

    2.5类加载

    image-20220928163817837

    • 基本说明:

      反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载

      1. 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强

        静态加载的类,即使没有用到也会加载,并且进行语法的校验

      2. 动态加载:运行时加载相关的类,如果运行时不用该类,即使不存在该类,也不会报错,降低了依赖性

    • 类加载的时机:

      1. 当创建对象时(new)//静态加载
      2. 当子类被加载时 //静态加载
      3. 调用类中的静态成员时 //静态加载
      4. 通过反射 //动态加载

    例子:静态加载和动态加载

    import java.lang.reflect.*;
    import java.util.*;
    public class classLoad_ {
    public static void main(String[] args) throws Exception {
    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入key");
    String key = scanner.next();
    switch (key) {
    case "1":
    Dog dog = new Dog();//静态加载,依赖性很强
    dog.cry();
    break;
    case "2":
    //反射 -->动态加载
    Class cls = Class.forName("Person"); //加载Person[动态加载]
    Object o = cls.newInstance();
    Method m = cls.getMethod("hi");
    m.invoke(o);
    System.out.println("ok");
    break;
    default:
    System.out.println("do nothing...");
    }
    }
    }
    //因为new Dog()是静态加载,因此必须编写Dog
    //Person类是动态加载,所以即使没有编写Person类也不会报错,只有当动态加载该类时,(有问题)才会报错
    class Dog{
    public void cry(){
    System.out.println("小狗在哭泣..");
    }
    }

    在没有编写Dog类时,即使在switch选择中,不一定会运行到new dog对象的case1,但是程序仍然报错了,因为静态加载的类,即使没有用到,也会加载,并且进行语法的校验

    image-20220928171802611

    在编写了Dog类对象后,可以看到编译通过:

    image-20220928172042301

    运行程序:可以看到,即使没有编写Person类,但是运行时没有用到,就不会报错

    image-20220928172525548

    使用到Person类,报错:(运行时加载)

    image-20220928172725660

    2.6类的加载过程

    • 类加载过程图

    image-20220928182453805

    • 类加载各阶段完成的任务
      • 加载阶段:将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。
      • 连接阶段:将类的二进制数据合并到jre中
      • 初始化阶段:JVM负责对类进行初始化,这里主要是指静态成员

    2.6.1加载阶段

    image-20220928183631407

    JVM 在该阶段的主要目的是,将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象

    image-20220928183033453

    2.6.2连接阶段-验证

    image-20220928183709788
    1. 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
    2. 包括:文件格式验证(是否以 魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
    image-20220928184408532
    1. 可以考虑使用 -Xverify:none 参数关闭大部分的类验证措施,缩短虚拟机类加载的时间

    2.6.3连接阶段-准备

    image-20220928183734760

    JVM会在该阶段对静态变量,分配内存并默认初始化(对应的数据类型的默认初始值,如0,0L,null,false等)。这些变量所使用的内存都将在方法区中进行分配

    例如:

    package li.reflection.classload_;
    //我们说明一个类加载的链接阶段-准备
    public class ClassLoad02 {
    public static void main(String[] args) {
    }
    }
    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.6.4连接阶段-解析

    image-20220928183750334

    虚拟机将常量池内的符号引用替换为直接应用的过程

    个人理解 java虚拟机中的符号引用和直接引用_maerdym的博客-CSDN博客

    2.6.5初始化阶段

    image-20220928183806300
    1. 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程
    2. ()方法是 由编译器按语句在源文件中出现的顺序,依次自动收集类中的 所有静态变量 的赋值动作 和 静态代码块中的语句,并进行合并。-->例子1
    3. 虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。

    例子1:演示类加载的初始化阶段

    package li.reflection.classload_;
    //演示类加载的初始化阶段
    public class ClassLoad03 {
    public static void main(String[] args) {
    //分析:
    /**
    * 1.加载B类,并生成 B的Class对象
    * 2.链接 :将num默认初始化为 0
    * 3.初始化阶段:
    * 3.1依次 自动收集类中的 所有静态变量的赋值动作 和 静态代码块中的语句,并合并
    * 收集:
    * clinit(){
    * System.out.println("B的静态代码块被执行");
    * num = 300;
    * num = 100;
    * }
    * 合并:num =100;
    */
    //直接使用类的静态属性也会导致类的加载
    System.out.println(B.num);//100
    }
    }
    class B {
    static {
    System.out.println("B的静态代码块被执行");
    num = 300;
    }
    static int num = 100;
    public B() {
    System.out.println("B的构造器被执行");
    }
    }
    image-20220928193333330

    例子2:

    在例子1中的程序里创建一个B类对象,打上断点,debug源码:

    image-20220928195002395

    可以看到在底层中,使用了对象锁synchronized (getClassLoadingLock(name)) :

    image-20220928194154742

    也就是说,加载类的时候,是有类的同步控制机制。

    正因为有这个机制,才能保证某个类在内存中,只有一份Class对象。

  • 相关阅读:
    聊聊计算机中的寄存器
    代码随想录打卡—day59—【单调栈】— 9.5 单调栈2
    Hyperledge Fabric-身份与角色认证
    [PAT练级笔记] 22 Basic Level 1022 D进制的A+B
    使用SPIDE操作SPI_DAC模块
    计算机相关内容的网站主题说明书
    17. Letter Combinations of a Phone Number
    查看docker 容器的端口
    365天搞定八股文——Day 002 内核态和用户态的区别
    Electron:主进程、渲染进程以及通信
  • 原文地址:https://www.cnblogs.com/liyuelian/p/16739387.html