为什么这个Java无效?三元运算符输出的类型

问题描述 投票:0回答:5

查看此代码。

// 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
  }

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

java iterator conditional-operator
5个回答
8
投票

表达式

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

现在,如果您问为什么编译器不推断出

(i2 == null) ? i1 : i2
将始终是数组或 Iterable:

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

假设,如果 Java 类型系统有点不同,这种特殊情况可以得到更好的处理。 具体来说,如果 Java 支持 代数数据类型 ,那么可以将

o
声明为“对象数组或可迭代对象”...并且
for
循环将是可类型检查的。


1 - 假设

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

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


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 循环,这是非法的。


1
投票

在这种情况下,条件表达式具有两种类型中最小上限的类型,即

Object
,foreach循环对类型
Object

不起作用

0
投票

您确实应该添加两个重载的 deepPrint( Object[] ) 和 deepPrint(Iterator i) 方法,并在 deepPrint(Object object) 中进行调度。是的,由于 for/each 循环工作方式的本质,您将需要复制并粘贴相同的代码并进行细微的更改。

尝试将所有这些放入一个大方法中是一种味道。


0
投票

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

所以你最终会得到这样的结果:

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 的回答,了解它对于编译器编写者来说如何更简单,但它也可以说是一种更简单的语言设计)三元运算符假定类型是两个返回类型之间的最低共同点,而不是容纳两种返回类型之一。考虑方法重载的情况。正确的方法是由编译时间决定的。这意味着编译器必须能够在编译时就声明的返回类型做出决定,并且不能将该决定延迟到运行时。

© www.soinside.com 2019 - 2024. All rights reserved.