JVM(六) 类的初始化

HOW

途径

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。在程序中,静态变量的初始化有两种途径:(1)在静态变量的声明处进行初始化;(2)在静态代码块中进行初始化。例如在以下代码中,静态变量a和b都被显示初始化,而静态变量c没有被显示初始化,它将保持默认值0。

public class Sample{
   private static int a =1;       //在静态变量的声明处进行优化
   public static long b;
   public static long c;

   Static{
      b=2;          //在静态代码块中进行初始化
   }
   ...
}

依次执行

静态变量的声明语句,以及静态代码块都被看做类的初始化语句,Java虚拟机会按照初始化语句在类文件中的先后顺序来依次执行它们。例如当以下Sample类被初始化后,它的静态变量a的取值为4.

public class Sample {
	static int a =1;
	static{ a=2;}
	static{ a=4;}
	public static void main(String args[]){
		System.out.println("a="+a); //打印a=4
	}
}

类的初始化步骤

(1)假如这个类还没有被加载和连接,那就先进行加载和连接。

(2)假如类存在直接的父类,并且这个父类还没有被初始化,那就先初始化直接的父类。

(3)假如类中存在初始化语句,那就依次执行这些初始化语句。


类的初始化时机(前面的博客已经提过两遍)

主动使用(六种)

1、创建类的实例

例:new Test();

2、访问某个类或接口的静态变量,或者对该静态变量赋值

例:int b=Test.a;

Test.a=b;

3、调用类的静态方法

例:Test.doSomething();

4、反射

例:Class.forName("come.tgb.Test");

5、初始化一个类的子类

例:class Parent{}

class child extends Parent

{public static int a =3;}

Child.a=4;

6、Java虚拟机启动时被标明为启动类的类

例:Test.java可能被编译成多个类文件,Test.class, Parent.class, Child.class,最终运行java Test,那么Test.class就是启动类。

除了以上6种情况,其他使用Java类的方式都被看看作是对类的被动使用,都不会导致类的初始化。


当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。

在初始化一个类时,并不会先初始化它所实现的接口。

在初始化一个接口时,并不会先初始化它的父接口。

因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。


干货上架!

Test2.java

class FinalTest
{
	public static final int x = 6/3;
	static 
	{
		System.out.println("FinalTest static block");
	}
}
public class Test2 {
	public static void main(String[] args)
	{
		System.out.println(FinalTest.x);
	}
}
运行结果:

2

分析:

用了final关键字,并将2赋值给x。使x为编译时的常量。所以调用就不会初始化。如果将final关键字去掉,那么就会初始化,依次执行。会输出"FinalTeststatic block”。


Test3.java

package jvmDemo;
import java.util.Random;
class FinalTest2
{
	public static final int x = new Random().nextInt(100); //100是不包含在内的,只产生0~99之间的整数。
static 
	{
		System.out.println("FinalTest static block");
	}
}

public class Test3 {
	public static void main(String[] args)
	{
		System.out.println(FinalTest2.x);
	}
}
运行结果:
FinalTest static block
20

分析:
和Test2.java唯一的不同是x的赋值。这里当编译时并不能确定x的值,运行的时候才确定,所以由主动使用的第二种。


Test4.java

package jvmDemo;

class Parent{
	static int a = 3;
	static 
	{
		System.out.println("Parent static block");	
	}
}

class Child extends Parent
{
	static int b = 4;
	static
	{
		System.out.println("Child static block");
	}
}

public class Test4 {
	static 
	{
		System.out.println("Test4 static block");
	}
	
	public static void main(String[] args)
	{
		System.out.println(Child.b);
	}
}
运行结果:

Test4 static block
Parent static block
Child static block
4

分析:

启动类先加载进行初始化, 对子类的"主动使用"会导致父类被初始化。     


Test5.java

package jvmDemo;

class Parent2{
	static int a = 3;
	static 
	{
		System.out.println("Parent static block");	
	}
}

class Child2 extends Parent2
{
	static int b = 4;
	static
	{
		System.out.println("Child static block");
	}
}

public class Test5 {
	static 
	{
		System.out.println("Test5 static block");
	}
	
	public static void main(String[] args)
	{
		Parent2 parent;
		System.out.println("--------------");
		parent = new Parent2();
		System.out.println(Parent2.a);
		System.out.println(Child2.b);
	}
}
运行结果:

Test5 static block
--------------
Parent static block
3
Child static block
4
分析:

启动类先加载进行初始化,只声明没有创建实例不会对Parent2进行初始化。对父类的"主动使用"并不会导致子类初始化。在同一个类加载器下只能初始化一次。


Test6.java

package jvmDemo;

class Parent3{
	static int a = 3;
	static 
	{
		System.out.println("Parent3 static block");	
	}
	static void dosomething()
	{
		System.out.println("do something");
	}
}

class Child3 extends Parent3
{
	static
	{
		System.out.println("Child3 static block");
	}
}

public class Test6 {
	public static void main(String[] args)
	{
		System.out.println(Child3.a);
		Child3.dosomething();
	}
}
运行结果:

Parent3 static block
3
do something

分析:

只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用。以上程序,Child3.a是对其父类的主动使用。


Test7.java

package jvmDemo;

class CL
{
	static 
	{
		System.out.println("Class CL");	
	}
}


public class Test7 {
	public static void main(String[] args) throws Exception
	{
		//获得系统类加载器
		ClassLoader loader = ClassLoader.getSystemClassLoader();
		
		Class<?> clazz = loader.loadClass("jvmDemo.CL");
		System.out.println("------------------");
		clazz = Class.forName("jvmDemo.CL");
	}
}
运行结果:

------------------
Class CL
分析:

调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

拓展:

getSystemClassLoader()

官网api文档的介绍:http://docs.oracle.com/javase/7/docs/api/

  • getSystemResourceAsStream
    public static InputStream getSystemResourceAsStream(String name)
    Open for reading, a resource of the specified name from the search path used to load classes. This method locates the resource through the system class loader (see getSystemClassLoader()).
    Parameters:
    name - The resource name
    Returns:
    An input stream for reading the resource, or null if the resource could not be found
    Since:
    1.1
loadClass

官网api文档的介绍:http://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#loadClass(java.lang.String)





 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值