代码题:Java 类的加载与初始化

本文结构:
1.先看几道题
2.类的加载于初始化
(1)类的加载
(2)类的初始化
(a)会发生类的初始化的情况
(b)不会发生类的初始化的情况

首先看几道题。

解析可在看完讲解后再看

Demo1
public class Demo1 {

    public static void main(String args[]) {

        Dog woofer = new Dog();
        Dog nipper = new Basenji();
        woofer.bark();
        nipper.bark();
    }
}


class Dog {
    public Dog() {
    }

    public static void bark() {
        System.out.print("woof ");
    }
}

class Basenji extends Dog {

    public Basenji() {
        super();
    }

    public static void bark() {
    }
}

//输出两个woof

//原因,第二个woof,子类没有覆盖掉父类的方法(静态变量不可被覆盖),构不成多态,故编译看左边,
 //父类引用还是调用的父类方法,即使是new 的子类。
Demo2
public class Demo2 {
    public static void main(String[] args) {
        Father father;
        father=new Son(1000);
       int i=father.getI();
        System.out.println(i);

    }
}

class Father {
    int i = 10;

    public Father() {
        System.out.println(getI());
        System.out.println("父类构造方法");
    }

    public int getI() {
        System.out.println("父类get方法");
        return i;
    }
}

class Son extends Father {
    int i = 100;

    public Son(int i) {
        super();
        System.out.println("子类构造方法");
        this.i = i;
    }

    @Override
    public int getI() {
        System.out.println("子类get方法");
        return i;

    }
}
//输出
//        子类get方法
//        0
//        父类构造方法
//        子类构造方法
//        子类get方法
//        1000

讲一下流程:先父类加载,然后子类加载,这样jvm知道了每个类都有哪些变量和方法。
    这时候new Son(1000),先不传参,先要进行初始化。先对父类初始化,再对子类进行初始化。
    故先进入Son的构造方法,这时候第一句是默认单独super()方法,由此进入父类的构造方法,
    先对父类进行初始化。结果父类构造方法第一句是调用,因为子类覆盖了父类的方法,故调用的是子类的方法。
    因此先输出“子类get方法”,这时子类还没有初始化完成(父类还没呢,因为父类构造方式是初始化的最后一步)
    故返回的是默认值0,输出“0”
    这时再输出“父类构造方法”,至此父类构造方法结束,父类初始化完成。开始子类初始化,输出“子类构造方法”。
    
    因为形成了多态,故调用的是子类的getI方法,因此输出“子类get方法”,并且此时子类初始化完成了,
    故输出“1000”
Demo3
class SingleTon {
    private static SingleTon singleTon = new SingleTon();
    public static int count1;
    public static int count2 = 0;
 
    private SingleTon() {
        count1++;
        count2++;
    }
 
    public static SingleTon getInstance() {
        return singleTon;
    }
}
 
public class Demo3 {
    public static void main(String[] args) {
        SingleTon singleTon = SingleTon.getInstance();
        System.out.println("count1=" + singleTon.count1);
        System.out.println("count2=" + singleTon.count2);
    }
}
1:SingleTon singleTon = SingleTon.getInstance();调用了类的SingleTon调用了类的静态方法,触发类的初始化
2:类加载的时候在准备过程中为类的静态变量分配内存并初始化默认值 singleton=null count1=0,count2=0
3:类初始化化,因为是SingleTon.getInstance()这种初始化只会初始化静态变量和执行静态代码块儿和执行getInstance()方法,
但这时第一句是private static SingleTon singleTon = new SingleTon(); 
这一句给singleton分配了一块儿内存空间并且进行初始化,因为静态变量只能初始化一次并且只要初始化开始那么这次的初始化就占住了位置只能由他来初始化,
所以这些静态变量只能由SingleTon.getInstance()这次初始化来完成,new SingleTon()的这次初始化就是初始化非静态成员变量和构造方法 
4:这时conunt1=1,count2=2,然后new的初始化完毕,之后再继续初始化静态变量(给静态变量赋值)count1不变为1;count2=0;
再执行SingleTon.getInstance()把值赋给外面的singleTon;

5:继续为count1与count2赋值,此时count1没有赋值操作,所有count1为1,但是count2执行赋值操作就变为0

类的加载与初始化

加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的。

(1)类的加载

  • 创建类的实例(首次创建该类对象)

    关于构造方法的执行时机,构造方法,在创建对象的最后一步,才会执行

  • 访问类的静态变量(首次)

  • 调用类的静态方法(首次)

  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

  • 加载某个类的子类,会先触发父类的加载

  • 直接使用java.exe命令来运行某个主类

(2)类的初始化

类的初始化主要就是对静态的类变量进行初始化:

(1)执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的显式赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)

(2)当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化

(3)虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步,每一个类在内存中都只有唯一的一个Class对象。

虽然类的加载大多数时候和类初始化是一气呵成的,但其实类的加载不一定就会触发类的初始化。

(a)会发生类的初始化的情况:

(1)当虚拟机启动,先初始化main方法所在的类

(2)使用new关键字创建一个类的对象

(3)调用该类的静态变量(final的常量除外)和静态方法

(4)使用java.lang.reflect包的方法对类进行反射调用

(5)当初始化一个类时,如果其父类没有被初始化,则先会初始化他的父类

(b)不会发生类的初始化的情况:

(1)引用静态常量不会触发此类的初始化,静态代码块不会执行,去掉final会执行

public class ClinitTest {
    public static void main(String[] args) {
        System.out.println(Son.NUM);
        System.out.println(Father.NUM);
    }
}

class Father {
    public static final int NUM = 10;

    static {
        System.out.println("Father类静态代码块");
    }
}

class Son extends Father {
    static {
        System.out.println("Son类静态代码块");
    }
}

//输出:
//		10
///		10

(2)当访问一个静态成员时,只有真正声明这个静态成员的类才会被初始化,下面代码中Son类不会初始化,Son类的静态代码块不会执行

public class ClinitTest {
    public static void main(String[] args) {
        System.out.println(Son.NUM);
    }
}

class Father {
    public static int NUM = 10;
    static {
        System.out.println("Father类静态代码块");
    }
}

class Son extends Father {
    static {
        System.out.println("Son类静态代码块");
    }
}

//输出:
//		Father类静态代码块
//		10

(3)某类型数组的动态初始化,不会触发此类的初始化

public class ClinitTest {
    public static void main(String[] args) {
        //没有创建Person类的对象,创建的是准备用来装Person对象的数组对象
        Person[] people = new Person[10];
    }
}

class Person {
    static {
        System.out.println("Person类静态代码块");
    }
}
posted @ 2021-01-30 13:58  牛家俊  阅读(149)  评论(1编辑  收藏  举报