在java中使用循环定义会出现哪些问题?

简介:

在上一篇文章《 inconstant constants ( 变化无常的常量 )   》的基础上,我们再来研究一下在 Java 中使用循环定义会出现哪些问题

老规矩,代码伺候 ^+^

例1:

public class 
ClassX{
   public static final int= 2*
 
ClassY.Y;
}

public class
 
ClassY{
   public static final int=ClassZ.Z+ 1
 
;
}

public class  ClassZ  extends 
ClassX{
   public static final int Z= X + 3
 
;
}

public class  
ClassTest{
   public static void
 
main(String[] args){
      System.out.println(ClassX.X
 + ClassY.Y + 
ClassZ.Z);
   }

代码中的static final变量X,Y,Z,循环定义。

你可以先想一下,这个值会是多少?看看与实际结果是否一致。

输出结果: 

 

现在对ClassTest.java进行一点修改,如下:

例2:

public class
 
ClassTest{
   public staticvoid
 
main(String[] args){
      System.out.println(ClassZ.Z
 + ClassY.Y + 
ClassX.X);
   }
}

两次的输出结果会一样么?
我既然这么问了,你肯定会说不一样,那你知道原因么?你知道这次的输出结果么?
可以先思考一下。
输出结果:
 

 

让我来告诉你这是怎么回事。

我们可以看见,对于三个staitc final 变量 X,Y,Z,他们的初始化表达式是循环定义的。在编译期间不能确定它们的值,所以它们是运行期间常量( runtime constants ),编译器不会进行常量替换。
而且每个表达式的计算将会依赖于类装载的顺序。
例如,为了计算出例1中的 ClassTest 结果,我们可以预见,ClassX 是第一个被装载的类,但是第一个完成初始化的类却是 ClassZ.
让我们一步一步看看都发生了什么。
1.   X = 2 * ClassY.Y;      计算X,需要知道ClassY.Y的值,下一步计算Y值
2.   Y = ClassZ.Z + 1;      计算Y,需要知道ClassZ.Z的值,下一步计算Z值
3.   Z = X + 3;                计算Z,需要知道X的值,而此时X的值还没有被计算出来(又转回来了,居然是个圈,呵呵^+^),所以我们使用X的默认值0。
因此:
      Z = 3;
      Y = 4;
      X = 8;
所以 ClassX.X + ClassY.Y + ClassZ.Z  =  15

例2当中,也是同一道理
不同的是,ClassZ是第一个被装载的类,ClassX是第一个完成初始化的类
1. Z = X + 3; 
2. X = 2 * ClassY.Y; 
3. Y = ClassZ.Z + 1;(此时使用Z的默认值0)
因此:
      Y = 1;
      X = 2;
      Z = 5;
所以 ClassZ.Z + ClassY.Y + ClassX.X = 8

只是简单的改变的输出顺序,结果却截然不同。哪一个才是你想要得结果呢?

我的例子看起来有点挖空心思钻牛角尖,但是在大型项目当中,也许就会出现与例子当中相同的循环定义,如果真的存在的话,那么在纷繁的代码当中想要发现循环定义可不是件容易的事情。
如果独立看每一个定义的话,似乎都可以进行常量替换,看不出任何问题。但是这样的代码在不久的将来就会引发问题,而且不易被我们所察觉。
在你的应用程序当中不经意的代码改变(例如示例代码中我们只是改变了输出顺序,却产生了截然不同的结果),就会导致不同的类装载顺序和计算顺序,或者在并发的线程调度中,可能也会导致致不同的类装载顺序和计算顺序。不幸的是,大多数编译器不认为这种代码是错误,也不会对编程人员给出任何警告。

我只是讲了一下循环定义会引发的问题,我暂时也想不出什么好的解决办法,只能在编程的过程当中尽量注意啦。
本文转自BlogJavaOo缘来是你oO的博客,原文链接:在java中使用循环定义会出现哪些问题?,如需转载请自行联系原博主。

相关文章
|
2月前
|
Java 程序员 API
Java循环操作哪个快?
本文探讨了Java中stream API与传统for循环在性能上的对比,通过多个示例分析了不同场景下两者的优劣。作者指出,尽管stream API使代码更简洁,但不当使用会降低可读性和性能,特别是在处理大数据量时。实验结果显示,在多数情况下,普通for循环的性能优于stream API,尤其是在单次操作耗时较短但需多次执行的场景中。文章建议开发者在设计初期就考虑全局流程,避免重复使用stream流,以提升代码质量和性能。
Java循环操作哪个快?
|
2月前
|
Java 程序员 API
Java循环操作哪个快?
本文探讨了Java中Stream API与传统for循环的性能对比及适用场景。作者通过实际案例分析,指出在某些情况下,过度使用Stream API会导致代码可读性和维护性下降。测试结果显示,在数据量较小的情况下,普通for循环的性能优于Stream API,尤其是在涉及多次类似操作时。因此,建议在开发中根据具体需求选择合适的遍历方式,以提高代码的可读性和性能。
Java循环操作哪个快?
|
2月前
|
Java 编译器
Java重复定义变量详解
这段对话讨论了Java中变量作用域和重复定义的问题。学生提问为何不能重复定义变量导致编译错误,老师通过多个示例解释了编译器如何区分不同作用域内的变量,包括局部变量、成员变量和静态变量,并说明了使用`this`关键字和类名来区分变量的方法。最终,学生理解了编译器在逻辑层面检查变量定义的问题。
Java重复定义变量详解
|
3月前
|
算法 Java 测试技术
🧑‍💻Java零基础:Java 的循环退出语句 break
【10月更文挑战第16天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
78 6
|
2月前
|
Java
在Java中定义一个不做事且没有参数的构造方法的作用
Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
|
4月前
|
Java
java基础(2)循环语句for、while、do...while
本文介绍了Java中的基础循环语句,包括for循环、while循环和do...while循环。文章通过示例代码展示了for循环的基本结构和用法,while循环的先判断后执行逻辑,以及do...while循环的先执行后判断逻辑。这些循环语句在Java编程中非常常用,用于执行重复的任务。
66 4
java基础(2)循环语句for、while、do...while
|
3月前
|
Java 测试技术 数据安全/隐私保护
📖Java零基础-while循环语句的深度解析
【10月更文挑战第6天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
60 1
|
3月前
|
Oracle Java 关系型数据库
重新定义 Java 对象相等性
本文探讨了Java中的对象相等性问题,包括自反性、对称性、传递性和一致性等原则,并通过LaptopCharger类的例子展示了引用相等与内容相等的区别。文章还介绍了如何通过重写`equals`方法和使用`Comparator`接口来实现更复杂的相等度量,以满足特定的业务需求。
36 3
|
3月前
|
传感器 Java 测试技术
📖Java零基础-do-while循环语句的深入剖析
【10月更文挑战第5天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
49 1
|
3月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
27 1