通用方法协方差 - 有效限制或编译器监督?

时间:2010-11-16 11:22:23

标签: java generics covariance

有没有人知道为什么java编译器不允许以下内容的明确答案?

class BaseClass {

      public <T extends Number> T getNumber(){
            return null;
      }
}

class SubClass extends BaseClass{

      @Override
      public <T extends Integer> T getNumber(){
            return null;
      }

}

这导致编译器抱怨:

“SubClass类型的方法getNumber()必须覆盖超类方法”

现在,当我把它放到我的同事们时,有些人试图解释它会导致编译器混淆。然而,正如还指出的,在概念上类似的以下内容是可编译的。

class BaseClass<T extends Number> {
      public T getNumber(){
            return null;

      }
}


class SubClass<T extends Integer> extends BaseClass<T>{
      @Override   
      public  T getNumber(){
            return null;

      }
}

如果子类调用超级实现,这可能会被滥用,但编译器会为此效果提供警告。我唯一的结论是,这是Sun公司的编译监督(不能让我自己说Oracle:-S)。

任何人都对这个有明确的答案吗?

4 个答案:

答案 0 :(得分:4)

假设确实允许在派生类的类型参数上添加更多限制。

那么如果你的班级也有<T extends Number> void setNumber(T number)方法怎么办?

BaseClass foo = new SubClass();
long longValue = 42;
foo.<Long>setNumber(longValue); 

编译器接受上述内容,因为BaseClass.setNumber接受从Number派生的任何类型参数。但实际实例只接受整数!

您可能会争辩说,如果type参数仅用于返回值,则应自动将其视为协变。但是编译器必须确保不以非协变的方式在方法体内使用type参数。

(在C# 4.0 this was actually solved中,但它涉及明确将您的类型参数标记为与outin关键字的协变或逆变。编译器不能简单地允许这样做不改变语言。)

答案 1 :(得分:0)

我不太关注你的问题:

当我实现您的第一部分源代码并使用javac *.java编译时,它编译得很好。但是,如果我更改了SubClass.getNumber()方法来执行此操作...

/* (non-Javadoc)
  * @see constraint.base.BaseClass#getNumber()
  */
 @Override
 public <T extends Number> T getNumber() {
  // TODO Auto-generated method stub
  return super.getNumber();
 }

我收到以下错误:

SubClass.java:18: type parameters of <T>T cannot be determined; no unique maximal instance exists for type variable T with upper bounds T,java.lang.Number
                return super.getNumber();
                                      ^
1 error

上面代码的原因是,通过调用super.getNumber(),编译器没有任何推断类型T的输入,因此它无法确定T将返回super.getNumber()。 {1}}因此,它无法满足SubClass.getNumber()

的返回类型

现在,为了从编译器"The method getNumber() of type SubClass must override a superclass method"获得错误,您的BaseClass应该是抽象以及它的getNumber()方法。< / p>

对于您提供的第二段代码,子类实际上可以调用super.getNumber(),因为编译器将知道(在编译时)SubClass的通用参数类型被推断为参数类型BaseClass

除此之外,我真的不知道你想问什么。

答案 2 :(得分:0)

@Wim Coenen提供了一个很好的答案。

这是一个小小的澄清。

如果这会编译,你会发生什么:

import java.math.BigInteger;

class BaseClass {

    public <T extends Number> T getNumber(T n) {
        System.out.println("Int value: " + n.intValue());
        return n;
    }
}

class SubClass extends BaseClass {

    @Override
    public <T extends BigInteger> T getNumber(T n) {
        System.out.println("Int value: " + n.intValue());

        // BigInteger specific!
        System.out.println("Bit count: " + n.bitCount());
        return n;
    }
}

public class Main {
    public static void main(String[] args) {
        BaseClass sub = new SubClass();

        // sub looks like a BaseClass...
        // ...but since it's a SubClass it expects a BigInteger!
        sub.getNumber(new Integer(5));
    }
}

答案 3 :(得分:0)

因为这里没有协方差。协方差意味着'随'变化',它指的是作为包含类类型而变化的返回类型。这不是发生在这里。你的协方差中没有'co'。