深入探究Java反射

深入探究Java反射

参考网址:

(1)尚硅谷Java基础视频

(2)韩顺平Java反射

(3)尚硅谷深入探究JVM

1. 反射概述

  • 为什么需要反射?

    • 反射是被视为动态语言(运行时可以改变其结构的语言,如Python、JS、C#)的关键,反射机制允许程序在执行期间借助于反射API取得任何类的内部信息,并能直接操作任意对象的内部属性和方法。Java因为有了反射被称为准动态语言。另外与之对应的是静态语言,例如C++

    • 反射是很多框架的基础,例如大名鼎鼎的Spring。在框架中,我们通过读取配置文件中的内容动态的创建对象,从而实现了代码和配置的分离,我们不需要改动代码,直接改动配置文件就可以达到改变代码运行结果的效果(设计模式的开闭原则)。

    • 反射是代理模式(分为静态代理和动态代理,动态代理是SpringAOP的基础)的基础。

  • 反射机制提供的功能:

    • 在运行时判断任何一个对象所属的类;

    • 在运行时构造任意一个类的对象;

    • 在运行时获取泛型信息;

    • 在运行时调用任何一个对象的成员变量和方法;

    • 在运行时处理注解;

    • 生成动态代理。

  • 反射相关的API

    • java.lang.Class:代表一个类;

    • java.lang.reflect.Field:代表类的成员变量;

    • java.lang.reflect.Constructor:代表类的构造器;

    • java.lang.reflect.Method:代表类的方法;

  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的信息。

2. 反射基础

2.1 Java程序在计算机中的三阶段

  • 例如我们程序中有一个类,如下:
class Cat {
    private String name;
    public Cat() {}
    public hi() {}
}
  • 则该类会有三个阶段,如下图:

在这里插入图片描述

  • 其中注意,第二个阶段即加载阶段是指类加载全过程,是广义上的加载,包含三个阶段:(1)加载(狭义上的加载);(2)链接;(3)初始化。

  • Class加载到内存中后,我们就称为运行时类,例如上图中中间的Class类对象。换句话说,Class的实例就对应着一个运行时类。

  • 准确来说,将.class文件字节码加载到内存中,并将这些静态数据转化成方法区的运行时的数据结构(klass),然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。关于klass可以参考:Java对象模型-oop和klass

2.2 获取Class对象的6种方式

  • 假设在com.wxx.pojo包下有两个类,父类是Person,子类时Student(这里的子父类是为了演示getSuperclass()),如下:
package com.wxx.pojo;

public class Person {

    public String name;

    public Person() { }
    public Person(String name) { this.name = name; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
package com.wxx.pojo;

public class Student extends Person {

    public Student() { this.name = "学生"; }
}
  • 我们可以通过如下方式获取Class对象:
package com.wxx;

import com.wxx.pojo.Person;
import com.wxx.pojo.Student;

@SuppressWarnings("all")
public class Test01 {

    public static void main(String[] args) throws Exception {

        Person person = new Student();

        // 方式一(编译阶段):Class.forName();  应用场景:配置文件。最常用
        Class cls1 = Class.forName("com.wxx.pojo.Student");
        // 方式二(加载阶段):类名.class;  应用场景:参数传递
        Class cls2 = Student.class;
        // 方式三(运行阶段):对象.getClass();  应用场景:有对象实例
        Class cls3 = person.getClass();
        // 方式四:通过类的加载器来获取类的Class对象
        ClassLoader classLoader = person.getClass().getClassLoader();
        Class cls4 = classLoader.loadClass("com.wxx.pojo.Student");
        // 方式五:基本内置类型(boolean、byte、char、short、int、long、float、double)通过.class获取
        Class cls5 = int.class;
        // 方式六:基本内置类型的包装类都有一个Type属性
        Class cls6 = Integer.TYPE;
        
        // 补充:获取父类类型
        Class cls7 = cls1.getSuperclass();
    }
}

2.3 哪些类型有Class对象

  • 如下类型有Class对象

    (1)外部类,成员内部类,静态内部类,局部内部类,匿名内部类;

    (2)interface:接口;

    (3)数组;

    (4)enum:枚举;

    (5)annotation:注解;

    (6)基本数据类型;

    (7)void

package com.wxx;

import java.lang.annotation.ElementType;

@SuppressWarnings("all")
public class Test02 {

    public static void main(String[] args) throws Exception {

        Class c1 = Object.class;  // 类
        Class c2 = Comparable.class;  // 接口
        Class c3 = String[].class;  // 一维数组
        Class c4 = int[][].class;  // 二维数组
        Class c5 = Override.class;  // 注解
        Class c6 = ElementType.class;  // 枚举
        Class c7 = long.class;  // 基本数据类型
        Class c8 = Integer.TYPE;  // 基本数据类型对应的包装类,或者写成:Class c8 = Integer.class;
        Class c9 = void.class;  // void
        Class c10 = Class.class;  // Class

        System.out.println(c1);  // class java.lang.Object
        System.out.println(c2);  // interface java.lang.Comparable
        System.out.println(c3);  // class [Ljava.lang.String;
        System.out.println(c4);  // class [[I
        System.out.println(c5);  // interface java.lang.Override
        System.out.println(c6);  // class java.lang.annotation.ElementType
        System.out.println(c7);  // long
        System.out.println(c8);  // int
        System.out.println(c9);  // void
        System.out.println(c10);  // class java.lang.Class

        // 只要元素类型与维度(比如都是一维数组),就返回同一个Class
        int[] a = new int[10], b = new int[100];
        System.out.println(a.getClass() == b.getClass());  // true
    }
}

2.4 反射的基本使用

  • 假设在com.wxx.pojo包下存在四个Java文件,即Creature、MyInterface、MyAnnotation、Person,关系是Person继承Creature,实现MyInterface接口,并且Person类上以及内部函数上有MyAnnotation注解,如下:
package com.wxx.pojo;

import java.io.Serializable;

public class Creature<T> implements Serializable {

    private char gender;
    public double weight;

    private void breath() { System.out.println("生物呼吸~"); }
    public void eat() { System.out.println("生物吃东西~"); }
}
package com.wxx.pojo;

public interface MyInterface {
    void info();
}
package com.wxx.pojo;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "hello";
}
package com.wxx.pojo;

import java.io.FileNotFoundException;
import java.io.IOException;

@MyAnnotation("hi")
public class Person extends Creature<String> implements Comparable<String>, MyInterface {

    public String name;
    private int age;  // 私有
    String sex;  // default
    public static final int N = 1000;

    public Person() { }
    private Person(String name) { this.name = name; }  // 私有
    Person(String name, int age) {  // default
        this.name = name;
        this.age = age;
    }
    @MyAnnotation(value = "hi")
    public Person(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getSex() { return sex; }
    public void setSex(String sex) { this.sex = sex; }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }

    public void show() {
        System.out.println("你好~");
    }

    private String showNation(String nation) {  // 私有
        System.out.println("我的国籍是:" + nation);
        return nation;
    }

    @MyAnnotation
    void say(int a, int b) throws IOException, FileNotFoundException {  // default
        System.out.println(a + " say hello to " + b);
    }

    @Override
    public void info() {
        System.out.println("我是一个人~");
    }

    @Override
    public int compareTo(String o) {
        return 0;
    }
    
	private static void showDesc() {
        System.out.println("我是一个可爱的人~");
    }
}
  • 反射的使用如下:
package com.wxx;

import com.wxx.pojo.Person;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;

@SuppressWarnings("all")
public class Test03 {

    public static void main(String[] args) throws Exception {

        /**
         * Class
         */
        String path = "com.wxx.pojo.Person";
        // 获取Person类对应的Class对象
        Class cls = Class.forName(path);
        // 输出
        System.out.println(cls);  // 显示cls对象是哪个类的对象    class com.wxx.pojo.Person
        System.out.println(cls.getClass());  // 输出cls的运行类型    class java.lang.Class
        // 得到包名
        System.out.println(cls.getPackage());  // package com.wxx.pojo
        // 得到全类名
        System.out.println(cls.getName());  // com.wxx.pojo.Person
        // 通过cls创建对象实例。Class的newInstance方法只能调用空参构造器,并且权限要够。
        Person person = (Person) cls.newInstance();
        System.out.println(person);  // Person{name='null', age=0, sex='null'}

        /**
         * Field
         */
        System.out.println("--------------------------------");
        // getField: 只能获取public的字段,包括从父类继承来的字段
        Field name = cls.getField("name");
        name.set(person, "Jerry");  // 修改public属性的值
        String pName = (String) name.get(person);
        System.out.println(pName);  // Jerry
        System.out.println(person);  // Person{name='Jerry', age=0, sex='null'}

        // getDeclaredField: 可以获取本类所有的字段,包括 private 的,但是不能获取继承来的字段
        // 注: 这里只能获取到 private 的字段,但并不能访问该 private 字段的值,除非加上 setAccessible(true)
        Field age = cls.getDeclaredField("age");
        age.setAccessible(true);  // 不能直接操作私有属性,我们需要先关闭属性或者方法的安全监测
        age.set(person, 10);
        int pAge = (int) age.get(person);
        System.out.println(pAge);  // 10
        System.out.println(person);  // Person{name='Jerry', age=10, sex='null'}

        // 获取所有public的成员变量的信息,包括从父类继承来的字段
        Field[] fs = cls.getFields();

        // 获取所有该类自己声明的成员变量的信息
        Field[] dfs = cls.getDeclaredFields();
        System.out.println();
        for (Field field : dfs) {
            String modifier = Modifier.toString(field.getModifiers());  // 得到权限修饰符
            if (modifier.length() != 0) modifier += " ";

            Class type = field.getType();  // 得到成员变量的类 类型
            String typeName = type.getName();

            String fieldName = field.getName();  // 得到成员变量的名称
            System.out.println(modifier + typeName + " " + fieldName + ";");
        }

        // 调用静态属性
        System.out.println();
        Field val = cls.getDeclaredField("N");
        val.setAccessible(true);
//        int N = (int) val.get(Person.class);
        int N = (int) val.get(null);  // 等价于上述写法
        System.out.println(N);  // 1000

        /**
         * Constructor
         */
        System.out.println("--------------------------------");
        // getMethod: 可以获取public的构造函数(构造函数不能继承)
        Constructor cons1 = cls.getConstructor(String.class, int.class, String.class);
        Person p1 = (Person) cons1.newInstance("Lili", 18, "female");  // 通过反射创建对象
        System.out.println(person);

        // getDeclaredMethod: 获取任意一个的构造函数
        Constructor<Person> cons2 = cls.getDeclaredConstructor(String.class);
        cons2.setAccessible(true);  // 不能直接操作私有构造方法,我们需要先关闭属性或者方法的安全监测
        Person p2 = cons2.newInstance("Tom");  // 泛型中已经指明为Person,则不用强转了
        System.out.println(p2);  // Person{name='Tom', age=0, sex='null'}

        // 获取所有public的构造函数
        Constructor[] cs = cls.getConstructors();

        // 获取所有的构造函数
        Constructor[] dcs = cls.getDeclaredConstructors();
        for (Constructor cons : dcs) {
            Annotation[] annos = cons.getAnnotations();  // 得到方法上的注解
            List<String> annosList = new ArrayList<>();
            for (Annotation c : annos) annosList.add(c.toString());
            String annosStr = "";
            if (!annosList.isEmpty()) annosStr = String.join("\n", annosList) + "\n";

            String modifier = Modifier.toString(cons.getModifiers());  // 得到权限修饰符
            if (modifier.length() != 0) modifier += " ";

            String consName = cons.getName();  // 获取构造函数的名称

            // 获取参数列表,得到的参数列表中的每个类型是 类型的类类型,比如int的类类型是int.class
            Class[] parameterTypes = cons.getParameterTypes();
            List<String> paraList = new ArrayList<>();
            for (Class c : parameterTypes) paraList.add(c.getName());

            // 输出
            System.out.println("\n" +
                    annosStr +  // 注解列表
                    modifier +  // 权限修饰符
                    consName + "(" +  // 构造函数名称
                    String.join(", ", paraList) + ")"  // 参数列表
            );
        }

        /**
         * Method
         */
        System.out.println("--------------------------------");
        // getMethod: 可以获取public的函数,包括父类继承而来的
        Method show = cls.getMethod("show");
        show.invoke(person);  // 你好~

        // getDeclaredMethod: 获取所有该类自己声明的方法,不管访问权限
        Method showNation = cls.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);  // 不能直接操作私有方法,我们需要先关闭属性或者方法的安全监测
        String nation = (String) showNation.invoke(person, "中国");  // 我的国籍是:中国
        System.out.println(nation);  // 中国

        // 获取所有public的函数,包括父类继承而来的
        Method[] ms = cls.getMethods();

        // 获取所有该类自己声明的方法,不管访问权限
        Method[] dms = cls.getDeclaredMethods();
        for (Method method : dms) {
            Annotation[] annos = method.getAnnotations();  // 得到方法上的注解
            List<String> annosList = new ArrayList<>();
            for (Annotation c : annos) annosList.add(c.toString());
            String annosStr = "";
            if (!annosList.isEmpty()) annosStr = String.join("\n", annosList) + "\n";


            String modifier = Modifier.toString(method.getModifiers());  // 得到权限修饰符
            if (modifier.length() != 0) modifier += " ";

            Class returnType = method.getReturnType();  // 得到方法的返回值类型的 类类型
            String returnTypeName = returnType.getName();

            String methodName = method.getName();  // 得到方法的名称

            // 获取参数列表,得到的参数列表中的每个类型是 类型的类类型,比如int的类类型是int.class
            Class[] parameterTypes = method.getParameterTypes();
            List<String> paraList = new ArrayList<>();
            for (Class c : parameterTypes) paraList.add(c.getName());

            // 获取异常列表
            Class[] exceptionTypes = method.getExceptionTypes();
            List<String> excepList = new ArrayList<>();
            for (Class c : exceptionTypes) excepList.add(c.getName());
            String excepStr = "";
            if (!excepList.isEmpty()) excepStr = " throws " + String.join(", ", excepList);

            // 输出
            System.out.println("\n" +
                    annosStr +  // 注解列表
                    modifier +  // 权限修饰符
                    returnTypeName + " " +  // 返回类型
                    methodName + "(" +  // 方法名
                    String.join(", ", paraList) + ")" +  // 参数列表
                    excepStr  // 异常
            );
        }

        // 调用静态方法
        System.out.println();
        Method showDesc = cls.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
        // 如果调用的运行时类没有返回值,则此invoke()返回null
//        Object returnVal = showDesc.invoke(Person.class);
        Object returnVal = showDesc.invoke(null);  // 等价于上述写法
        System.out.println(returnVal);  // null

        /**
         * 获取运行时类Person的父类的信息
         */
        System.out.println("--------------------------------");
        // 获取父类的 类类型
        Class superclass = cls.getSuperclass();
        System.out.println(superclass.getName());  // com.wxx.pojo.Creature
        // 获取带泛型的父类
        Type gsc = cls.getGenericSuperclass();
        System.out.println(gsc.getTypeName());  // com.wxx.pojo.Creature<java.lang.String>
        // 获取带泛型的父类中的泛型
        ParameterizedType paraType = (ParameterizedType) cls.getGenericSuperclass();
        Type[] atas = paraType.getActualTypeArguments();
        System.out.println(atas[0].getTypeName());  // java.lang.String
        System.out.println(((Class) atas[0]).getTypeName());  // 等价于上一句话  java.lang.String

        /**
         * 获取运行时类Person的接口
         */
        System.out.println("--------------------------------");
        // 获取运行时类Person的接口
        Class[] interfaces1 = cls.getInterfaces();
        for (Class t : interfaces1) System.out.println(t);
        // 获取运行时类Person父类的接口
        System.out.println();
        Class[] interfaces2 = cls.getSuperclass().getInterfaces();
        for (Class t : interfaces2) System.out.println(t);

        /**
         * 获取运行时类Person所在的包
         */
        System.out.println("--------------------------------");
        Package pack = cls.getPackage();
        System.out.println(pack.getName());  // com.wxx.pojo

        /**
         * 获取运行时类Person声明的注解
         */
        System.out.println("--------------------------------");
        Annotation[] annos = cls.getAnnotations();
        for (Annotation a : annos) System.out.println(a);  // @com.wxx.pojo.MyAnnotation(value=hi)
    }
}
  • 结果如下:
class com.wxx.pojo.Person
class java.lang.Class
package com.wxx.pojo
com.wxx.pojo.Person
Person{name='null', age=0, sex='null'}
--------------------------------
Jerry
Person{name='Jerry', age=0, sex='null'}
10
Person{name='Jerry', age=10, sex='null'}

public java.lang.String name;
private int age;
java.lang.String sex;
public static final int N;

1000
--------------------------------
Person{name='Jerry', age=10, sex='null'}
Person{name='Tom', age=0, sex='null'}

@com.wxx.pojo.MyAnnotation(value=hi)
public com.wxx.pojo.Person(java.lang.String, int, java.lang.String)

com.wxx.pojo.Person(java.lang.String, int)

private com.wxx.pojo.Person(java.lang.String)

public com.wxx.pojo.Person()
--------------------------------
你好~
我的国籍是:中国
中国

public java.lang.String toString()

public volatile int compareTo(java.lang.Object)

public int compareTo(java.lang.String)

public java.lang.String getName()

public void setName(java.lang.String)

public void show()

private java.lang.String showNation(java.lang.String)

private static void showDesc()

public java.lang.String getSex()

public int getAge()

public void setSex(java.lang.String)

public void info()

public void setAge(int)

@com.wxx.pojo.MyAnnotation(value=hello)
void say(int, int) throws java.io.IOException, java.io.FileNotFoundException

我是一个可爱的人~
null
--------------------------------
com.wxx.pojo.Creature
com.wxx.pojo.Creature<java.lang.String>
java.lang.String
java.lang.String
--------------------------------
interface java.lang.Comparable
interface com.wxx.pojo.MyInterface

interface java.io.Serializable
--------------------------------
com.wxx.pojo
--------------------------------
@com.wxx.pojo.MyAnnotation(value=hi)
  • 通过上面对反射的使用,我们可能会有如下疑问:

  • 通过直接new的方式 或 反射的方式都可以调用公共结构,开发中到底使用哪个?

    • 建议直接使用new的方式,因为反射的效率比较低。那么什么时候使用反射的方式呢?这和反射的特征不能分离,即反射具有动态性。如果在编译的时候不能确定新建哪个类的对象,此时就必须使用反射,很多框架就是这样的,比如Spring
  • 反射机制与面向对象中的封装性是不是矛盾的?如何看待这两个技术?

    • 不是矛盾的,如果类中的成员变量或成员函数是private,表明代码的作者不建议我们使用这些函数(一般来说这些函数是内部使用的,我们可以通过调用public的函数间接的调用到这些函数)。但是我们非要调用这些私有函数,通过反射也是可以实现的。

    • 例如单例模式中,构造函数被私有化,我们就不可以通过new的方式新建对象,从而保证单例。但是通过反射可以破坏单例。关于单例模式可以参考:深入探究单例模式

  • 如何理解Java中万事万物皆对象?

    • 首先,我们调用方法都是通过:对象.xxx();

    • 文件在Java中也是对象(File对象);网络地址在Java中是URL对象;

    • 反射中我们的类也是对象,是Class的对象,Class本身也是对象(因此通过类直接调用静态对象也能解释的通了)。

    • 前端中的标签等在Java中也是对象,数据库中的记录等都是对象。

  • 为什么我们创建的类中最好都要提供一个权限为public的空参构造器?

    • 反射时我们经常会使用clazz.newInstance()创建对象(clazzClass类型的对象),如果没有空参构造器会报错。

    • 在继承中,创建子类对象时无论写不写super(),都会默认调用父类的空参构造器。当然,如果显示的调用了带参的super,则不会调用父类的空参构造器了。

2.5 反射对注解的操作

  • 该文件在com.wxx包,如下:
package com.wxx;

import java.lang.annotation.*;
import java.lang.reflect.Field;

/**
 * Date: 2020/11/24 21:06
 * Content: 反射操作注解
 */
@SuppressWarnings("all")
public class Test08 {

    public static void main(String[] args) throws Exception {

        Class cls = Class.forName("com.wxx.Customer");

        // 通过类上反射获得注解
        Annotation[] as = cls.getAnnotations();
        for (Annotation a : as) System.out.println(a);

        // 获取类上注解中value的值
        MyTable myTable = (MyTable) cls.getAnnotation(MyTable.class);
        System.out.println(myTable.value());

        // 获取类中字段的注解
        System.out.println("========================================");
        Field name = cls.getDeclaredField("name");  // 获取字段
        MyField myField = name.getAnnotation(MyField.class);  // 获取字段上的注解
        System.out.println(myField.columnName());  // db_name
        System.out.println(myField.type());  // varchar
        System.out.println(myField.length());  // 10
    }
}

// 类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable {
    String value();
}

// 属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField {
    String columnName();
    String type();
    int length();
}

@MyTable("db_customer")
@SuppressWarnings("all")
class Customer {

    @MyField(columnName = "db_id", type = "int", length = 10)
    private int id;
    @MyField(columnName = "db_age", type = "int", length = 3)
    private int age;
    @MyField(columnName = "db_name", type = "varchar", length = 10)
    private String name;

    public Customer() { }

    public Customer(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
  • 结果如下:
@com.wxx.MyTable(value=db_customer)
db_customer
========================================
db_name
varchar
10
  • 注解必须是RUNTIME的,我们才能用反射获取到,因为反射具有动态性,在运行时访问对象,SOURCE、CLASS的注解在运行时不存在。

  • 其实框架(例如Spring)可以看成:注解+反射+设计模式

3. 反射创建Class的原理

3.1 概述

  • 关于反射的原理,就要聊到JVM中的类加载子系统,可以参考:类加载子系统。这里也讲解一下。

  • 更加详细的内容可以参考:第20章 类的加载过程详解。建议看这一个。

  • 首先我们要明白JVM虚拟机的组成部分,一共可以分为三大部分,如下图:

在这里插入图片描述


在这里插入图片描述

  • 重点关注的是类加载子系统

3.2 类加载子系统

  • 类加载子系统的作用:

    (1)类加载器子系统作用负责从文件系统或者网络中加载class文件,class文件在文件开头会有特定的文件标识。

    (2)加载的类信息存放在一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息时class文件中常量池部分的内存映射)。

  • 类加载的过程分为三个阶段:

    • 加载:所谓加载,简而言之就是将Java类的字节码加载到机器内存中,并在方法区中构建出Java的原型-------类模板对象(Klass),最后在堆中生成Class实例

    • 链接:

      (1)验证:验证.class文件是否合法。包含:文件格式验证,元数据验证,字节码验证,符号引用验证。

      (2)准备:为类的静态变量分配内存,并将其初始化为默认值。static final修饰的常量一般(不是绝对的)在该阶段就被显式赋值(编译的时候已经默认赋值,此时会将等于号右侧的值显式赋给该变量)。

      (3)解析:将符号引用转化为直接引用。

    • 初始化:为类的静态变量赋予正确的初始值。这个阶段会调用<clinit>()方法,该方法被加锁了,因此一个类的Class对象只会存在一个。

  • 哪些情况会触发类的加载(这里是指调用了<clinit>())?类加载过程中调用<clinit>()称为类的主动使用,否则称为被动使用。

    • 主动使用:

      (1)当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。

      (2)当调用类的静态方法时,即当使用了字节码invokestatic指令。

      (3)当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic或者putstatic指令。(对应访问变量、赋值变量操作)

      (4)当使用java.reflect包中的方法反射类的方法时。比如Class.forName("com.atguigu.java.Test")

      (5)当初始化子类时,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

      (6)如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化。

      (7)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

      (8)当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。(涉及解析REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄对应的类)

    • 被动使用:

      (1)当访问一个静态字段时,只有真正声明这个字段的类才会被初始化。即当通过子类引用父类的静态变量,不会导致子类的初始化。

      (2)通过数组定义引用类,不会触发此类的初始化

      (3)引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了。

      (4)调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

  • 12
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值