【java解惑】final域变量初始化顺序

如下所示代码:

public class Example049 {

	private final int overtime;
	public static final Example049 INSTANCE = new Example049();//1
	private static final int CURRENT_YEAR = Calendar.getInstance().get(
			Calendar.YEAR);//2

	private Example049() {
		overtime = CURRENT_YEAR - 1970;
	}

	public int getOverTime() {
		return overtime;
	}

	public static void main(String[] args) {
		System.out.println(INSTANCE.getOverTime());
	}
}

结果说明:

输出结果是-1970,如果将1和2调换位置,则输出结果为当前年减去1970的差--45。

代码分析:

该程序所遇到的问题是由类初始化顺序中的循环而引起的。类的初始化是由虚拟机对其 main 方法的调用而触发的。 首先,其静态域被设置为缺省值,其中 INSTANCE 域被设置为 null,CURRENT_YEAR 被设置为 0,overtime被设置为0。接下来,静态域初始器按照其出现的顺序执行初始化。 静态域INSTANCE的值是通过调用构造器而计算出来的。这个构造器会用一个涉及静态域 CURRENT_YEAR 的表达式来初始化 overtime。通常,读取一个静态域是会引起一个类被初始化的事件之一,但是我们已经在初始化类了,递归的初始化尝试会直接被忽略掉。因此,CURRENT_YEAR 的值仍旧是其缺省值 0,这就是为什么结果是-1930了。将2和1调换位置,静态域CURRENT_YEAR会先被初始化,再初始化INSTANCE的时候,CURRENT_YEAR已经有正确的值了,所以输出结果正确。

该程序表明,在 final 类型的静态域被初始化之前,存在着读取它的值的可能,而此时该静态域包含的还只是其所属类型的缺省值。这是与直觉相违背的,因为我们通常会将 final 类型的域看作是常量。final 类型的域只有在其初始化表达式是常量表达式时才是常量。

    由类初始化中的循环所引发的问题是难以诊断的,但是一旦被诊断到,通常是很容易订正的。要想订正一个类初始化循环,需要重新对静态域的初始器进行排序,使得每一个初始器都出现在任何依赖于它的初始器之前。 

    某些通用的设计模式本质上就是初始化循环的, 特别是本题展示的单例模式( Singleton)和服务提供者框架( Service Provider Framework)。类型安全的枚举模式(Typesafe Enum pattern)也会引起类初始化的循环。

    总之,要当心类初始化循环。最简单的循环只涉及到一个单一的类,但是它们也可能涉及多个类。 类初始化循环也并非总是坏事,但是它们可能会导致在静态域被初始化之前就调用构造器。静态域, 甚至是 final 类型的静态域,可能会在它们被初始化之前,被读走其缺省值。




注:本【java解惑】系列均是博主阅读《java解惑》原书后将原书上的讲解和例子部分改编然后写成博文进行发布的。所有例子均亲自测试通过并共享在github上。通过这些例子激励自己惠及他人。同时本系列所有博文会同步发布在博主个人微信公众号搜索“爱题猿”或者“ape_it”方便大家阅读。如果文中有任何侵犯原作者权利的内容请及时告知博主以便及时删除如果读者对文中的内容有异议或者问题欢迎通过博客留言或者微信公众号留言等方式共同探讨。

源代码地址https://github.com/rocwinger/java-disabuse

时间: 2024-10-10 18:46:06

【java解惑】final域变量初始化顺序的相关文章

java 静态变量初始化顺序

public class Elvis { public static final Elvis INSTANCE = new Elvis(); private final int beltSize; private static final int CURRENT_YEAR = Calendar.getInstance().get(Calendar.YEAR); private Elvis() { beltSize = CURRENT_YEAR - 1930; } public int beltS

Java 类的实例变量初始化的过程 静态块、非静态块、构造函数的加载顺序

Java 类的实例变量初始化的过程 静态块.非静态块.构造函数的加载顺序 先看一道Java面试题: 1 public class Baset { 2 private String baseName = "base"; 3 // 构造方法 4 public Baset() { 5 callName(); 6 } 7 // 成员方法 8 public void callName() { 9 // TODO Auto-generated method stub 10 System.out.p

【细说Java】Java变量初始化顺序

Java的变量初始化顺序,对这里一直似懂非懂,面试的时候也经常被问到,但答的一直不好,现在整理记录一下,以后忘记了可以来看看. 程序分为两个部分,第一个部分不考虑继承,第二个部分考虑继承: (1)不考虑继承的情况 代码如下: public class JavaTest { public JavaTest() { System.out.println("执行JavaTest构造方法1"); } public JavaTest(String param) { System.out.prin

调整static变量初始化顺序的一个办法

// wrap the LaunchDir variable in a function to work around static/global initialization order static FString& GetWrappedLaunchDir() { static FString LaunchDir; return LaunchDir; } 在ue4中看到这么一段代码,注释有点意思 不同cpp文件里的全局static变量初始化顺序是不可控的 FString显然会依赖很多内存分配

分析java类的静态成员变量初始化先于非静态成员变量

分析java类的静态成员变量初始化先于非静态成员变量 依上图中当class字节码文件被jvm虚拟机加载到内存中依次经过 连接 验证:对字节码进行验证 准备:给静态变量分配内存并赋予变量类型各自的默认值(注:基本类型为0或false,对象为null,static final修饰的常量直接赋予相应的值) 解析:类中符号引用转换成直接引用 初始化:为类的静态变量/静态语句块初始化相应的值 而类的初始化契机是:类在被第一次主动使用的情况下,主动使用包括以下6中情况: 使用new关键字实例化对象时. 调用

JAVA加载与变量初始化

Java对象初始化 这是一道阿里巴巴的关于Java对象初始化的面试题,堪称经典,代码很简单(编写格式做了些修改),但是需要面试者对Java中对象初始化有一个透彻的认识,那么通过这道面试题,对我有点启发,所以希望在这里分享给大家,希望能给迷惘的初学者一起指引,下面我们直入主题,先看看代码: public class InitializeDemo {   private static int k = 1;   private static InitializeDemo t1 = new Initia

Java静态方法,静态变量,初始化顺序

1. 静态方法: 成员变量分为实例变量和静态变量.其中实例变量属于某一个具体的实例,必须在类实例化后才真正存在,不同的对象拥有不同的实例变量.而静态变量被该类所有的对象公有(相当于全局变量),不需要实例化就已经存在. 方法也可分为实例方法和静态方法.其中,实例方法必须在类实例化之后通过对象来调用,而静态方法可以在类实例化之前就使用.与成员变量不同的是:无论哪种方法,在内存中只有一份——无论该类有多少个实例,都共用同一个方法. 实例方法的调用: ClassA a = new ClassA();  

Java构造方法、成员变量初始化以及静态成员变量初始化三者的先后顺序是什么样的?

[Java笔试真题]:构造方法.成员变量初始化以及静态成员变量初始化三者的先后顺序是什么样的? [解答]:当类第一次被加载的时候,静态变量会首先初始化,接着编译器会把实例变量初始化为默认值,然后执行构造方法. Java程序的初始化一般遵循以下三个原则(以下三原则优先级依次递减): ① 静态对象(变量)优先于非静态对象(变量)初始化,其中,静态对象(变量)只初始化一次,而非静态对象(变量)可能会初始化多次: ② 父类优先于子类进行初始化: ③ 按照成员变量定义顺序进行初始化,即使变量定义散布于方法

Java入门记(三):初始化顺序

初始化顺序的规则 1.在一个类的对象实例化时,成员变量首先初始化,然后才调用构造器,无论书写顺序.如果调用构造器前,没有显式初始化,那么会赋默认值. 这样做法的原因可以理解为:构造器执行时可能会用到一些成员变量的初值. 2.static变量早于所有其他的类成员变量初始化,同样无论书写顺序.但是static变量仅在所在类第一次被使用时初始化一次. 3.基类构造器总是在导出类的构造过程中被调用,而且按照继承层级逐渐向上链接(调用顺序则是从基类开始向下).可以理解为,这么做的逻辑关系是在一个类构建时可