概述
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类加载问题所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复