什么是协变返回类型?

123

在Java中,协变返回类型是什么?在面向对象编程中呢?


6
这篇博客文章(https://blogs.oracle.com/sundararajan/entry/covariant_return_types_in_java)解释了Java中的协变返回类型,这里只是为知识库添加信息。 - Akhil Jain
@AkhilJain:那篇博客文章非常出色和简单。这是我见过的最好的通过示例解释Java支持协变返回类型的方法。 - kevinarpe
@kevinarpe 谢谢,我很高兴它对许多人有帮助。 - Akhil Jain
@Manash 协变返回类型基本上用于运行时多态性,当方法被覆盖时,该覆盖可以允许方法返回相同类对象返回类型或子类返回类型。 - Manash Ranjan Dakua
9个回答

163

协变返回类型意味着在重写一个方法时,被重写方法的返回类型的子类型也可以作为重写方法的返回类型。

通过一个例子来说明,一个常见的情况是Object.clone() - 它声明返回类型为Object。你可以在自己的类中将其重写如下:

public class MyFoo
{

   ...

   // Note covariant return here, method does not just return Object
   public MyFoo clone()
   {
       // Implementation
   }
}

这里的好处是,任何持有对MyFoo对象的显式引用的方法都可以调用clone()并且无需进行强制类型转换就能知道返回值是MyFoo的一个实例。没有协变返回类型,MyFoo中被覆盖的方法必须声明返回Object类型,因此调用代码必须明确将方法调用的结果向下转型(即使双方“知道”它只能是MyFoo的一个实例)。

请注意,clone()没有什么特别之处,任何被覆盖的方法都可以有协变返回 - 我在这里使用它作为示例,因为这是一个标准方法,经常使用到该特性。


难道不应该与 List<Foo>List<FooBar> 相关吗? - zinking
2
这里讨论的是更广义上的协变类型,而不仅仅是针对协变的返回类型。尽管如此,它们都是相同的基本原理——你可以将clone()的顶层定义视为Method<Void, Object>,并询问更具体的Method<Void, MyFoo>是否可分配给该父类型。只有当Java方法在其返回类型上是协变的时,才会是这样的情况。 - Andrzej Doyle

49

这里有另一个简单的例子:

Animal

public class Animal {

    protected Food seekFood() {

        return new Food();
    }
}

Dog

public class Dog extends Animal {

    @Override
    protected Food seekFood() {

        return new DogFood();
    }
}

可以修改Dog类的seekFood()方法的返回类型为DogFood,它是Food的子类,如下所示:

@Override
protected DogFood seekFood() {

    return new DogFood();
}

这是完全合法的重载,DogseekFood()方法的返回类型被称为协变返回类型


10
从JDK 1.5发布开始,Java引入了协变类型。我将通过一个简单的案例来解释,当我们重写函数时,函数被允许更改其行为,这是你在大多数书籍中读到的,但作者们遗漏的是我们还可以改变返回类型。请查看下面的链接以获得澄清,只要返回类型可以分配给方法基础版本的返回类型,我们就可以更改返回类型。因此,返回派生类型的这个特性称为协变...... Can overridden methods differ in return type?

9
协变返回类型简单来说就是返回自身类的引用或其子类的引用。
class Parent {
 //it contain data member and data method
}

class Child extends Parent { 
//it contain data member and data method
 //covariant return
  public Parent methodName() {
     return new Parent();
          or 
     return Child();
  }

}

2
它还包括这样一种情况:Parent.foo() 返回一个与类型 A 无关的类型 A,而 Child.foo() 返回从 A 派生出来的类型 B - Davis Herring

2

除了以上的回答,协变返回类型中可以进行重写,但有一个限制,即重写方法(子类方法)的返回类型应该是被重写方法(父类方法)返回类型的子类。这个规则从Java 5开始就有效。


1
协变返回类型指定返回类型可以沿着子类的方向变化。
class One{  
    One get(){return this;}  
}  

class Two extends One{  
  Two get(){return this;}  

void message(){
  System.out.println("After Java5 welcome to covariant return type");
}  

public static void main(String args[]){  
    new Two().get().message();  
}  
}

在Java 5之前,不可能通过改变返回类型来覆盖任何方法。但是,自从Java5以后,如果子类覆盖了返回非原始类型的任何方法,且将其返回类型更改为子类类型,则可以通过更改返回类型来覆盖方法。


1
  • 它有助于避免类层次结构中存在的混淆类型转换,从而使代码可读性、可用性和可维护性更强。
  • 当覆盖方法时,我们可以自由地拥有更具体的返回类型。

  • 有助于防止返回值时出现运行时ClassCastException异常。

参考: www.geeksforgeeks.org


0
Java中的协变返回类型允许缩小重写方法的返回类型。 这个特性有助于避免客户端进行向下转型。它允许程序员在不需要类型检查和向下转型的情况下编程。 协变返回类型始终仅适用于非原始返回类型。
interface Interviewer {
    default Object submitInterviewStatus() {
        System.out.println("Interviewer:Accept");
        return "Interviewer:Accept";
    }
}
class Manager implements Interviewer {
    @Override
    public String submitInterviewStatus() {
        System.out.println("Manager:Accept");
        return "Manager:Accept";
    }
}
class Project {
    public static void main(String args[]) {
        Interviewer interviewer = new Manager();
        interviewer.submitInterviewStatus();
        Manager mgr = new Manager();
        mgr.submitInterviewStatus();
    }
}

另一个例子来自Java,

UnaryOperator.java

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    /**
     * Returns a unary operator that always returns its input argument.
     *
     * @param <T> the type of the input and output of the operator
     * @return a unary operator that always returns its input argument
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

Function.java

@FunctionalInterface
public interface Function<T, R> {

    ........
    ........
    ........
    ........

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

0
在Java5之前,无法通过更改返回类型来覆盖任何方法。但是自从Java5以来,如果子类覆盖了任何返回类型为非原始类型的方法,且将其返回类型更改为子类类型,则可以通过更改返回类型来覆盖方法。

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