21

与此问题相同,但适用于 java

更新 根据一些人的评论和回应,很明显 Java 几乎没有未定义的行为。

所以我也想问一下什么行为不明显。请在回答时区分两者:)

4

10 回答 10

15

与线程有关的任何事情... :)

还:

  • 覆盖方法并期望它们在版本之间以相同的方式使用
  • 关于底层平台的假设(例如文件分隔符)
  • 垃圾收集/终结的详细信息
  • 关于类初始化的一些细节
  • Integer.valueOf(等)是否返回相同的对象
  • 性能、延迟和内存使用
于 2008-12-17T22:54:32.080 回答
12

Java 中几乎没有未定义的行为,与 C/C++ 相比,它是一个定义更明确的平台。这样做的原因是 C/C++ 编译器旨在为非常不同的平台生成代码,因此被授予相当广泛的自由度,以防止过于严格的要求会迫使编译器为给定平台生成次优代码。

Java 通过以非常精确的方式定义几乎所有行为并只允许很小的自由度,从而牺牲了其中的一部分。这当然使平台更容易处理。

发生未定义行为的主要领域是多个线程的确切时间和调度(正如 Tom Hawtin 已经提到的)。

有几个地方的行为不明显,'虽然,所以它可能看起来未定义,但不是(Oscar Reyes 给出的字符串比较示例就是一个很好的例子)。

还有一些行为被定义为未定义的地方(例如,HashMap 中元素的顺序被定义为依赖于实现并且不需要是常量)。

于 2008-12-17T23:03:13.700 回答
8

我认为Java(TM) Puzzlers: Traps, Pitfalls, and Corner Cases这本书非常有用,它解释了 Java 的许多隐藏点和未定义的行为。

于 2008-12-18T14:53:54.140 回答
4

序列化。它本身并不是未定义的(有一个确定性算法)。但是,对于不经意的观察者来说,什么会或不会导致 serialVersionUID 的变化根本不明显,从而挫败了您使用 RMI、JMS 和各种其他首字母缩略词的所有尝试。

因此,当您知道需要序列化对象时,考虑您的选择通常是一个好主意。我特别喜欢“始终将其作为一个字段包含”技术:

private static final long serialVersionUID = 1L;

仅当您(开发人员)知道您的代码发生了破坏兼容性的更改时,才更改该字段的值。不要让 JDK 为您做出决定......

于 2008-12-17T23:25:29.617 回答
3

我不完全确定您所说的“未定义行为”是什么意思,但正如其他人所指出的那样,核心语言在平台、语言版本和 JVM 之间是非常可预测的。

然而,对于图形(Swing、AWT)来说,情况并非如此,它们往往是不可预测的,并且不一定可以在不同平台上重现。我从事基于图形密集型 Java 的可视化应用程序,我花了很多时间“一次编写,到处调试”。

此外,Object.clone() 存在一些重大问题,在大多数情况下不鼓励使用它。请参阅 Joshua Bloch 的“Effective Java”中的第 11 项以获得完整答案。

于 2008-12-17T23:19:42.200 回答
1

定义明确但不明显:

平等的对象测试:

== 用于测试引用(这两个对象引用是否指向同一个对象)

而equals用于测试对象是否相等。

所以例如

new String("test") == new String("test")  

是假的,而

new String("test").equals( new String("test") )

是真的

字符串对象被实习,因此以下返回 true:

String a = "test";
String b = "test";

a == b  // returns true 

但是如果字符串是在其他地方创建的(例如来自数据库)

String a = "test";
String b = getFromDataBase(); // internally the remote returns "test"

a == b  // returns false.

验证是假的。

我已经在带有脚本的 jsp 中看到了这种情况,而新程序员不明白为什么要验证

 <%if( param == "continue" ) { %>

永远不会发生

于 2008-12-17T22:56:50.530 回答
-1

我记得的一件事是关于 jvm 与 jni 的兼容性。我们有一个在 jdk1.4 上开发的应用程序,当将它安装在带有 ibm jvm 的机器上时(我相信是 jikes),jni 调用就吐了!不过那是在 2006 年。我认为这与作为语言的 java 关系不大,而与作为平台的 java 关系更大。

于 2008-12-17T22:57:33.573 回答
-1

添加动态与静态绑定以及如何将其应用于重载和覆盖的方法并不明显

于 2008-12-18T14:29:51.083 回答
-1

不是未定义但意外的行为是双精度数在转换为整数时如何舍入。0.6d 总是向下舍入为 0;实际上 0.9d 也向下舍入为 0。但 0.99999999999999995 及更大的值将向上舍入为 1。

将 Math.random() 调用的结果转换为提防时,这只是一个有趣的行为。

于 2008-12-18T15:10:37.820 回答
-1

我知道有两种未定义的行为:

a) 重载带有参数的方法,该参数是重载方法中相同参数的子类。例如:

void doSomething(Object obj);
void doSomething(String str);

无法知道在 doSomething("Hello world!") 中将调用哪个方法,因为两个签名都是有效的。此外,此行为可以从 VM 更改为 VM,甚至从执行更改为执行。

b) 在构造函数中调用同一类的非最终方法。如果在子类中重写此方法,则会发生未定义的行为。请注意,构造是从超类到子类的。如果子类方法使用一些本地子类属性,情况会变得特别糟糕。在 Oracle VM 的情况下,将构造本地属性,然后超类构造函数将完成其执行,当到达子类的构造函数时,将再次构造属性,覆盖先前定义的值。

于 2012-12-27T17:31:56.227 回答