使用超类/子类进行方法/构造函数重载

3

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

情况1:

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,它将打印“Object”。但是,如果我传入一个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)?
3个回答

6
一般来说,Java会调用最窄的非模糊定义,因此在前几种情况下,如果您传递一个窄类型,它将调用最窄的函数,如果您传递一个更宽的类型(比如InputStream),则会得到较宽类型的函数(在第1种情况下,对于InputStream而言,这是方法2)。这里有一个简单的测试,并且请注意,向下转型会扩大类型并调用更宽类型的方法
核心问题是Java是否能够解析唯一的调用函数。这意味着,如果您提供了多个匹配的定义,您需要匹配最高已知类型,或者唯一匹配一个更广泛的类型而不同时匹配更高的类型。基本上:如果您匹配多个函数,则其中一个函数需要在层次结构中更高,以便Java解决差异,否则调用约定明显是模糊的。

当方法签名模糊不清时,Java似乎会抛出编译错误。在我看来,情况4是这种情况中最糟糕的例子,因此我写了一个快速测试,实际上得到了预期的编译错误,抱怨函数调用的匹配模糊。

情况5并没有使任何事情变得更好或更糟:Java不使用返回类型来消除要调用哪个方法的歧义,因此它对您没有帮助--而且由于定义已经模糊不清,您仍然会遇到编译错误。

所以这是一个简单的摘要:

  1. 当使用普通InputStream调用时,由于调用FilteredInputStream使用第三个定义,调用某些实现InputStream但不是FilteredInputStream的东西使用第二个定义,其他任何东西使用第一个定义而导致的模糊调用而导致编译错误

  2. 第二个定义

  3. 模糊不清,将导致编译错误

  4. 模糊不清,将导致编译错误

  5. 模糊不清,将导致编译错误

最后,如果你对自己调用的定义有疑虑,你应该考虑修改代码以消除歧义或指定正确的类型参数来调用“正确”的函数。Java会在无法做出明智决策时(当事情真正模糊不清时)告诉你,但避免任何这些问题的最好方法是通过一致和明确的实现。不要做奇怪的事情,比如第四种情况,这样就不会遇到奇怪的问题。

情况1:如果您执行InputStream i = new FileInputStream(new File("new.txt"))。调用someMethod(i)将会调用第二个方法.. 对吗? - Gaurav Saxena
@Guarav,这就是我说的,尽管你说得更简洁。 - Mark Elliot

2
在函数重载的情况下,被调用的方法将是与传递对象相对应的最受限但兼容的参数类型。还需要注意的是,重载方法的绑定是在编译时决定的,而不是由运行时确定的对象类型决定的。例如:
案例1:如果输入在编译时为InputStream类型,则将调用第二个方法。BufferedInputStream将进入第二个方法。
案例2:这在编译时失败,因为BlockingDeque类型的引用是模糊的,参数可以适合这两种方法中的任何一种,因为它扩展了这两种类型。
案例3:这里没有问题,第三个方法,因为Linkedlist与其他两个参数都不兼容。
案例4:不明确,因为使用这些参数我可以进入那两个方法中的任何一个,没有办法区分。
案例5:返回类型在重载方法中没有作用。案例2成立。

请查看LinkedList的Javadoc文档:http://download-llnw.oracle.com/javase/6/docs/api/java/util/LinkedList.html - Leo Izen
方法重载的绑定是在编译时决定的,而不是由运行时确定的对象类型决定的。非常感谢,+1。 - Pedro A

0

这与关于重载参数的问题有些牵强,但是有一个相当明确的原因解释为什么情况5更糟糕。

情况5是使用称为协变返回类型的语言特性。这最初不在Java中,但我相信它是在v1.5中添加的(部分原因是因为这个问题)。如果编译器无法确定正确的返回类型,它将失败,这就是此情况发生的情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接