`
java菜菜鸟
  • 浏览: 11085 次
  • 性别: Icon_minigender_1
  • 来自: 广州
文章分类
社区版块
存档分类
最新评论

关于类的初始化时机的小结

阅读更多
看了下 臧圩人 的 JAVA面试题解惑系列(一)——类的初始化顺序,链接在这里:http://zangweiren.iteye.com/blog/208122
感觉总结得不错.我也补充几点.

就是关于类的初始化顺序的问题,其实当java程序需要使用某个类时,java虚拟机会确保这个类已经被加载,连接,和初始化,其中连接过程又会执行验证,准备和解析这三个子步骤.这里不进行详细的讲解.有关资料可以去google下.
   那么当类或者接口被加载和连接时,JVM严格定义了初始化的时机,所有的JVM实现必须在每个类或接口被java程序"首次调用"时才初始化它们.很正常,要是你的应用有N多个类,有些类可能一时用不到而JVM统统帮你加载的话,你的机子能受得了吗?
   因此,java程序对类的使用方式可分为有两种:主动使用和被动使用那么参照前面的说法,JVM只有在程序需要主动使用一个类或接口时才会初始化它, 那什么时侯是程序对类的主动使用呢?有以下六种情况:
  • 使用new创建类的实例时,或者通过反射,克隆,和反序列化等方法进行创建类的实例时.
  • 调用类的静态方法时.
  • 访问某个类/接口的静态变量[其中这里的静态变量的定义又有区别,将会在以下进行讲解],或对该静态变量进行赋值操作
  • 初始化一个类的子类
  • JVM启动时被标注为启动类的类.


较为特殊的是访问某个类/接口的静态变量这个操作它分为两种情况:
  • 对于final类型的静态变量,如果在编译期间就能够计算出变量的值,那么访问该静态变量时,将被看作是对类的被动使用,而不会导致类的初始化.
  • 而不能在编译期间确定值的静态变量,访问该静态变量时,将被看作是对类的主动使用,会导致类的初始化.


示例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中的方法中打印的.
	}
}




第一次发表文章,请大家多多包涵
3
0
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    c++ 类和对象 讲义

    本资源摘要信息主要讲解了C++语言中的类和对象相关知识点,涵盖了构造函数、析构函数、对象的初始化、参数传递、默认参数、构造函数的重载等内容。 一、类和对象的概念 在C++语言中,类是对对象的抽象描述,而对象...

    疯狂JAVA讲义

    5.3.2 成员变量的初始化和内存中的运行机制 128 5.3.3 局部变量的初始化和内存中的运行机制 130 5.3.4 变量的使用规则 130 5.4 隐藏和封装 132 5.4.1 理解封装 132 5.4.2 使用访问控制符 132 5.4.3 package和...

    突破程序员基本功的16课.part2

    2.1.3 类变量的初始化时机 2.2 父类构造器 2.2.1 隐式调用和显式调用 2.2.2 访问子类对象的实例变量 2.2.3 调用被子类重写的方法 2.3 父子实例的内存控制 2.3.1 继承成员变量和继承方法的区别 2.3.2 内存...

    java新手学习指导意见(很实用)

    #### 小结 本篇文档提供了Java初学者在学习过程中所需掌握的一些核心概念,包括`static`关键字的使用及其重要性,以及如何在方法中正确地传递参数。通过理解这些基础知识,初学者可以更好地构建自己的代码逻辑,并...

    Linux2.6内核标准教程(共计8-- 第1个)

    6.4.4 时钟中断处理小结 272 6.5 内核定时器工作原理 273 6.5.1 初始化内核定时器节点 273 6.5.2 激活内核定时器节点 273 6.5.3 内核定时器的处理过程 277 6.6 微秒级延迟 280 6.6.1 微妙级延迟的访问...

    Linux2.6内核标准教程(共计8--第6个)

    6.4.4 时钟中断处理小结 272 6.5 内核定时器工作原理 273 6.5.1 初始化内核定时器节点 273 6.5.2 激活内核定时器节点 273 6.5.3 内核定时器的处理过程 277 6.6 微秒级延迟 280 6.6.1 微妙级延迟的访问...

    Linux2.6内核标准教程(共计8--第3个)

    6.4.4 时钟中断处理小结 272 6.5 内核定时器工作原理 273 6.5.1 初始化内核定时器节点 273 6.5.2 激活内核定时器节点 273 6.5.3 内核定时器的处理过程 277 6.6 微秒级延迟 280 6.6.1 微妙级延迟的访问...

    Linux2.6内核标准教程(共计8--第4个)

    6.4.4 时钟中断处理小结 272 6.5 内核定时器工作原理 273 6.5.1 初始化内核定时器节点 273 6.5.2 激活内核定时器节点 273 6.5.3 内核定时器的处理过程 277 6.6 微秒级延迟 280 6.6.1 微妙级延迟的访问...

    Linux2.6内核标准教程(共计8--第2个)

    6.4.4 时钟中断处理小结 272 6.5 内核定时器工作原理 273 6.5.1 初始化内核定时器节点 273 6.5.2 激活内核定时器节点 273 6.5.3 内核定时器的处理过程 277 6.6 微秒级延迟 280 6.6.1 微妙级延迟的访问...

    Linux2.6内核标准教程(共计8--第7个)

    6.4.4 时钟中断处理小结 272 6.5 内核定时器工作原理 273 6.5.1 初始化内核定时器节点 273 6.5.2 激活内核定时器节点 273 6.5.3 内核定时器的处理过程 277 6.6 微秒级延迟 280 6.6.1 微妙级延迟的访问...

    Linux2.6内核标准教程(共计8--第5个)

    6.4.4 时钟中断处理小结 272 6.5 内核定时器工作原理 273 6.5.1 初始化内核定时器节点 273 6.5.2 激活内核定时器节点 273 6.5.3 内核定时器的处理过程 277 6.6 微秒级延迟 280 6.6.1 微妙级延迟的访问...

    Linux2.6内核标准教程(共计8--第8个)

    6.4.4 时钟中断处理小结 272 6.5 内核定时器工作原理 273 6.5.1 初始化内核定时器节点 273 6.5.2 激活内核定时器节点 273 6.5.3 内核定时器的处理过程 277 6.6 微秒级延迟 280 6.6.1 微妙级延迟的访问...

    Java虚拟机

    7.3.5 初始化 7.4 类加载器 7.4.1 类与类加载器 7.4.2 双亲委派模型 7.4.3 破坏双亲委派模型 7.5 本章小结 第8章 虚拟机字节码执行引擎 8.1 概述 8.2 运行时栈帧结构 8.2.1 局部变量表 8.2.2 操作数栈 ...

    单例模式讲解

    ### 单例模式详解 ...#### 六、小结 通过以上介绍可以看出,单例模式的实现方式多种多样,不同的实现方式适用于不同的应用场景。选择合适的单例模式能够有效地优化程序性能,提高代码的可读性和可维护性。

    C+进阶与提高

    - 定义、初始化、类型大小、常用运算、成员函数和数组:全面介绍C++中的string类的特性和使用方法。 ##### 2.12 C++之父的建议 - 给C程序员的建议:提供C++之父对C程序员在学习C++时的一些建议。 ##### 2.13 练习 ...

    《阿里巴巴 Java开发手册》读后感小结

    6. 构造方法与业务逻辑:构造函数不应包含业务逻辑,可将初始化逻辑放在init方法中。set/get方法同样不应包含业务逻辑,以保持代码清晰。 7. 避免修改集合:如使用Arrays.asList()转换数组为List,不应调用其修改...

Global site tag (gtag.js) - Google Analytics