2

查看此代码。

// Print object and recurse if iterable
private static void deep_print(Object o) {
  System.out.println(o.getClass().toString() + ", " + o.toString());

  boolean iter = false;
  Iterable<?> i1 = null;
  Object[] i2 = null;

  if (o instanceof Iterable<?>) {
    iter = true;
    i1 = (Iterable<?>) o;
  } else if (o instanceof Object[]) {
    iter = true;
    i2 = (Object[]) o;
  }

  if (iter) {
    for (Object o_ : i2 == null ? i1 : i2) deep_print(o_); // ERROR: Can only iterate over an array or an instance of java.lang.Iterable
  }

我知道如何解决它。我只是想知道为什么会这样。编译器不应该简单地检查所有可能的输出吗?

4

5 回答 5

8

表达式的静态结果类型是 Object的(i2 == null) ? i1 : i2共同祖先。语句要求表达式的静态类型为数组类型或数组类型。事实并非如此,因此您会收到编译错误。i1i2forIterable

现在,如果您问为什么编译器不推断出(i2 == null) ? i1 : i2始终是数组或 Iterable:

  1. Java 语言规范 (JLS) 不允许这样做。
  2. 如果 JLS 确实允许它(但不要求它),那么不同的编译器的行为会有所不同,这取决于它们在定理证明方面的表现。坏的。
  3. 如果 JLS 需要它,那么编译器必须包含一个复杂的定理证明器`1 (BAD SLOW),并且您会遇到解决停机问题2 (BAD BAD BAD) 的“小麻烦”。
  4. 实际上,编译器需要知道表达式具有两种类型中的哪一种,因为它需要在每种情况下生成不同的代码。

假设,如果 Java 类型系统稍有不同,则可以更好地处理这种特殊情况。具体来说,如果 Java 支持代数数据类型,则可以声明o为“对象数组或可迭代”......并且for循环将是可类型检查的。


1 - 假设o已初始化为o = (x * x < 0) ? new Object() : new Object[0]. 确定这将始终导致一个Object[]实例需要一个小证明,涉及一个(实)数的平方不是负数的事实。这是一个简单的例子,可以构建任意复杂的例子,需要任意困难的证明。

2 - 停机问题在数学上被证明是一个不可计算的函数。换句话说,存在数学上不可能证明它们是否终止的函数。

于 2009-08-18T03:47:23.857 回答
2

为了说明 Stephen C 的答案,请考虑以下代码:

void test() {
      Iterable<Integer> i1 = new ArrayList<Integer>();
      Object[] i2 = { 1, 2, 3 };      
      method1(false ? i1 : i2);
      method1(true ? i1 : i2);  
}

void method1(Object o) {
    System.out.println("method1(Object) called");
}

void method1(Object[] o) {
    System.out.println("method1(Object[]) called");
}

void method1(Iterable<?> o) {
    System.out.println("method1(Iterable<?>) called");
}

这是 test() 的输出:

method1(Object) called
method1(Object) called

由于方法重载是在编译时完成的,因此您可以看到三元运算符表达式的静态类型是 Object,因为操作数的类型不同。因此,当您这样做时:

for (Object o_ : i2 == null ? i1 : i2)

您实际上是在要求编译器在对象上生成一个 foreach 循环,这是非法的。

于 2009-08-18T04:14:26.207 回答
1

在这种情况下,条件表达式具有两种类型中最小上界的类型,即Object,并且 foreach 循环对类型不起作用Object

于 2009-08-18T03:45:18.947 回答
0

你真的应该添加两个重载的 deepPrint(Object[]) 和 deepPrint(Iterator i) 方法,并在你的 deepPrint(Object object) 中进行调度。是的,因为 for/each 循环的工作原理,您需要复制和粘贴相同的代码并进行细微更改。

试图将所有这些都放在一个大方法中是一种气味。

于 2009-08-18T03:50:11.823 回答
0

您可以通过将 Object[] 包装在 Arrays.asList(Object[]) 中来避免代码重复,然后在所有情况下都有一个 Iterable。是的,它比处理数组稍慢,但它具有干燥的优势,恕我直言,它应该始终是第一个近似值。

所以你最终会得到这样的东西:

Iterable<?> it = null;
if (o instanceof Iterable<?>) {
    it = (Iterable<?>) o;
} else if (o instanceof Object[]) {
    it = Arrays.asList((Object[]) o);
}

if (it != null) {
   for (Object o_ : it) deep_print(o_);
}

为简单起见(请参阅 Stephen C 的回答,了解编译器编写者如何更简单,但它也可以说是一种更简单的语言设计)三元运算符假定该类型是两种返回类型之间的最低公共指示符,而不是容纳一个两种返回类型。考虑方法重载的情况。正确的方法是确定和编译的时间。这意味着编译器必须能够在编译时对声明的返回类型做出决定,并且不能将该决定延迟到运行时。

于 2009-08-18T04:18:00.957 回答