反射的应用

137 阅读24分钟

16.2.2 创建对象

之前咱们创建对象是很简单的事情,直接new。
​
总结,到目前为止,我们要拿到某个类的对象用过哪些方式?
(1)直接new
Student stu = new Student();
​
(2)调用方法
A:案例1
ArrayList<String> list = new ArrayList();
list.add("hello");
....
​
Iterator<String> iter = list.iterator();
//获取的是Iterator接口的实现类对象,这个实现类在ArrayList中以内部类Itr形式存在。
​
B:案例2
LocalDate today = LocalDate.now();
​
(3)获取某个类的常量对象
Month m = Month.MARCH;
​
今天我们要教大家另一种获取/创建某个类的对象的方式。
(4)通过Class对象直接new对象
步骤:
①先获取这个类型的Class对象
(4种方式可选,选择任意一种都相同)
②调用Class对象的newInstance()方法来创建这个类的对象
​
这种方式要求该类型必须有公共的无参构造。
建议大家写类的时候尽量保留公共的无参构造。
​
(5)通过Class对象 + Constructor对象来new对象
步骤:
①先获取这个类型的Class对象
(4种方式可选,选择任意一种都相同)
②根据Class对象来获取你要调用的这个类的某个构造器的Constructor对象
​
Constructor<T> getConstructor(Class<?>... parameterTypes)
          返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
          返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
​
③调用Constructor对象的newInstance()方法来创建这个类的对象
​
如果构造器不是public,或者说在当前位置是不可见,那么通过(5)方式,也可以创建它的对象,
但是需要再调用newInstance()方法创建对象之前,需要调用一下下面的代码:
    Constructor对象.setAccessible(true)
import org.junit.Test;
​
import java.lang.reflect.Constructor;
​
public class TestCreateObject {
    @Test
    public void test1()throws Exception{
        //(1)获取Student类的Class对象
        Class<?> clazz = Class.forName("com.atguigu.bean.Student");
        //(2)调用Class对象的newInstance()方法来创建这个类的对象
        //现在Class对象代表的是 Student类
        Object obj = clazz.newInstance();//调用是Student类的公共的无参构造
        //这个obj的编译时类型是Object,但是实际类型/运行时类型是Student
        System.out.println(obj);//自动调用obj.toString
        //编译时找的是Object类toString
        //如果Student类没有重写toString,仍然执行Object类的toString
        //如果Student类重写toString,仍然执行Student类的toString
    }
​
    @Test
    public void test2()throws Exception{
        //(1)获取Student类的Class对象
        Class<?> clazz = Class.forName("com.hh.bean.Student");
        //现在Class对象代表的是 Student类
​
        //(2)获取Constructor对象
        /*
        Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
          返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
​
         */
        Constructor<?> constructor = clazz.getDeclaredConstructor(int.class, String.class);
        //这里(int.class,String.class)是你要获取的Student类的某个构造器的形参类型列表
​
​
        constructor.setAccessible(true);//取消了权限修饰符的检查
​
        //(3)通过Constructor对象的newInstance()方法来创建这个类的对象
        Object obj = constructor.newInstance(1, "张三");//这里创建的是Student类的对象
        //这个obj的编译时类型是Object,但是实际类型/运行时类型是Student
​
        System.out.println(obj);
        //编译时找的是Object类toString
        //如果Student类没有重写toString,仍然执行Object类的toString
        //如果Student类重写toString,仍然执行Student类的toString
    }
}

16.2.3 操作属性

回忆之前如何操作某个类的成员变量?
(1)方式一:要求该成员变量在使用的位置是可见的
①静态
    类名.静态变量
②非静态
    对象名.实例变量
(2)方式二:要求该成员变量有对应的get/set方法,当然该get/set方法在使用的位置必须是可见的
①静态
    类名.setXxx(值)
    变量 = 类名.getXxx()
②非静态
    对象名.setXxx(值)
    变量 = 对象名.getXxx()
​
今天要教大家新的方式,通过反射操作某个类的成员变量?
(3)方式三:
​
静态变量:
步骤:
①先获取该类的Class对象
②直接根据Class获取你要操作的静态变量对应的Field对象
​
 Field getField(String name) :返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
 Field getDeclaredField(String name) :返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
③获取该静态变量的值 或者 设置该静态变量值
获取该静态变量的值 :Field的对象.get(null);
设置该静态变量值    : Field的对象.set(null, 值);
这里的null表示不需要Class对象所表示的类型的实例对象。
​
如果该静态变量是私有的,需要在get/set它的值之前,调用如下代码:
    Field的对象.setAccessible(true);
​
​
非静态实例变量:
步骤:
①先获取该类的Class对象
②创建Class对象所表示的类型的实例对象(见通过反射创建对象的笔记)
③直接根据Class获取你要操作的静态变量对应的Field对象
​
 Field getField(String name) :返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
 Field getDeclaredField(String name) :返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
四获取该静态变量的值 或者 设置该静态变量值
获取该静态变量的值 :Field的对象.get(实例对象名);
设置该静态变量值    : Field的对象.set(实例对象名, 值);
这里的实例对象名表示操作哪个对象的xx属性值。
​
如果该静态变量是私有的,需要在get/set它的值之前,调用如下代码:
    Field的对象.setAccessible(true);
​
import org.junit.Test;
​
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
​
public class TestField {
    @Test
    public void test1()throws Exception{
        //①先获取该类的Class对象
        Class clazz = Class.forName("com.hh.bean.Student");
​
//        ②直接根据Class获取你要操作的静态变量对应的Field对象
        // Field getDeclaredField(String name) :返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
        Field schoolField = clazz.getDeclaredField("school");
​
        schoolField.setAccessible(true);//取消权限修饰符的检查
​
        //③获取该静态变量的值
        Object value = schoolField.get(null);//这里null的意思,表示不需要Student类的对象
        //这里的value是Student类的school静态变量的值
        System.out.println(value);
​
        System.out.println("----------------------");
​
        schoolField.set(null, "嘿嘿");
        value = schoolField.get(null);//这里null的意思,表示不需要Student类的对象
        //这里的value是Student类的school静态变量的值
        System.out.println(value);
    }
​
    @Test
    public void test2()throws Exception{
        //①先获取该类的Class对象
        Class clazz = Class.forName("com.hh.bean.Student");
​
        //②创建Student类的对象
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);//取消了权限修饰符的检查
        Object obj1 = constructor.newInstance();//这里创建的是Student类的对象
        Object obj2 = constructor.newInstance();//这里创建的是Student类的对象
​
​
//        ③直接根据Class获取你要操作的静态变量对应的Field对象
        // Field getDeclaredField(String name) :返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
        Field idField = clazz.getDeclaredField("id");
​
        idField.setAccessible(true);//取消权限修饰符的检查
​
        //③获取该实例变量id的值
        Object obj1IdValue = idField.get(obj1);//明确指定获取哪个Student对象的id属性
        //这里的obj1IdValue是Student类的obj1对象的id属性值
        System.out.println(obj1IdValue);
​
        System.out.println("----------------------");
​
        idField.set(obj1, 1001);
        obj1IdValue = idField.get(obj1);
        System.out.println(obj1IdValue);
        System.out.println(obj1);
        System.out.println(obj2);
    }
}
​

16.2.4 操作方法

之前调用方法的方式:要求该方法在使用的位置是可见的
(1)静态方法
    类名.静态方法(【实参列表】)
(2)非静态方法
    对象名.非静态方法(【实参列表】)
​
​
今天要教大家用反射来调用方法:
​
静态方法:
步骤:
①获取该类对应的Class对象
②通过Class对象获取你要调用的方法的Method对象
​
 Method getMethod(String name, Class<?>... parameterTypes)
          返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
 Method getDeclaredMethod(String name, Class<?>... parameterTypes)
          返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
​
③无论该方法是否是私有的(可见的),都可以加如下这句代码:
    Method的对象.setAccessible(true);
​
④通过调用   Method对象的invoke方法,来调用Method对象对应的方法
    Method对象.invoke(null);   表示该方法是无参的
    Method对象.invoke(null,实参列表); 表示该方法是有参
    null表示不需要这个类的实例对象。
​
非静态方法:
步骤:
①获取该类对应的Class对象
​
②要创建该Class对象对应类型的实例对象(见反射创建对象的笔记)
例如:Class对象代表Student类的话,那么这里要创建Student的对象
​
③通过Class对象获取你要调用的方法的Method对象
​
​
 Method getMethod(String name, Class<?>... parameterTypes)
          返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
 Method getDeclaredMethod(String name, Class<?>... parameterTypes)
          返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
​
④无论该方法是否是私有的(可见的),都可以加如下这句代码:
    Method的对象.setAccessible(true);
​
⑤通过调用   Method对象的invoke方法,来调用Method对象对应的方法
        Method对象.invoke(实例对象名);   表示该方法是无参的
        Method对象.invoke(实例对象名,实参列表); 表示该方法是有参
        实例对象名表示该方法的调用需要这个类的实例对象。
import org.junit.Test;
​
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
​
public class TestMethod {
    @Test
    public void test1()throws Exception{
        //获取该类对应的Class对象
        Class clazz = Class.forName("com.atguigu.bean.Student");
​
        //②通过Class对象获取你要调用的方法的Method对象
        Method getSchoolMethod = clazz.getDeclaredMethod("getSchool");//这里表是getSchool方法,没有参数
   
       /*
       ③无论该方法是否是私有的(可见的),都可以加如下这句代码:
        Method的对象.setAccessible(true);   
        */
        getSchoolMethod.setAccessible(true);//如果该方法是public的,这句话也可以不写
        
        //④通过调用   Method对象的invoke方法,来调用Method对象对应的方法
        Object value1 = getSchoolMethod.invoke(null);
        //这里传null表示不需要Student的对象
        //这里的value1是值 getSchool方法的返回值
        System.out.println("value1 = " + value1);
        //value1 = null,因为school变量没有赋值,默认值是null
​
        System.out.println("------------------------");
        //②通过Class对象获取你要调用的方法的Method对象
        Method setSchoolMethod = clazz.getDeclaredMethod("setSchool",String.class);
        //这里表是setSchool方法,有参数,1个参数,参数的类型是String类型
​
        setSchoolMethod.setAccessible(true);//如果该方法是public的,这句话也可以不写
​
        Object value2 = setSchoolMethod.invoke(null,"尚硅谷");
        //这里传null表示不需要Student的对象
        //这里传"尚硅谷"表示 setSchool方法需要一个实参"尚硅谷"
        //这里的value2是值 setSchool方法的返回值
        System.out.println("value2 = " + value2);
//        value2 = null,是因为setSchool没有返回值,该方法的返回值类型是void
​
        value1 = getSchoolMethod.invoke(null);
        System.out.println("value1 = " + value1);
​
    }
​
    @Test
    public void test2()throws Exception{
        //获取该类对应的Class对象
        Class clazz = Class.forName("com.atguigu.bean.Student");
​
        //创建Student类的对象
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object obj1 = constructor.newInstance();//创建的是Student类的对象1
        Object obj2 = constructor.newInstance();//创建的是Student类的对象2
​
        //②通过Class对象获取你要调用的方法的Method对象
        Method getIdMethod = clazz.getDeclaredMethod("getId");
​
       /*
       ③无论该方法是否是私有的(可见的),都可以加如下这句代码:
        Method的对象.setAccessible(true);
        */
        getIdMethod.setAccessible(true);//如果该方法是public的,这句话也可以不写
​
        //④通过调用   Method对象的invoke方法,来调用Method对象对应的方法
        Object value1 = getIdMethod.invoke(obj1);
        //这里传obj1表示需要Student的对象,通过obj1对象调用getId方法
        //这里的value1是值 obj1对象调用getId方法的返回值
        System.out.println("value1 = " + value1);
        //value1 = 0,因为id变量没有赋值,默认值是0
​
        System.out.println("------------------------");
        //②通过Class对象获取你要调用的方法的Method对象
        Method setIdMethod = clazz.getDeclaredMethod("setId",int.class);
        //这里表是setId方法,有参数,1个参数,参数的类型是int类型
​
        setIdMethod.setAccessible(true);//如果该方法是public的,这句话也可以不写
​
        Object value2 = setIdMethod.invoke(obj1,1001);
        //这里传obj1表示需要Student的对象,通过obj1对象调用setId方法
        //这里传1001表示 setId方法需要一个实参1001
        //这里的value2是值 setId方法的返回值
        System.out.println("value2 = " + value2);
//        value2 = null,是因为setId没有返回值,该方法的返回值类型是void
​
        value1 = getIdMethod.invoke(obj1);
        System.out.println("value1 = " + value1);
​
        System.out.println(obj1);
        System.out.println(obj2);
​
    }
}

16.3 注解

16.3.1 注解的概念

1什么是注解?
在Java代码中出现 @注解名
​
例如:@Override2注解有什么用?
注解的作用是对代码进行注释这个注释不仅仅是给人看的,也可以被程序读取,
程序读取该注解之后可以执行相应的代码
​
例如:
当我们在某个方法上加上@Override,那么编译器(编译器也是一段程序)会读取到该注解,
读取之后会对该方法进行“更加”严格的格式检查,看该方法是否符合“重写”的要求
​
如果不加@Override,编译器也会对方法进行格式检查,只是检查更宽泛更松一些
​
加或不加@Override本身,并不会改变方法是否重写的本质3JavaSE阶段,基础的注解只有少数的几个:1@Override:标记重写的方法2@Deprecated:标记已过时的类方法属性等
在之前的版本,JDK不会删除已过时的类方法等,只是标记它们已过时,不建议程序使用
这种方式称为“逻辑”删除
后续的新JDK版本,可能会把已过时的类方法等删除,而且是“物理”删除3@SuppressWarnings:抑制警告
public class TestOverride {
}
class Father{
    public static void method(){
       //...
    }
​
    public void print1n(){
        //....
    }
​
    public void fun(){
​
    }
}
class Son extends Father{
    //不是重写,编译器并没有提示我们违反了重写的要求
//    @Override
    public static void method(){
        //...
    }
    //不是重写,编译器并没有提示我们违反了重写的要求
//    @Override
    public void println(){
        //...
    }
​
    //正确的重写,加或不加,都没有影响
    @Override
    public void fun(){
​
    }
}
​
import java.util.Date;
​
public class TestSuppressWarnings {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        int a = 10;
​
        Date d = new Date(2023,4,8);
        System.out.println(d);
​
    }
}
​

16.3.2 注解的定义、使用、读取

4一个完整的注解,需要包含3个部分,缺少一个都没有意义1)声明
(2)使用
(3)读取:决定注解的意义靠它
​
5如何声明一个注解?
(1)形式一:没有任何成员的注解
修饰符 @interface 注解名{
}
​
(2)形式二:没有任何成员的注解
修饰符 @interface 注解名{
    成员
}
​
6使用
(1)对于形式一的注解,使用的时候非常简单
@注解名
​
注意:使用的位置如果需要限制的话,需要在注解定义时,用元注解加以说明
​
​
7元注解
元注解(加在注解上面的注解,给注解加的注解)
(1@Target:标记注解可以使用的目标位置
@Target(指定位置),这里的位置必须通过ElementType枚举类的几个常量对象来指定
一共是10个位置可以选,可以选择其中的1个或多个
TYPE(类型定义上面), FIELD(属性上面),METHOD(方法上面),
PARAMETER(形参上面),CONSTRUCTOR(构造器上面),LOCAL_VARIABLE(局部变量),
ANNOTATION_TYPE(注解类型),PACKAGE(包),TYPE_PARAMETER(泛型变量<T>),TYPE_USE(泛型使用<String>)
​
(2@Retention:标记注解的生命周期
@Retention(生命周期),这里的生命周期必须通过RetentionPolicy枚举类的3个常量对象之一来指定
SOURCE(源码阶段),CLASS(字节码阶段),RUNTIME(运行时)
​
SOURCE(源码阶段):该注解只能被编译器读取
CLASS(字节码阶段):该注解可以被编译器读取,也可以被类加载器加载过程中读取,JVM中不保留它
RUNTIME(运行时):该注解可以被编译器类加载器反射代码读取,JVM中保留它
​
(3@Documented:该注解可以被javadoc.exe读取,生成到API文档中
咱们自己定义的注解,加不加它没什么用
​
(4@Inherited:标记该注解是否可以被子类继承8读取
自定义的注解必须通过“反射”读取
注意:如果使用的注解要被“反射”读取到,那么要求该注解的生命周期必须是RUNTIME
    注解的生命周期,需要在注解定义时,用元注解加以说明

16.3.3 注解的成员定义、使用

9、注解的成员
【修饰符】 @interface 注解名{
    返回值类型 方法名();
    返回值类型 方法名() default 默认返回值;
}
​
注意:
(1)注解中的抽象方法,不能有形参,()必须是空的。
(2)注解中的抽象方法,返回值类型只能是
八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
​
10、包含成员的注解的使用
@注解名(抽象方法名=返回值)
​
注意:
(1)如果抽象方法名是value,且只有一个抽象方法需要指定返回值,那么可以省略value=
换句话说,如果要为多个抽象方法指定返回值,那么必须在每一个返回值前面写明"方法名=",
       如果抽象方法名不是value,那么也必须在返回值前面写明"方法名="。
(2)抽象方法可以指定默认返回值
​
​
​
总结一下default关键字的使用位置:
①switch-case
②接口中定义默认方法
③注解中给抽象方法指定默认返回值
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TwoAnnotation {
    String value();
    String method() default "hello";
}
@OneAnnotation
@TwoAnnotation("哈哈")
public class MyClass {
    @OneAnnotation
    private int a;
​
    @OneAnnotation
    public void method(){
//        @OneAnnotation
        int num = 1;
    }
}

16.4 类加载

16.4.1 类加载器

1、类加载的过程需要用到类加载器,类加载器分为几种:
(1)引导类加载器或根加载器
它负责加载jre/rt.jar核心库
根加载器不是Java语言实现的,是C/C++语言实现的。
​
(2)扩展类加载器(Extension ClassLoader)
它负责加载JRE\lib\ext
​
(3)应用程序类加载器
它负责加载项目的classpath下的类型(自定义类型,第三方依赖的jar)
​
2、类加载器为什么要这么多种,它们是如何合作工作的?
工作模式:双亲委托模式
这么设计的目的:安全
​
双亲:
角度一:继承
角度二:组合(选的是它)
    扩展类加载器里面有一个parent的成员变量,记录了引导类加载器
    应用程序类加载器里面有一个parent的成员变量,记录了扩展类加载器
    换句话说:
        扩展类加载器对象.getParent()可以得到引导类加载器
        应用程序类加载器对象.getParent()可以得到扩展类加载器
​
 双亲委托模式:(了解)
(1) 当 应用程序类加载器 接到加载某个类的任务时,
会先在JVM中检查一下,这个类是否已经被加载过了,如果已经加载过了,就直接返回它的Class对象,
就不用重复加载。
如果这个类没有被加载过,它会把这个任务先“委托”给parent加载器,这里就是委托给扩展类加载器。
(2)当扩展类加载器 接到加载某个类的任务时,
会先在JVM中检查一下,这个类是否已经被加载过了,如果已经加载过了,就直接返回它的Class对象,
就不用重复加载。
如果这个类没有被加载过,它会把这个任务先“委托”给parent加载器,这里就是委托给引导类加载器。
(3)当引导类加载器 接到加载某个类的任务时,
会先在JVM中检查一下,这个类是否已经被加载过了,如果已经加载过了,就直接返回它的Class对象,
就不用重复加载。
如果这个类没有被加载过,它会在自己负责的路径下搜索这个类的.class文件,如果可以正确加载,
那么返回它的Class对象,加载完毕。
如果在它负责的路径下没有找到这个类的.class文件或者无法正确加载,那么它会把任务或异常往回传,
给扩展类加载器。
(4)当扩展类加载器 再次接到parent回传的加载某个类的任务时,
它会在自己负责的路径下搜索这个类的.class文件,如果可以正确加载,
那么返回它的Class对象,加载完毕。
如果在它负责的路径下没有找到这个类的.class文件或者无法正确加载,那么它会把任务或异常往回传,
给应用程序类加载器。
(5) 当 应用程序类加载器 再次接到parent回传的加载某个类的任务时,
它会在自己负责的路径下搜索这个类的.class文件,如果可以正确加载,
那么返回它的Class对象,加载完毕。
如果在它负责的路径下没有找到这个类的.class文件或者无法正确加载,就报ClassNotFoundException或NoClassDefError。
​

​
import org.junit.Test;
​
public class TestClassLoader {
    @Test
    public void test1()throws Exception{
        Class clazz = Class.forName("java.lang.String");
        ClassLoader loader = clazz.getClassLoader();
        System.out.println(loader);
    }
​
    @Test
    public void test2()throws Exception{
        Class clazz = Class.forName("com.sun.nio.zipfs.ZipUtils");
        ClassLoader loader = clazz.getClassLoader();
        System.out.println(loader);
    }
​
    @Test
    public void test3()throws Exception{
        Class clazz = Class.forName("com.haha.load.TestClassLoader");
        ClassLoader loader = clazz.getClassLoader();
        System.out.println(loader);
​
        ClassLoader parent = loader.getParent();
        System.out.println(parent);
​
        ClassLoader grandParent = parent.getParent();
        System.out.println(grandParent);
    }
}
​

16.4.2 类加载过程

3、类加载器如何加载一个类?
(1)加载:load
就是指将类型的class字节码数据读入内存 ==> IO过程
(2)连接:link
①验证:校验合法性等
例如:字节码文件一定是以cafebabe开头
     版本等问题
②准备:准备对应的内存(方法区),创建Class对象,为类变量(静态变量)赋默认值,为静态常量赋初始值。
如果此时加载的是Student类,school和value有分配内存,并且有默认值,分别是null和0.
但是并没有为id和name属性分配存储值的内存,因为它们的内存需要在new Student对象时才会
在堆中开辟内存存储值。
public class Student {//类加载之后,会用一个Class对象来描述Student类
    private static String school;//静态变量
    private static int value;//静态变量
    private int id; //类加载之后,会用一个Field对象来描述id属性
    private String name;
}
③解析:把字节码中的符号引用替换为对应的直接地址引用
​
(3)初始化:initialize(类初始化)即执行<clinit>类初始化方法,
    大多数情况下,类的加载就完成了类的初始化,有些情况下,会延迟类的初始化。
​
    类初始化为类的静态变量赋值用的。

16.5 类初始化

16.5.1 类初始化过程

1、类初始化的目的:为类的静态变量赋值用的。
如果这个类没有定义静态变量,那么这个过程相当于什么都没的干。
​
2、类初始化和哪些代码有关
(1)静态变量的显式赋值
(2)静态代码块
​
(1)和(2)是按照编写顺序执行。
本质上,类在编译时其实会把和类初始化有关的代码,重新组装为一个<clinit>()的方法,
称为类初始化方法。
cl:classinit:initialize初始化
​
3、如果子类加载和初始化时,发现父类还没有被加载和初始化,应该会先加载和初始化父类。
但是如果父类已经初始化过了,不会重复初始化父类。
​
注意:每一个类只会初始化1次。先父后子。
public class Demo {
    private static int a = 1;
    static{
        System.out.println("a = " + Demo.a);
        System.out.println("静态代码块");
        a = 2;
        System.out.println("a = " + Demo.a);
    }
    public static void method(){
        System.out.println("Demo.method ");
        System.out.println("a = " + a);
    }
}
​
public class TestDemo {
    public static void main(String[] args) {
        Demo.method();
​
    }
}
​
public class Base {
    private static int a = 1;
    static{
        System.out.println("a = " + a);
        System.out.println("父类的静态代码块");
    }
}
​
public class Sub extends Base{
    private static int b = 2;
    static{
        System.out.println("b = " + b);
        System.out.println("子类的静态代码块");
    }
​
    public static void method(){
        System.out.println("Sub.method");
    }
}
​
public class TestSub {
    public static void main(String[] args) {
        Sub.method();
    }
}
​

16.5.2 触发类初始化?

4、哪些行为会触发类的初始化呢?
(1)main方法所在的类,在执行main方法之前,会先初始化主类
(2new对象之前,会先初始化这个类
(3)调用某个类的静态成员(静态方法、静态变量等),会先初始化这个类
(4)用反射代码操作某个类,会先初始化这个类
(5)子类初始化时,如果父类没有初始化,会触发父类初始化
​
5、某些行为不会触发类的初始化,即只会加载这个类,但是类初始化被延迟了。
(1)使用某个类的静态的常量(static  final)
(2)通过子类调用父类的静态变量,静态方法,只会导致父类初始化,不会导致子类初始化,即只有声明静态成员的类才会初始化
(3)用某个类型声明数组并创建数组对象时,不会导致这个类初始化
​
public class TestClinit {
    static {
        //静态代码块中,正常应该编写和静态变量赋值有关的代码
        //但是这里为了演示它的执行过程,所以这里编写输出语句
        System.out.println("主类的初始化");
    }
    public static void main(String[] args) throws Exception{
        System.out.println("hello");
​
/*        new Example();
​
        MyDemo.method();*/
​
        Class<?> clazz = Class.forName("com.haha.clinit.Example");
​
    }
}
class Example{
    static {
        //静态代码块中,正常应该编写和静态变量赋值有关的代码
        //但是这里为了演示它的执行过程,所以这里编写输出语句
        System.out.println("Example类的初始化");
    }
}
class MyDemo{
    static {
        //静态代码块中,正常应该编写和静态变量赋值有关的代码
        //但是这里为了演示它的执行过程,所以这里编写输出语句
        System.out.println("MyDemo类的初始化");
    }
    public static void method(){
        System.out.println("MyDemo.method");
    }
}
public class TestNonClinit {
    public static void main(String[] args) {
        System.out.println(MyMath.PI);
​
        Son.method();//这个方法是从父类继承的
​
        Son[] arr = new Son[5];//这里创建的是Son[]类型的对象,不是Son类型的对象
        arr[0] = new Son();//创建Son类对象,就会触发Son类的初始化
        System.out.println(arr.length);
    }
}
​
class MyMath{
    public static final double PI = 3.14;
    static{
        System.out.println("MyMath类初始化");
    }
}
class Father{
    static{
        System.out.println("Father类初始化");
    }
    public static void method(){
        System.out.println("Father.method");
    }
}
class Son extends Father{
    static{
        System.out.println("Son类初始化");
    }
}

16.6 实例初始化

16.6.1 实例初始化过程

1、什么是实例初始化?
“创建对象”的过程中为对象的“实例变量”赋值的过程,称为实例初始化。
实例变量是指非静态的成员变量。
​
【修饰符】 class 类名{
    【修饰符】 static  数据类型 静态变量;
    【修饰符】  数据类型 实例变量;
​
    static{
        //静态代码块
        数据类型 变量名;//局部变量
    }
​
    {
        //非静态代码块
         数据类型 变量名;//局部变量
    }
​
    【修饰符】 返回值类型 方法名(【形参列表】){//形参是局部变量
        数据类型 变量名; //局部变量
    }
}
​
2、实例初始化过程和哪些代码有关?
(1)super()或super(实参列表)
(2)实例变量的显式赋值
    private int a = 1; 其中 a= 1;就是实例变量的显式赋值
(3)非静态代码块,又称为构造块(了解)
(4)构造器中除了(1)语句,剩下的代码
​
它们的执行顺序是(1)(2)(3)(4)
它们的执行顺序是(1)(3)(2)(4)
(2)(3)按照编写的顺序。绝大部分类没有(2)和(3)
​
结论:
    每次new对象,上面(1)(2)(3)(4)都要走一遍。
    只不过new后面的构造器不同,那么(1)(4)可能会不同。
​
本质上,上面4个部分的代码会在编译时组装成一个一个的<init>方法,实例初始化方法。
public class Demo {
    private int a = 1;
    {
        System.out.println("a = " + a);
        System.out.println("非静态代码块");
        a = 2;
    }
​
    public Demo(){
//        super();//只要构造器首行没有写this(),
//        this(实参列表),super(实参列表),默认就是super()
        System.out.println("无参构造");
    }
​
    public Demo(int a){
        //        super();//只要构造器首行没有写this(),
        //        this(实参列表),super(实参列表),默认就是super()
        this.a = a;
        System.out.println("有参构造");
    }
}
​

​
import java.lang.reflect.Constructor;
​
public class TestDemo {
    public static void main(String[] args) throws Exception{
/*        Demo d1 = new Demo();
​
        Demo d2 = new Demo(3);*/
​
        Class clazz = Class.forName("com.haha.instance.Demo");
        Constructor constructor = clazz.getDeclaredConstructor(int.class);
    }
}
​

16.6.2 先父后子

3、原则:子类的实例初始化过程中,一定会执行父类相应的实例初始化代码。
 super() ==> 对应父类的无参构造 ==> 对应父类的<init>()
 super(实例列表)==> 对应父类的有参构造 ==> 对应父类的<init>(形参列表)
public class Father {
    private int a;
    {
        System.out.println("1.父类的非静态代码块");
    }
​
    public Father() {
        System.out.println("2.父类的无参构造");
    }
​
    public Father(int a) {
        this.a = a;
        System.out.println("3.父类的有参构造");
    }
}
​

​
public class Son extends Father{
    {
        System.out.println("4.子类的非静态代码块");
    }
​
    public Son() {
        //super();
        System.out.println("5.子类的无参构造");
    }
​
    public Son(int a) {
        super(a);
        System.out.println("6.子类的有参构造");
    }
}
​
public class TestSon {
    public static void main(String[] args) {
        Son s = new Son();//1245
        Son s2 = new Son(1);//1346
    }
}
​

16.6.3 先类初始化后实例初始化

4、先完成类的初始化,才能进行实例初始化。
  类初始化只会执行一次,实例初始化每次new都执行。
public class Base {
    private static int a;
    private int b;
    static{
        System.out.println("1.Base父类的静态代码块");
    }
    {
        System.out.println("2.Base父类的非静态代码块");
    }
​
    public Base() {
        System.out.println("3.Base父类的无参构造");
    }
​
    public Base(int b) {
        this.b = b;
        System.out.println("4.Base父类的有参构造");
    }
}
​
public class Sub extends Base{
    static{
        System.out.println("5.Sub子类的静态代码块");
    }
    {
        System.out.println("6.Sub子类的非静态代码块");
    }
​
    public Sub() {
        //super();
        System.out.println("7.Sub子类的无参构造");
    }
​
    public Sub(int b) {
        super(b);
        System.out.println("8.Sub子类的有参构造");
    }
}
​
public class TestSub {
    public static void main(String[] args) {
        Sub s = new Sub();
        //152367
​
        Sub s2 = new Sub(5);
        //2468
    }
}
​