类加载

静态加载与动态加载

静态加载:编译时加载需要的类,如果没有定义该类则报错,依赖性太强。

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

public static void main(String[] args) throws ... {
       //new Dog() 是静态加载,因此必须编写Dog类
        Dog dog = new Dog();
   //Person类是动态加载,因此没有编写Person类编译器也不会报错,只有当动态加载该类时才会报错
        Class cls = Class.forName("reflection.Person");
    }

类加载时机

java 动态和静态 java动态加载和静态加载_User

类加载过程图 

java 动态和静态 java动态加载和静态加载_开发语言_02

类加载的各个阶段

加载阶段

java 动态和静态 java动态加载和静态加载_开发语言_03

连接阶段——验证

java 动态和静态 java动态加载和静态加载_java 动态和静态_04

连接阶段——准备

java 动态和静态 java动态加载和静态加载_java_05

//n1 是实例属性,不是静态变量,因此在准备阶段不会分配内存
    public int n1 = 10;
    //n2 是静态变量,分配内存。默认初始化为0,而不是20
    public static int n2 = 20;
    //n3 是常量,分配内存。它一旦被赋值就不变,因此直接初始化为30
    public static final int n3 = 30;

连接阶段——解析

java 动态和静态 java动态加载和静态加载_开发语言_06

    符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。而直接引用就是指向目标的指针等。

初始化阶段

java 动态和静态 java动态加载和静态加载_User_07

    对出现的顺序的理解:对下面这段代码: static int num = 100; 在静态代码块上方,因此会被 num = 300覆盖。 

class B{
    public static void main(String[] args) {
        System.out.println(B.num); // 300
    }
    static int num = 100; //在静态代码块前出现
    static{
        System.out.println("B 静态代码块被执行");
        num = 300;
    }
}

这样的话,就不会被 num = 300覆盖,最终num = 100。 

static{
        System.out.println("B 静态代码块被执行");
        num = 300;
    }
    static int num = 100; //在静态代码块后出现

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

java.lang.Class类

java 动态和静态 java动态加载和静态加载_java_08

java.lang.reflect.Field类

java 动态和静态 java动态加载和静态加载_User_09

java.lang.reflect.Method类

java 动态和静态 java动态加载和静态加载_java 动态和静态_10

java.lang.reflect.Constructor类

java 动态和静态 java动态加载和静态加载_User_11

 

通过反射创建对象

方式一:调用类中的public修饰的无参构造器(Java11中已弃用)。方式二:调用类中的指定构造器(如果该构造器是private,要用到暴力破解)。

Class类相关方法

java 动态和静态 java动态加载和静态加载_java 动态和静态_12

Constructor类相关方法 

java 动态和静态 java动态加载和静态加载_开发语言_13

public static void main(String[] args) throws ...{
        //1. 先获取到User类的Class对象
        Class<?> userClass = Class.forName("reflection.User");
        //2. 通过public无参构造器创建实例
        User user1= (User) userClass.newInstance(); //该方法已弃用
        System.out.println(user1);

        //3. 用public有参构造器创建实例
        Constructor<?> constructor = userClass.getConstructor(String.class);//()里是构造器对应的参数
        User user = (User)constructor.newInstance("ss");
        System.out.println(user); //不是同一个对象

        //4. 通过非public的有参构造器创造实例
        //得到private的构造器方法,要用 getDeclaredConstructor
        Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
        //创建实例,要用到 暴力破解
        declaredConstructor.setAccessible(true);//关闭检查
        User user2 = (User)declaredConstructor.newInstance(100,"sssss");
        System.out.println(user2);
    }

通过反射访问类中的成员

java 动态和静态 java动态加载和静态加载_User_14

class User{
    private int age;
    private String name;

    public void say(){
        System.out.println("say方法被调用");
    }
    public void say(String name){
        System.out.println("带有参数的say方法被调用");
    }
    public static void say(String name,int age){
        System.out.println("静态say方法被调用");
    }
    private void hi(){
        System.out.println("private方法被调用");
    }
}
public static void main(String[] args) throws ... {
        //1. 先获取到User类的Class对象
        Class<?> userClass = Class.forName("reflection.User");
        //2. 得到一个对象
        Object o = userClass.newInstance();
        //3. 获取public方法(不带参数)
        Method say = userClass.getMethod("say");
        say.invoke(o);
        //4. 获取public方法(带参数)
        Method say1 = userClass.getMethod("say", String.class); //要把参数的Class写入
        say1.invoke(o,"aaa");
        //5. 获取static方法
        Method say2 = userClass.getMethod("say", String.class, int.class);
        //因为 hi2对应的是static方法,因此不用写对象(写也可以)
        say2.invoke(null,"aaa",12);
        //6. 获取private方法
        Method hi = userClass.getDeclaredMethod("hi");
        hi.setAccessible(true); //爆破
        hi.invoke(o);
    }

如果方法有返回值,统一返回Object,但是它的运行类型(getClass)和方法定义的返回类型一致。