Java 协变 returns
Java Co-variant returns
在 java 中,为什么当非协变 return 类型产生编译时错误时,协变 return 类型是可接受的。当然,如果 JVM 可以处理协变 return 类型,那么它也可以处理非协变 return 类型。我假设当 java 看到一个具有协变 return 的重写方法时,它只应用与调用对象关联的方法。为什么非协变 return 类型不能发生同样的情况。
我的猜测是,这与违反 superclass' 方法合同的条款有关,当然,如果允许这样做,那么 sub class(覆盖)方法的行为不是很可预测(因为 return 类型不一致)?
举个例子(假设 DogFood 是 Food 的子class,但 CatFood 不是 Food 的子class):
动物class
public class Animal {
public Food seekFood() {
return new Food();
}
}
狗class
public class Dog extends Animal {
public DogFood seekFood() { //This is OK since its a covariant
return new DogFood();
}
}
猫class
public class Cat extends Animal {
public CatFood seekFood() { // This won't compile. Catfood is not covariant
return new CatFood();
}
}
如果两个方法具有相同的签名(方法名称和参数类型),编译器将无法决定选择调用哪个方法。如果两个方法具有相同的名称,但不同的参数类型和不同的 return 类型 - 它们具有不同的签名,然后编译器可以选择调用哪一个。
更新:
javac 将协变方法编译为其基础 class 方法,当您调用它时,基础 class 方法委托调用 subclass 方法。由于它们 return 不同的类型,因此无法进行类型转换。感谢 A. Sundararajan's Weblog ,这个过程可以用一个代码片段非常清楚地解释:
class CircleFactory extends ShapeFactory {
public Circle newShape() {
// your code from the source file
return new Circle();
}
// javac generated method in the .class file
public Shape newShape() {
// call the other newShape method here -- invokevirtual newShape:()LCircle;
}
}
class A {
Ra f(Pa x) { ... }
}
class B extends A {
@Override
Rb f(Pb x) { ... }
}
继承规则决定co-variant/contra-variant行为:
类型 T 的值只能分配给类型 T 或 parent 类型的变量。
方法调用意味着将其实际方法参数 (1) 分配给局部参数 (2),并将结果值 (1) 分配给要使用的某个位置 (2)。
现在,如果编译器遇到实际上可以是 B 的 A object,那么 B.f
就是一个有效的覆盖:
Pb可能只有Pa或者a时才有效 parent class (contra-variant);
因为你B.f
至少能接收到Pa值
Rb 可能只有当它是 Ra 或 a child class (co-variant);
时才有效
因为 B.f
必须 return 可以分配给 Ra 的东西。
这使得 child class 更严格、更具体。
在手头的案例中,当 DogFood 是 child 食物时,Cat 可能会 return DogFood。所以任何动物的食物确实是食物,即使动物实际上是猫。
在 java 中,为什么当非协变 return 类型产生编译时错误时,协变 return 类型是可接受的。当然,如果 JVM 可以处理协变 return 类型,那么它也可以处理非协变 return 类型。我假设当 java 看到一个具有协变 return 的重写方法时,它只应用与调用对象关联的方法。为什么非协变 return 类型不能发生同样的情况。 我的猜测是,这与违反 superclass' 方法合同的条款有关,当然,如果允许这样做,那么 sub class(覆盖)方法的行为不是很可预测(因为 return 类型不一致)?
举个例子(假设 DogFood 是 Food 的子class,但 CatFood 不是 Food 的子class):
动物class
public class Animal {
public Food seekFood() {
return new Food();
}
}
狗class
public class Dog extends Animal {
public DogFood seekFood() { //This is OK since its a covariant
return new DogFood();
}
}
猫class
public class Cat extends Animal {
public CatFood seekFood() { // This won't compile. Catfood is not covariant
return new CatFood();
}
}
如果两个方法具有相同的签名(方法名称和参数类型),编译器将无法决定选择调用哪个方法。如果两个方法具有相同的名称,但不同的参数类型和不同的 return 类型 - 它们具有不同的签名,然后编译器可以选择调用哪一个。
更新: javac 将协变方法编译为其基础 class 方法,当您调用它时,基础 class 方法委托调用 subclass 方法。由于它们 return 不同的类型,因此无法进行类型转换。感谢 A. Sundararajan's Weblog ,这个过程可以用一个代码片段非常清楚地解释:
class CircleFactory extends ShapeFactory {
public Circle newShape() {
// your code from the source file
return new Circle();
}
// javac generated method in the .class file
public Shape newShape() {
// call the other newShape method here -- invokevirtual newShape:()LCircle;
}
}
class A {
Ra f(Pa x) { ... }
}
class B extends A {
@Override
Rb f(Pb x) { ... }
}
继承规则决定co-variant/contra-variant行为:
类型 T 的值只能分配给类型 T 或 parent 类型的变量。
方法调用意味着将其实际方法参数 (1) 分配给局部参数 (2),并将结果值 (1) 分配给要使用的某个位置 (2)。
现在,如果编译器遇到实际上可以是 B 的 A object,那么 B.f
就是一个有效的覆盖:
Pb可能只有Pa或者a时才有效 parent class (contra-variant);
因为你
B.f
至少能接收到Pa值Rb 可能只有当它是 Ra 或 a child class (co-variant);
时才有效因为
B.f
必须 return 可以分配给 Ra 的东西。
这使得 child class 更严格、更具体。
在手头的案例中,当 DogFood 是 child 食物时,Cat 可能会 return DogFood。所以任何动物的食物确实是食物,即使动物实际上是猫。