Java中"new String[-1]"编译通过,为什么?

26

在Java中调试时,我使用了一个负数长度来初始化一个新的字符串数组。即:

String[] arr = new String[-1];

令我惊讶的是,编译器没有对此发出任何抱怨。 谷歌搜索也没有找到相关答案。有人可以为这个问题提供一些线索吗?

非常感谢!


1
编译器不会检查数组的大小,但如果大小为负数,则会抛出java.lang.NegativeArraySizeException异常。 - isvforall
3
String[] arr = new String[someNumberThatMightOrMightNotBeNegative]; - 挑战你在编译时检查它!请注意“that”的含义。 - Izkata
@Izkata - 我使用Eclipse进行了检查,但它仍然非常从容不迫(即没有警告或任何异常)。 - Tom Teman
@TomTeman 你误解了。编译器必须实际运行代码才能确定数字是否为负数,即使如此,它也必须尝试所有可能的代码路径,包括所有可能的输入,才能明确确定该数字是否为负数。这是不可能的。因此,语言设计者决定在运行时进行检查,因为这将捕获您在问题中提出的版本和我上面评论中的版本。 - Izkata
@Izkata 是的,我从 Stephen C 的回答中理解了这一点(以及运行时和编译时的区别)。但由于我们正在处理一个原始常量,所以编译器没有标记它仍然很奇怪。哦,好吧,代码设计者的工作方式很神秘。 - Tom Teman
显示剩余3条评论
4个回答

25

原因是JLS允许这样做,如果编译器将其标记为编译错误,则会拒绝有效的Java代码。

这在JLS 15.10.1中有明确说明。以下是相关摘录:

"…… 如果任何DimExpr表达式的值小于零,则抛出NegativeArraySizeException异常。"

如果Java编译器将此代码标记为错误,则该指定行为无法在该特定代码中发生。

此外,我找不到任何授权编译器拒绝像-1这样的编译时常量表达式的“显而易见的错误”情况的文本。 (而且谁能说这真的是一个错误?)


下一个问题当然是“为什么JLS允许这样做?”

您需要问Java设计者。但是我可以想到一些(大多数)合理的原因:

  • 最初被忽视,并且没有强烈的理由修复它。 (注意,修复它会破坏源代码兼容性。)

  • 它被认为是太不寻常/边缘的情况,不值得处理。

  • 这可能会给编写源代码生成器的人带来问题。 (想象一下,必须编写代码来评估编译时常量表达式,以便您不会生成非可编译代码。使用当前的JLS规范,您可以简单地生成带有“错误”大小的代码,并在执行该代码时处理异常(或不处理)。)

  • 也许有人计划在Java中添加"unarrays" :-)


其他答案建议编译器可以或应该“标记”这种情况。如果“标记”指输出警告信息,则 JLS 明确允许这样做。然而,编译器是否应该这样做还有争议。一方面,如果上述代码是错误编写的,则标记出这个错误很有用。另一方面,如果它不是一个错误(或者这个“错误”不相关),那么警告就是噪音,甚至更糟。无论哪种情况,这都需要与各自编译器的维护者进行讨论。


@TonyEnnis:它允许这样做,因为它在这方面没有任何明确的规定;请参见http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.10。 - Oliver Charlesworth
1
并不是因为DimExpr不允许负的编译时常量,所以指定的行为不会发生。 - Marko Topolnik
1
@MarkoTopolnik - 我已经注意到了您的评论,但我不同意。 - Stephen C
1
那么,你是否声称现在手头上发生“指定行为”的唯一方式是执行new String [<negative_compile_time_constant>]?因为像 int i = -1; new String [i] 这样的代码已经超出了编译时常量的范围。 - Marko Topolnik
1
不,我不这么认为。我的观点是,如果new String[-1]被标记为编译错误,那么new String[-1]就不可能抛出异常。JLS规定它应该抛出异常...没有任何关于编译时常量表达式的限定。这是我对JLS的理解。无论如何,这是一个愚蠢的争论,因为事实上这不是一个编译错误,而且JLS中也没有说它应该是一个编译错误。 - Stephen C
显示剩余5条评论

2
我认为没有理由不在编译时(至少作为警告)标记此问题,因为此执行条件会无条件地抛出NegativeArraySizeException

我已经使用我的编译器做了一些快速实验,发现它对这种情况非常放松。它没有对定值表达式中的整数除零、定值索引处超出数组边界等问题发出警告。

由此我得出结论:在这里的一般模式是信任程序员。


另外,请检查Pau的第一个代码块,它很有道理,为什么不在编译时或运行时对数组大小施加限制(这里我指传递-1值,它将引发异常)。 - Nandkumar Tekale

1

编译器只负责检查语言的语法,而不是您代码的语义含义。 因此,如果您的代码中没有语法错误,编译器不会报错,这是合理的。

在Java中,数组是在运行时分配的,这是完全可以的。如果它在编译时分配,则编译器如何检查以下代码?

// runtime pass the length, with any value
void t(int length) {
   String[] stirngs = new String[length];
}

当将负值作为长度传递给构造数组时,运行时异常将被抛出。

public class Main {

    public static void main(String[] args) {
        String[] v = new String[-1];
    }
}

有错误:

Exception in thread "main" java.lang.NegativeArraySizeException
    at Main.main(Main.java:5)

2
那么死代码检测呢?那是语法还是语义?死代码是编译器错误。Java 中还有许多类似的功能,例如检查每个代码路径是否导致返回语句。 - Marko Topolnik
没有返回值的方法肯定会被编译器检测到,但是像 if(true) { System.out.println("true"); } else { System.out.println("dead code"); } 这样的语义死代码却不一定会被检测到。只有集成开发环境才会标记为警告。 - Pau Kiat Wee
但是一个方法中有两个返回值,第三个却缺失了——这也是语义上的死代码。否则它本应该被 CFG(控制流图)消除,而不是通过复杂的死代码检测算法来消除。静态分析不能用于语法检查。类型检查、方法调用的有效性检查等也不能用作语法检查。Java 编译器会进行大量的语义检查,符合 JLS(Java 语言规范)。 - Marko Topolnik
2
@PauKiatWee:这是错误的,“编译器只负责检查语言语法,而不是您代码的语义含义。”在此处检查编译器阶段http://www.mec.ac.in/resources/notes/notes/compiler/Module1/intro.html。 - Nandkumar Tekale

0

Java编译器以一个整数作为数组的长度,它可以是变量或者编译时常量。当数组被创建时,它的长度就确定了并且在创建之后不再改变。

编译器应该标记负的编译时常量作为数组的长度,但实际上并没有这么做。如果长度是负数,那么你将在运行时得到一个NegativeArraySizeException异常。


“编译器应该将负的编译时常量标记为数组的长度”这种说法是不准确的,根据@Stephen C的回答,这样做会违反JLS。 - Redwood
@LawrenceJohnston:我的意思是“编译器不会将负的编译时常量标记为数组的长度。它应该标记它,因为对于编译器来说实现这个检查很容易。” - Don Li

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接