通过推断类型自动向下转换


11

在Java中,必须显式转换以向下转换变量

public class Fruit{}  // parent class
public class Apple extends Fruit{}  // child class

public static void main(String args[]) {
    // An implicit upcast
    Fruit parent = new Apple();
    // An explicit downcast to Apple
    Apple child = (Apple)parent;
}

除了Java不执行任何类型推断的事实外,是否还有其他原因可以满足此要求?

使用新语言实现自动向下转换是否存在任何“陷阱”?

例如:

Apple child = parent; // no cast required 

您是在说,如果编译器可以推断出向下转换的对象始终是正确的类型,那么您应该不能明确表示向下转换?但是然后取决于编译器版本以及它可以对某些程序执行多少类型推断可能是无效的...类型推断器中的错误可能会阻止有效程序的编写等...引入一些特殊的方法是没有意义的这种情况取决于很多因素,如果用户每次发布时都无缘无故地继续添加或删除演员表,这对用户来说是不直观的。
巴库里

Answers:


24

上演总是成功的。

当对象运行时类型不是强制转换中使用的类型的子类型时,向下转换可能会导致运行时错误。

由于第二种操作很危险,因此大多数类型化的编程语言都要求程序员明确要求它。本质上,程序员告诉编译器“相信我,我知道更好-这在运行时就可以了”。

当涉及类型系统时,向上转换将证明的负担加到了编译器上(必须对其进行静态检查),向下转换将证明的负担加到了程序员上(必须认真考虑)。

有人可能会争辩说,一种经过适当设计的编程语言将完全禁止垂头丧气,或者提供安全的强制类型转换,例如返回可选类型Option<T>。但是,许多广泛使用的语言都选择了更简单,更实用的方法,T否则就简单地返回并引发错误。

在您的特定示例中,可以将编译器设计为通过简单的静态分析来推断出parent实际上是一个Apple结果,并允许隐式强制转换。但是,总的来说,这个问题是无法确定的,因此我们不能指望编译器会表现出太多的魔力。


1
仅供参考,Rust向下转换为可选语言的示例,但这是因为它没有真正的继承,只有“任何类型”
Kroltan '16

3

通常,当编译器对某些事物的静态了解不如您所知道的(或至少是希望的)特定知识时,便会执行向下转换。

在类似您的示例的情况下,该对象被创建为an Apple,然后通过将引用存储在type变量中而丢弃了该知识Fruit。然后,您想再次使用相同的引用Apple

因为信息只是“本地”丢弃的,所以可以肯定的parentApple,即使其声明的类型为,编译器也可以保留真正的知识Fruit

但是通常没有人这样做。如果要创建一个Apple并将其用作Apple,请将其存储在Apple变量中,而不是一个变量中Fruit

当您拥有Fruit并想要将其用作时Apple,通常意味着您已Fruit通过某种方式获得了通行证,该方式通常可以返回任何一种Fruit,但是在这种情况下,您知道它是一个Apple。几乎总是您不仅在构造它,而且还通过其他一些代码来传递它。

一个明显的例子是,如果我有一个parseFruit函数可以将“ apple”,“ orange”,“ lemon”等字符串转换为适当的子类;通常,我们(和编译器)所能知道的就是该函数返回某种Fruit,但是如果我调用了,parseFruit("apple")我就知道将要调用an Apple并且可能要使用Apple方法,因此我可以向下转换。

再一次,一个足够聪明的编译器可以通过内联for的源代码来弄清楚这一点parseFruit,因为我使用常量来调用它(除非它在另一个模块中,并且我们有单独的编译,例如Java)。但是,您应该能够轻松地看到涉及动态信息的更复杂的示例如何变得难以(甚至甚至不可能!)让编译器进行验证。

在现实的代码中,通常会出现向下转换的情况,编译器无法使用通用方法来验证向下转换的安全性,并且在简单的情况下(例如,紧随上移之后,我们试图通过下移来获取相同类型的信息)就不会发生。


3

您要在哪里划清界限。您可以设计一种语言来检测隐式向下转换的有效性:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    // An implicit downcast to Apple 
    Apple child = parent; 
}

现在,让我们提取一个方法:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    eat(parent);
}
public static void eat(Fruit parent) { 
    // An implicit downcast to Apple 
    Apple child = parent; 
}

我们还是很好。静态分析要困难得多,但仍然可行。

但是问题突然出现,有人补充道:

public static void causeTrouble() { 
    // An implicit upcast 
    Fruit trouble = new Orange();
    eat(trouble);
}

现在,您要在哪里引发错误?这造成了一个难题,可以说问题出在Apple child = parent;,但是可以用“但是以前有效”来反驳。另一方面,添加 eat(trouble);导致了问题”,但是多态性的全部目的就是要允许这种情况。

在这种情况下,您可以做一些程序员的工作,但不能一路走好。在放弃之前,您做得越深入,就越难解释出了什么问题。因此,根据尽早报告错误的原则,最好尽快停止。

顺便说一句,在Java中,您描述的下调实际上不是下调。这是一般的演员表,也可以将苹果变成橙子。因此,从技术上来讲,@ chi的想法已经在这里,Java中没有垂头丧气,只有“每个播客”。设计一个专门的向下运算符是很有意义的,当在其参数类型的下游找不到结果类型时,它将抛出编译错误。最好使“每个广播”的使用更加困难,以阻止程序员在没有充分理由的情况下使用它。XXXXX_cast<type>(argument)想到了C ++的语法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.