繁体   English   中英

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

[英]Why is this invalid Java? Type of ternary operator output

看看这段代码。

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

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

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

现在,如果你问为什么编译器没有推导出(i2 == null) ? i1 : i2 (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] o = (x * x < 0) ? new Object() : new Object[0] 确定始终会导致Object[]实例需要一个小证据,其中涉及(实数)的平方不是负数的事实。 这是一个简单的例子,可以构造任意复杂的例子,需要任意的难以证明。

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)

你真的要求编译器在Object上生成一个foreach循环,这是非法的。

在这种情况下,条件表达式具有两种类型的最小上限类型,即Object ,而foreach循环不适用于Object类型

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

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

您可以通过在Arrays.asList(Object [])中包装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的答案,它对于编译器编写者来说更简单,但它可以说是一种更简单的语言设计)三元运算符假定这种类型是两种返回类型之间最常见的标记,而不是容纳一种类型。两种返回类型。 考虑方法重载的情况。 确定正确的方法并编译时间。 这意味着编译器必须能够在编译时对声明的返回类型做出决定,并且不能将该决定延迟到运行时。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM