Java - 当返回类型为自己的方法参数类型使用泛型时,覆盖扩展接口的返回类型

时间:2011-02-20 11:03:02

标签: java generics methods types return

我偶然发现了java继承的好奇心,我希望你能就此提出更好的想法:

假设有两个接口A和A1

接口A1扩展A

接口A有一个返回泛型类型的方法。

泛型类型就像GenericType<T>

现在基本的想法是从中更改此通用返回类型 接口A中的GenericType<Object> 接口A1中的GenericType<String>

一开始似乎很容易(稍后会出现不好的事情)

我们声明接口A像

public interface InterfaceA {
  public GenericType<? extends Object> getAGenericType();  
}

和接口A1一样

public interface InterfaceA1 extends InterfaceA
{
  @Override
  public GenericType<String> getAGenericType();
}

如您所见,我们被迫在接口A中编写GenericType<? extends Object>以允许使用基于泛型的“子类”覆盖它。 (实际上,generictype的泛型参数是子类,而不是泛型类型本身)

现在假设GenericType有自己的方法,如:

public interface GenericType<D>
{
  public void doSomethingWith( D something );
}

现在尝试实例化A1效果很好。

而是试图实例化A会很糟糕。要看看为什么要看这个“使用界面”类:

public class LookAtTheInstance
{
  @SuppressWarnings("null")
  public static void method()
  {
    InterfaceA a = null;
    InterfaceA1 a1 = null;

    GenericType<String> aGenericType = a1.getAGenericType();

    GenericType<? extends Object> aGenericType2 = a.getAGenericType();
    Object something = null;
    aGenericType2.doSomethingWith( something );
  }
}

你问:“现在?”

它不适用于最后一行。实际上,参数“something”甚至不是来自“Object”类型,它来自Type“?extends Object”。所以你不能传递声明的“对象”类型。你根本无法传递任何东西。

所以你最终声明了很好的接口,但事实证明,它无法正确实例化。

您是否有想法如何建模这样的用例,其中子类必须覆盖返回类型,而返回类型是泛型?

或者你会如何解决这种模型案例?

或者我只是错过了泛型声明中的一个简单点,我的例子可能就是这样吗?

-----------(1)由于答案而编辑-----------

一个非常好的基本想法是使界面A更抽象!我首先有完全相同的想法,但是......(这必须要来)

假设这样做:

我们引入了一个新的接口AGeneric

public interface InterfaceAGeneric<T>{
  public GenericType<T> getAGenericType();
}

现在我们必须从这个新界面扩展A和A1:

public interface InterfaceA extends InterfaceAGeneric<Object>{}
public interface InterfaceA1 extends InterfaceAGeneric<String>{}

这样可行,但它打破了原始继承的道路。

如果我们希望A1仍可从A扩展,我们必须将A1更改为

public interface InterfaceA1 extends InterfaceA, InterfaceAGeneric<String>{}

再次出现问题。这不起作用,因为我们间接扩展了具有不同泛型类型的相同接口。遗憾的是,这是不允许的。

你看到了问题吗?

-

并指出另一种情况:

如果您将GenericType<? extends Object>投射到GenericType<Object>,它显然有效。 例如:

public class LookAtTheInstance
{
  public static void main( String[] args )
  {
    InterfaceA a = new InterfaceA()
    {
      @Override
      public GenericType<? extends Object> getAGenericType()
      {
        return new GenericType<Object>()
        {
          @Override
          public void doSomethingWith( Object something )
          {
            System.out.println( something );
          }
        };
      }
    };
    ;

    @SuppressWarnings("unchecked")
    GenericType<Object> aGenericType2 = (GenericType<Object>) a.getAGenericType();

    Object something = "test";
    aGenericType2.doSomethingWith( something );
  }  
}

所以我觉得解析方法的参数类型

public interface GenericType<D extends Object>
{
  public void doSomethingWith( D something );
}

错了。

如果D与“?extends Object”统一,为什么参数类型不被强制为“对象”?

这不会更有意义吗?

6 个答案:

答案 0 :(得分:6)

  

现在的基本思路是将接口A中GenericType的通用返回类型更改为接口A1中的GenericType

这是不可能的,因为 Java Generics是不变的。 [1]

正如您所发现的那样,您不能让接口声明返回GenericType<Object>的方法,并且在子接口中覆盖返回GenericType<String>的方法:后者返回类型不是前者的子类型。并且有充分的理由!

你试过

  

使用不同的泛型类型间接扩展相同的接口。遗憾的是,这是不允许的。

这是不可行的:例如同时实现Epublic E set(int index, E element)的类List<String>List<Object>的类型应该是什么?您的子类接口必须生成类似的混合:子接口中getAGenericType的返回值必须同时实现GenericType<String>GenericType<Object>接口。正如我们所看到的,这是不可能的。

编译器不知道你要对GenericType中的type参数做什么(虽然它理论上可以找到,但它没有)。如果您有一个GenericType<String>类型的变量并为其分配了GenericType<Object>,那么最终可能会将Long个实例放在需要String的位置,并获得ClassCastException你不会指望一个人。

在变量doSomethingWith的{​​{1}}方法中,您可以传递一件事:GenericType<? extends Object> aGenericType2null是唯一具有null子类型的对象引用。 ? extends Object的下限类型是null类型,它不能用Java表示,只隐式存在? extends Object引用的类型。

[1] http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29#Java

答案 1 :(得分:2)

@Override annotation:

  

覆盖方法时,您可能会   想要使用@Override注释   它指示编译器你   打算覆盖中的方法   超类。如果由于某种原因,   编译器检测到该方法   不存在于其中一个超类中,   它会产生错误。

使用此注释,您无法更改函数的返回类型。

如果要覆盖返回类型,只需使接口A更抽象,将泛型添加到此接口:

public interface InterfaceA<T> {
  public GenericType<T> getAGenericType();  
}

Sample about overriding a generic method in a generic class.

答案 2 :(得分:2)

我不知道这是否是您所期望的,但您可以声明您的界面是这样的

public interface Interface <K extends Object> { ... }    

虽然你的课可能看起来像     public class InterfaceImpl extends Interface<String> { ... }

答案 3 :(得分:1)

问题是InterfaceA不知道它持有什么类型。如果你让InterfaceA采用泛型参数,那么你可以这样做:

public interface InterfaceA<T>
{
  public GenericType<T> getAGenericType();  
}

public interface InterfaceA1 extends InterfaceA<String>
{
  @Override
  public GenericType<String> getAGenericType();
}

public class LookAtTheInstance
{
  @SuppressWarnings("null")
  public static void method()
  {
    InterfaceA<String> a = null;
    InterfaceA1 a1 = null;

    GenericType<String> aGenericType = a1.getAGenericType();

    GenericType<String> aGenericType2 = a.getAGenericType();
    String something = null;
    aGenericType2.doSomethingWith( something );
  }
}

答案 4 :(得分:1)

我参加聚会已经好几年了,但是我在搜索相关问题的过程中找到了这个页面,但没有一个答案能够真正触及中心问题,我认为值得澄清。让我们看一个稍微充实的例子:

interface GenericType<D> {
    D getAValue();
    void doSomethingWith(D value);
}

class StringType implements GenericType<String> {
    @Override
    public String getAValue() {
        return "Hello World";
    }

    @Override
    public void doSomethingWith(final String value) {
        System.out.println(value.length());
    }
}


interface InterfaceA {
    GenericType<? extends Object> getAGenericType();
}

interface InterfaceA1 extends InterfaceA {
    @Override
    GenericType<String> getAGenericType();
}

class AnActualA1 implements InterfaceA1 {
    @Override
    public GenericType<String> getAGenericType() {
        return new StringType();
    }
}


class LookAtTheInstance {
    public static void method() {
        InterfaceA1 a1 = new AnActualA1();

        // 'g1' is a StringType, which implements GenericType<String>; yay!
        GenericType<String> g1 = a1.getAGenericType();

        // Everything here is fine.
        String value = g1.getAValue();
        g1.doSomethingWith("Hello World");


        // But if we upcast to InterfaceA???
        InterfaceA a = (InterfaceA) a1;

        // Note: a.getAGenericType() still returns a new StringType instance,
        // which is-a GenericType<? extends Object>.
        GenricType<? extends Object> g = a.getAGenericType();

        // StringType.getAValue() returns a String, which is-an Object; yay!
        Object object = g.getAValue();

        // StringType.doSomethingWith() method requires a String as the parameter,
        // so it is ILLEGAL for us to pass it anything that cannot be cast to a
        // String. Java (correctly) prevents you from doing so.

        g.doSomethingWith(new Object()); // Compiler error!
    }
}

从概念上讲,GenericType不是GenericType,因为GenericType只能doSomethingWith()字符串,而GenericType需要能够doSomethingWith()任何对象。 GenericType是一种折衷方案,编译器允许您将其用作任何GenericType的“基类”,其中D是一个Object,但只允许您使用该类型的引用来调用任何可能的运行时类型安全的方法的价值 '?' (例如getAValue(),其返回值始终可以安全地转换为Object,因为D是一个Object而不管运行时类型如何)。

很难说原始海报实际上试图用这个代码进行建模是什么(如果有的话),特别是GenericType的通用性究竟需要多少,但也许继承应该是另一种方式围绕?

/**
 * I can do something with instances of one particular type and one particular
 * type only.
 */
interface GenericType<D> {
    void doSomethingWith(D value);
}

/**
 * I can do something with instances of any type: I am-a GenericType<String>
 * because I can totally do something with a String (or any other kind of
 * Object).
 */
interface NonGenericType extends GenericType<Object>, GenericType<String> {
    @Override
    void doSomethingWith(Object value);
}


interface StringHandlerFactory { // nee InterfaceA1
    GenericType<String> getAGenericType();
}

/**
 * I extend StringHandlerFactory by returning a NonGenericType (which is-a
 * GenericType<String>, satisfying the interface contract, but also so much
 * more).
 */
interface ObjectHandlerFactory extends StringHandlerFactory { // nee InterfaceA
    @Override
    NonGenericType getAGenericType();
}

缺点是没有好的方法向java编译器表达NonGenericType扩展GenericType,即使在概念上它可以在这种情况下,因为GenericType从不使用D作为返回值。您必须手动指定希望它扩展的每个GenericType。 :(

答案 5 :(得分:0)

  

所以你最终声明了很好的接口,但事实证明,它无法正确实例化。

我认为InterfaceA的目的根本不是实例化的,因为它的一个可靠类是通用的。这就是你的意思:

public GenericType<? extends Object> getAGenericType()