关于类的初始化时机的小结
程序员文章站
2022-05-21 19:28:53
...
看了下 臧圩人 的 JAVA面试题解惑系列(一)——类的初始化顺序,链接在这里:[url]http://zangweiren.iteye.com/blog/208122[/url]
感觉总结得不错.我也补充几点.
就是关于类的初始化顺序的问题,其实当java程序需要使用某个类时,java虚拟机会确保这个类已经被加载,连接,和初始化,其中连接过程又会执行验证,准备和解析这三个子步骤.这里不进行详细的讲解.有关资料可以去google下.
那么当类或者接口被加载和连接时,JVM严格定义了初始化的时机,所有的JVM实现必须在每个类或接口被java程序"首次调用"时才初始化它们.很正常,要是你的应用有N多个类,有些类可能一时用不到而JVM统统帮你加载的话,你的机子能受得了吗?
因此,java程序对类的使用方式可分为有两种:[color=red]主动使用和被动使用[/color]那么参照前面的说法,JVM只有在程序需要主动使用一个类或接口时才会初始化它, 那什么时侯是程序对类的主动使用呢?有以下六种情况:
[list]
[*]使用new创建类的实例时,或者通过反射,克隆,和反序列化等方法进行创建类的实例时.
[*]调用类的静态方法时.
[*][color=red]访问某个类/接口的静态变量[其中这里的静态变量的定义又有区别,将会在以下进行讲解],[/color]或对该静态变量进行赋值操作
[*]初始化一个类的子类
[*]JVM启动时被标注为启动类的类.
[/list]
较为特殊的是访问某个类/接口的静态变量这个操作它分为两种情况:
[list]
[*]对于final类型的静态变量,如果在编译期间就能够计算出变量的值,那么访问该静态变量时,将被看作是对类的被动使用,而不会导致类的初始化.
[*]而不能在编译期间确定值的静态变量,访问该静态变量时,将被看作是对类的主动使用,会导致类的初始化.
[/list]
示例1:访问编译常量时,不会导致类的初始化
示例2:访问不能在编译期间确定的静态常量值时,导致类的初始化
较为特殊的情况是当JVM初始化一个类时,要求它的所有父类都已经被初始化.而只有当程序访问的静态变量或静态方法在当前类或接口中定义时,才视作对类的主动使用.参看以下示例:
这时侯可能有人会说:怎么是88呢?那父类不是会被初始化吗?静态块中的打印语句怎么没有执行?这是因为Sub.number_base这个静态的常量是编译期确定的静态常量,所以它不会对Base类进行初始化操作.所以只打印出常量的值.如果在main方法中使用Sub.sayHello()那么将会对Base类进行初始化.同时打印出静态代码块中的输出语句.但是不会对Sub进行初始化操作,参看示例:
而且,最后提醒大家的是,当类存在有继承关系时,类的初始化顺序也会有所不同,看以下的例子,估计你能猜到为何打印结果是这样啦~
第一次发表文章,请大家多多包涵 :wink:
感觉总结得不错.我也补充几点.
就是关于类的初始化顺序的问题,其实当java程序需要使用某个类时,java虚拟机会确保这个类已经被加载,连接,和初始化,其中连接过程又会执行验证,准备和解析这三个子步骤.这里不进行详细的讲解.有关资料可以去google下.
那么当类或者接口被加载和连接时,JVM严格定义了初始化的时机,所有的JVM实现必须在每个类或接口被java程序"首次调用"时才初始化它们.很正常,要是你的应用有N多个类,有些类可能一时用不到而JVM统统帮你加载的话,你的机子能受得了吗?
因此,java程序对类的使用方式可分为有两种:[color=red]主动使用和被动使用[/color]那么参照前面的说法,JVM只有在程序需要主动使用一个类或接口时才会初始化它, 那什么时侯是程序对类的主动使用呢?有以下六种情况:
[list]
[*]使用new创建类的实例时,或者通过反射,克隆,和反序列化等方法进行创建类的实例时.
[*]调用类的静态方法时.
[*][color=red]访问某个类/接口的静态变量[其中这里的静态变量的定义又有区别,将会在以下进行讲解],[/color]或对该静态变量进行赋值操作
[*]初始化一个类的子类
[*]JVM启动时被标注为启动类的类.
[/list]
较为特殊的是访问某个类/接口的静态变量这个操作它分为两种情况:
[list]
[*]对于final类型的静态变量,如果在编译期间就能够计算出变量的值,那么访问该静态变量时,将被看作是对类的被动使用,而不会导致类的初始化.
[*]而不能在编译期间确定值的静态变量,访问该静态变量时,将被看作是对类的主动使用,会导致类的初始化.
[/list]
示例1:访问编译常量时,不会导致类的初始化
/**
* 测试类Tester
*/
class Tester{
public final static int number=100;
static{
System.out.println("Tester类被初始化!number="+number);
}
}
/**
* 在测试类Demo中访问编译时常量
*/
public class Demo1 {
/**
* main方法 程序入口
* @param args
*/
public static void main(String[] args) {
System.out.println(Tester.number); //仅打印出100而静态代码块中的内容不会被输出
}
}
示例2:访问不能在编译期间确定的静态常量值时,导致类的初始化
/**
* 测试类Tester
*/
class Tester{
public final static int number=(int)(Math.random()*100)/10+2;
static{
System.out.println("Tester类被初始化!number="+number);
}
}
/**
* 在测试类Demo中访问编译时常量
*/
public class Demo1 {
/**
* main方法 程序入口
* @param args
*/
public static void main(String[] args) {
System.out.println(Tester.number); //先会求出number的值然后执行类的静态代码块.打印静态代码块内的内容后显示number的结果.
}
}
较为特殊的情况是当JVM初始化一个类时,要求它的所有父类都已经被初始化.而只有当程序访问的静态变量或静态方法在当前类或接口中定义时,才视作对类的主动使用.参看以下示例:
/**
* 父类Base
*/
class Base{
public static final int number_base=88;
static{
System.out.println("Base类被初始化!");
}
public static void sayHello(){
System.out.println("在Base类的sayHello()方法中!");
}
}
/**
* 子类Sub
*/
class Sub extends Base{
public static final int number_sub=66;
static{
System.out.println("Sub类被实例化!");
}
}
/**
* 测试类
*/
public class Demo2 {
/**
* main 方法,程序唯一入口
* @param args
*/
public static void main(String args[]){
//访问父类的静态变量number_base
System.out.println(Sub.number_base);
//正确打印结果为88
}
}
这时侯可能有人会说:怎么是88呢?那父类不是会被初始化吗?静态块中的打印语句怎么没有执行?这是因为Sub.number_base这个静态的常量是编译期确定的静态常量,所以它不会对Base类进行初始化操作.所以只打印出常量的值.如果在main方法中使用Sub.sayHello()那么将会对Base类进行初始化.同时打印出静态代码块中的输出语句.但是不会对Sub进行初始化操作,参看示例:
/**
* 父类Base
*/
class Base{
public static final int number_base=88;
static{
System.out.println("Base类被初始化!");
}
public static void sayHello(){
System.out.println("在Base类的sayHello()方法中!");
}
}
/**
* 子类Sub
*/
class Sub extends Base{
public static final int number_sub=66;
static{
System.out.println("Sub类被实例化!");
}
}
/**
* 测试类
*/
public class Demo2 {
/**
* main 方法,程序唯一入口
* @param args
*/
public static void main(String args[]){
//访问父类的静态变量number_base
System.out.println(Sub.number_base);//正确打印结果为88
//这里调用了继承自父类的sayHello()方法,将会导致父类初始化而子类不会被初始化
Sub.sayHello();
//打印:
//88
//Base类被初始化!
//在Base类的sayHello()方法中!
}
}
而且,最后提醒大家的是,当类存在有继承关系时,类的初始化顺序也会有所不同,看以下的例子,估计你能猜到为何打印结果是这样啦~
package cn.com.wlf.classdemo.src;
/**
* 父类Base
*/
class Base{
public static final int number_base=88;
static{
System.out.println("Base类被初始化!");
}
public static void sayHello(){
System.out.println("在Base类的sayHello()方法中!");
}
}
/**
* 子类Sub
*/
class Sub extends Base{
public static final int number_sub=66;
static{
System.out.println("Sub类被实例化!");
}
/**
* 子类的静态方法sayHello_Sub
*/
public static void sayHello_Sub(){
System.out.println("这是子类Sub中的方法中打印的.");
}
}
/**
* 测试类
*/
public class Demo2 {
/**
* main 方法,程序唯一入口
* @param args
*/
public static void main(String args[]){
Sub.sayHello_Sub();
//打印结果是:
//Base类被初始化!
//Sub类被实例化!
//这是子类Sub中的方法中打印的.
}
}
第一次发表文章,请大家多多包涵 :wink: