Java - 当返回类型对自己的方法参数类型使用泛型时,重写扩展接口的返回类型

2022-09-04 03:26:06

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

假设有两个接口 A 和 A1

接口 A1 扩展了 A

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

泛型类型类似于 。GenericType<T>

现在的基本思想是将此泛型返回类型从接口 A 更改为接口 A1 中的泛型返回类型GenericType<Object>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 有自己的方法,如下所示:

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 );
  }
}

你问:“那现在呢?

它不适用于最后一行。事实上,参数“某物”甚至不是来自类型“对象”,而是来自类型“?扩展对象”。因此,您无法传递声明的“对象”类型。你根本无法通过任何东西。

因此,您最终声明了漂亮的接口,事实证明,这些接口无法正确实例化。

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

或者你会如何绕过这样一个模型案例?

或者我只是在泛型声明中遗漏了一个简单的点,而我的例子是可能的?

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

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

假设这样做:

我们引入了一个新的界面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 与 “?扩展对象“为什么参数类型不强制为”对象“?

这不会更有意义吗?


答案 1

现在的基本思想是将此泛型返回类型从接口 A 中的 GenericType 更改为接口 A1 中的 GenericType。

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

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

您尝试

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

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

编译器不知道您将如何处理其中的类型参数(尽管理论上它可以找到答案,但它不会)。如果你有一个类型的变量并为其分配了 a,你很可能最终会在 a 是预期的实例中放置一个实例,并在你不会期望的地方得到一个实例。GenericTypeGenericType<String>GenericType<Object>LongStringClassCastException

在变量的方法中,您可以传递一件事:. 是唯一子类型为 的对象引用。? extend Object 的下限类型是 null 类型,它不能用 Java 表示,并且仅隐式地作为引用的类型存在。doSomethingWithGenericType<? extends Object> aGenericType2nullnull? extends Objectnull

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


答案 2

我不知道这是否是你所期望的,但你可以像这样声明你的接口:

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

虽然您的类可能如下所示:

public class InterfaceImpl implements Interface<String> { ... }