【问题标题】:Method/Constructor Overloading with Super/Sub types超/子类型的方法/构造函数重载
【发布时间】:2010-10-17 02:58:49
【问题描述】:

我有一些关于在某些情况下会调用哪个重载方法的问题。

案例一:

public void someMethod(Object obj){
    System.out.println("Object");
}
public void someMethod(InputStream is){
    System.out.println("InputStream");
}
public void someMethod(FilterInputStream fis){
    System.out.println("FilterInputStream");
}

我知道如果我传递一个String 它将打印“对象”。但是,如果我给它传递一个 InputStream 呢?如果我将诸如 BufferedInputStream 之类的东西传递给它,它会变得更加混乱。这会调用 Object 一、InputStream 一还是 FilterInputStream 一?方法出现的顺序重要吗?

案例 2:

这有点棘手,因为它利用了多接口继承。 BlockingQueue 和 Deque 都不是彼此的子/超类型,但都是 BlockingDeque 的超类型。 Sun 为接口添加了多重继承,因为它们不需要树结构。 BlockingDeque 的声明是
public interface BlockingDeque extends BlockingQueue, Deque {

public void someMethod(BlockingQueue bq){
    System.out.println("BlockingQueue");
}
public void someMethod(Deque bq){
    System.out.println("Deque");
}
public void someCaller(){
     BlockingDeque bd = new LinkedBlockingDeque();
     someMethod(bd);
}

这会调用 someMethod(BlockingQueue) 还是 someMethod(Deque)?

案例 3:

您可以将这两者结合起来:

public void someMethod(Queue q){
    //...
}
public void someMethod(Deque q){
    //...
}
public void someMethod(List p){
    //...
}
public void someCaller(){
    someMethod(new LinkedList());
}

同样的问题:someMethod(Queue)、someMethod(Deque) 还是 someMethod(List)?

案例 4:

通过引入两个参数,您也可以使事情变得非常复杂:

public void someMethod(Collection c1, List c2){
    //...
}
public void someMethod(List c1, Collection c2){
    //...
}
public void someCaller(){
    someMethod(new ArrayList(), new ArrayList());
}

这会调用 someMethod(Collection, List) 还是相反?

案例5:

当它们有不同的返回类型时,情况会变得更糟:

public Class<?> someMethod(BlockingQueue bq){
    return BlockingQueue.class;
}
public String someMethod(Deque bq){
    return "Deque";
}
public void someCaller(){
     BlockingDeque bd = new LinkedBlockingDeque();
     System.out.println(someMethod(bd));
}

这些可能会变得非常糟糕。在这种情况下 someCaller 会打印什么? someMethod(BlockingQueue).toString(),还是 someMethod(Deque)?

【问题讨论】:

    标签: java inheritance overloading


    【解决方案1】:

    一般来说,Java 会调用最窄的非歧义定义,所以对于前几个情况,如果你传递一个窄类型,它将调用最窄的函数,如果你传递一个更宽的类型(比如 InputStream),你会得到更宽的类型函数(对于 InputStream 的情况 1,即方法 2)。 Here's a simple testnote that downcasting will widen the type, and call the wider type's method

    核心问题是Java是否可以解析一个独特的函数来调用。这意味着如果您提供的定义有多个匹配项,您需要匹配已知的最高类型,或者唯一匹配更广泛的类型而不也匹配更高的类型。基本上:如果你匹配多个函数,其中一个需要在层次结构中更高,Java 才能解决差异,否则调用约定肯定是模棱两可的。

    当方法签名不明确时,Java 似乎会引发编译错误。在我看来,案例 4 是典型的最糟糕的例子,所以我写了一个 quick test,实际上确实得到了预期的编译错误,抱怨要调用的函数匹配不明确。

    案例 5 并没有使任何事情变得更好或更糟:Java 不使用返回类型来消除要调用的方法的歧义,因此它对您没有帮助——而且由于定义已经模棱两可,您仍然要最终出现编译错误。

    所以快速总结:

    1. 使用普通 InputStream 调用时由于不明确调用导致编译错误,使用 FilteredInputStream 调用使用 3rd def,使用实现 InputStream 但不是 FilteredInputStream 的东西调用使用 2nd def,其他任何东西,1st def

    2. 第二个定义

    3. 不明确,会导致编译错误

    4. 不明确,会导致编译错误

    5. 不明确,会导致编译错误

    最后,如果您怀疑自己调用了您认为应该调用的定义,您应该考虑更改代码以消除歧义,或努力指定正确的类型参数以调用“正确”函数.当 Java 无法做出明智的决定时(当事情真的模棱两可时),Java 会告诉您,但避免任何这些问题的最佳方法是通过一致且明确的实现。不要做奇怪的事情,比如案例 4,你就不会遇到奇怪的问题。

    【讨论】:

    • 案例 1:如果您执行 InputStream i = new FileInputStream(new File("new.txt"))。调用 someMethod(i) 将调用第二种方法.. 对吗?
    • @Guarav,这就是我所说的,尽管你说得更简洁。
    【解决方案2】:

    在重载函数的情况下,调用的方法将是引用传递的对象时具有最多限制但兼容的参数类型的方法。另外需要注意的是,重载方法的绑定是在编译时决定的,而不是在运行时确定的对象类型。例如

    案例 1:如果在编译时输入是 InputStream 类型,那么将调用第二个方法。 BufferedInputStream 将进入第二种方法。

    案例 2:这在编译时失败,因为 BlockingDeque 引用类型不明确,并且参数可以适合这两种方法中的任何一种,因为它扩展了这两种类型

    案例 3:这里没问题,第三种方法,因为 Linkedlist 与其他两个参数中的任何一个都不兼容

    案例 4:模棱两可,因为有了这些论点,我可以进入这两种方法中的任何一种,并且无法辨别

    案例 5:返回类型在重载方法中不起作用。案例 2 成立。

    【讨论】:

    【解决方案3】:

    这与关于重载参数的问题有点相切,但有一个非常明确的理由说明案例 5 “更糟”。

    案例 5 是您使用称为协变返回类型的语言功能的地方。这最初并不存在于 Java 中,但我相信是在 v1.5 中添加的(部分原因是这个问题)。如果编译器无法确定正确的返回类型是什么,那就是失败了,这就是在这种情况下发生的情况。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-13
      • 1970-01-01
      相关资源
      最近更新 更多