我是靠谱客的博主 爱笑外套,最近开发中收集的这篇文章主要介绍java类加载问题,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

package com.company;

public class Main {
    public static void main(String[] args) {
	// write your code here
        Son son = new Son();
        System.out.println("---end---");
    }
}
class Son extends Father {
    private int i = 1;
    private long l = 2L;
    static int ssi = 3;
    {
        System.out.println("1son init block");
    }
    static {
        System.out.println("2son static block");
    }
    Son() {
        l = 3L;
        System.out.println("3son constructor");
    }
}
class Father {
    int fi;
    static int fsi = 4;
    static Son son = new Son();
    {
        System.out.println("4father init block");
    }
    static {
        System.out.println("5father static block");
    }
    Father() {
        fi = 1;
        System.out.println("6father constructor");
    }
}

以上代码,会输出什么样的结果呢?

4father init block
6father constructor
1son init block
3son constructor
5father static block
2son static block
4father init block
6father constructor
1son init block
3son constructor
---end---

最终结果如上所示。
在程序正常运行的时候,是会先编译成字节码,然后按照字节码的顺序去运行。此时字节码的排序就有很多讲究了。
此例子中,主程序中new了一个新的son,那么son要先进行初始化,但是son继承了父类father,在字节码中会先出现super()的东西,也就是先要对father进行初始化。因此也就会先执行father中的初始化模块的代码,也就是"4father init block"和"6father constructor"。然后是进行son自己的初始化,因此执行son自己的初始化模块的代码"1son init block"和"3son constructor"。
初始化完成之后,就是要执行static块的代码,因为是继承了父类,所以是要先执行父类的5father static block。然后是自己的static块代码2son static block。
然后是static的成员变量,此时又new了一个son,那就是重复前面一部分了。
哈哈哈哈,发现如果在father中new新的son时,如果不加上static,会陷入循环,出现错误。


再来看一道题:

public class Main {
    public static void main(String[] args) {
	// write your code here
        SingleTon.getInstance();
        System.out.println(SingleTon.count1);
        System.out.println(SingleTon.count2);
    }
}

class SingleTon{
    public static int count1;
    public static int count2 = 0;
    private static SingleTon instance = new SingleTon();
    private SingleTon(){
        count1++;
        count2++;
    }
    public static SingleTon getInstance() {
        return instance;
    }
}

输出的结果是1 1
如果更改instance被new的所在行数,那么结果就是不一样的

public class Main {
    public static void main(String[] args) {
	// write your code here
        SingleTon.getInstance();
        System.out.println(SingleTon.count1);
        System.out.println(SingleTon.count2);
    }
}

class SingleTon{
    private static SingleTon instance = new SingleTon();
    public static int count1;
    public static int count2 = 0;
    private SingleTon(){
        count1++;
        count2++;
    }
    public static SingleTon getInstance() {
        return instance;
    }
}

此时的结果就是1 0
区别的关键点就在于,先加载然后blabla之后初始化。
在加载的时候c1和c2都为0,但是初始化的时候是要按照顺序执行的,因此最后的结果不一样。
意思就是先把这个类加载进来,此时c1和c2都赋值为0,第一段代码呢就是按顺序,c1不动,c2赋值为0,然后是执行new对象,这就涉及到构造函数了,分别对c1和c2加1,最终返回的为1 1
第二段代码呢,加载完之后,是先执行的new,引用构造函数,分别加1,然后是c1不动,c2赋值为0,因此结果为1 0


什么时候会初始化?
1.碰见new,或者是getstatic和putstatic(读取和设置一个类的静态字段的时候。而由final修饰的在编译时期就已经把结果放在常量池的静态字段),或者是invokestatic(调用类的静态方法)这4条字节码时,如果类没有进行初始化,则需要先触发其初始化。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先进行初始化
3.当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化(接口初始化例外,不要求所有的父接口全部初始化,只有真正调用到父接口的时候才会初始化)
4.再启动虚拟机的时候,用户需要指定一个需要执行的主类,虚拟机先初始化这个类
5.当使用java7的动态语言支持时,如果一个MethodHandle实例在解析时,该方法对应的类没有进行初始化,则需要先触发其初始化。

以上行为称为对一个类的主动引用,除此之外,其他的方式都不会触发初始化,被称为被动引用。
1.子类引用父类的静态字段,并不会导致子类初始化(此时静态资源不属于父类子类的,底层也只是通过superclass.value进行调用,因此不会对subclass进行初始化)

public class Main {
    public static void main(String[] args) {
	// write your code here
        System.out.println(SubClass.value);
    }
}
class SuperClass{
    static{
        System.out.println("Superclass init");
    }
    public static int value = 123;
}
class SubClass extends SuperClass{
    static{
        System.out.println("subclass init");
    }
}

输出为:

Superclass init
123

2.通过数组定义引用类,不会引发这个类的初始化。

public class Main {
    public static void main(String[] args) {
	// 有加载,但是没有初始化
        SuperClass[] sc = new SuperClass[10];
    }
}
class SuperClass{
    static{
        System.out.println("Superclass init");
    }
    public static int value = 123;
}
class SubClass extends SuperClass{
    static{
        System.out.println("subclass init");
    }
}

3.常量在编译的过程中会存入调用类的常量池中,本质上并没有调用引入常量的类,因此不会触发该类的初始化。

public class Main {
    public static void main(String[] args) {
	// 不是常量,会初始化类,因此此时在获取静态的字段
        System.out.println(SuperClass.value);
    }
}
class SuperClass{
    static{
        System.out.println("Superclass init");
    }
    public static int value = 123;
}
class SubClass extends SuperClass{
    static{
        System.out.println("subclass init");
    }
}

Superclass init
123
public class Main {
    public static void main(String[] args) {
	// 以为加上了final,此时value就是一个常量
        System.out.println(SuperClass.value);
    }
}
class SuperClass{
    static{
        System.out.println("Superclass init");
    }
    public static final int value = 123;
}
class SubClass extends SuperClass{
    static{
        System.out.println("subclass init");
    }
}

123

1.在初始化的时候,会将类成员和static类代码块收集到到()这么个类构造器中,类构造器收集的顺序是源文件中代码的顺序决定的。
静态语句块中,只能访问到静态语句块之前的变量,之后的变量,在静态语句块中只能赋值,但是不能访问。

public class Main {
    static {
        i = 0; //给变量赋值是可以编译通过
        System.out.println(i); //但是不能访问
    }
    static int i = 1; //变量声明
    public static void main(String[] args) {
        
    }
}

2.类构造器()方法与类的构造器()方法不同,虚拟机会保证在子类的()方法之前执行,因此,在虚拟机中第一个被执行的()方法的类肯定是Object。
3.由于父类的()方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值
4.类构造器()方法对于类或者接口来说并不是必须的,如果一个类中没有静态语句块,编译器就不会为了这个类生成()方法。
5.接口中也可以定义static变量,生成()方法不需要先执行父接口中的()方法,同理,接口的实现类在初始化的时候也一样不会执行接口中的()方法
6.虚拟机会保证一个类的()方法在线程中被正确的枷锁,同步,如果多个线程同时去初始化一个类,只会有一个线程去初始化,其他线程阻塞。

public class Main {
    public static void main(String[] args) {
        Runnable run = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始创建对象");
                Loader c = new Loader();
                System.out.println(Thread.currentThread().getName() + "创建对象结束");
            }
        };
        //只能有一个线程进入到clinit方法中去执行,保证类的初始化方法只执行一次
        new Thread(run).start();
        new Thread(run).start();
    }
}
class Loader{
    static{
        System.out.println("CInit 开始了");
        if(true){
            while(true){

            }
        }
    }
}

Thread-0开始创建对象
Thread-1开始创建对象
CInit 开始了

最后

以上就是爱笑外套为你收集整理的java类加载问题的全部内容,希望文章能够帮你解决java类加载问题所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(41)

评论列表共有 0 条评论

立即
投稿
返回
顶部