Java泛型:子类化时类型不兼容

6

当从通用类类型/形式类型参数 T/E 继承时,使用有效的类类型/实际类型参数,例如 Type/String,会出现许多组合,这很容易造成困惑。不知道该选择哪个以及何时使用?

    public class SubClass<T> implements SuperIfc<T>  <-- It is straight forward to understand
    public class SubClass<T> implements SuperIfc<Type>

    public class SubClass<Type> implements SuperIfc<T>
    public class SubClass<Type> implements SuperIfc<Type>
    public class SubClass<Type> implements SuperIfc
    public class SubClass implements SuperIfc<Type>
    public class SubClass implements SuperIfc<T>    <--- Hope we cannot declare <T> in his case while initialising SubClass.

    // Bounded type parameter
    public class SubClass<T extends Type> implements SuperIfc<Type>
    public class SubClass<T extends Type> implements SuperIfc<T> <-- Looks <T> at SuperIfc also refers <T extends Type>, and no need to declare it again at SuperIfc.

    // Recursive type bound
    public class SubClass<T extends Comparable<T>>> implements SuperIfc<T>
    public class SubClass<T extends Comparable<T>>> implements SuperIfc<Type>

为了更清晰地解决“在子类化时不兼容类型”的问题。

案例一:

public class Test {

    interface TestIfc {

        public static <T extends TestIfc> T of(int choice) {

            if(choice == 1) {
                return new TestImpl(); <-- PROB_1: incompatible type error 
            } else {
                return new SomeOtherTestImpl(); //incompatible type error
            }
        }
    }

    static class TestImpl implements TestIfc {}
    
    static class SomeOtherTestImpl<T extends TestIfc> implements TestIfc {

        //The below method also having same error though with declaration
        public T of() {
            return new TestImpl();  <-- PROB_2: incompatible type error
        }
    }
}

案例1:

问题1:返回类型为T extends TestIfc,但返回了TestImpl implements TestIf,出了什么问题?

问题2:与问题1类似,如何在不进行外部强制转换的情况下进行纠正? 请帮忙。


案例2:

public interface SuperIfc<T> {
    
    public T create(Object label);
}

class Type {

    public static Type of(){
         return new Type();
    }
}
------

public class SubClass<Type> implements SuperIfc<Type>{

    @Override
    public Type create() {
        return Type.of(); <---- PROB_1: cannot resolve method
    }
}
-------

public class SubClass<T extends Type> implements SuperIfc<Type>{

    @Override
    public Type create() {
        return Type.of(); <---- PROB_1: is resolved
    }
}

SuperIfc<Type> object = new SubClass(); <-- PROB_2 Unchecked assignement warning
SuperIfc<Type> object = new SubClass<TypeImpl>(); <-- PROB_3: bound should extend Type
  1. I would like to know how to resolve Case_2, PROB_1 and PROB_2 together?

  2. How to write subclass for generic super class with class types and what are the rules?

  3. What should be taken care when changing generic T to class Type while subclassing? may be the difference between below and when to use?

     public class SubClass<Type> implements SuperIfc<Type>
     public class SubClass<Type> implements SuperIfc
     public class SubClass implements SuperIfc<Type>
     public class SubClass<T extends Type> implements SuperIfc<Type>
     public class SubClass<T extends Type> implements SuperIfc<T>
     public class SubClass<T> implements SuperIfc<Type>
    

PROB_2 你应该写成 SuperIfc<TypeImpl> object = new SubClass<>(); 否则你就在使用原始类型。 - matt
PROB_1 你的方法根本不应该是通用的。public static TestIfc of(int choice) - matt
我同意上面的观点,你是正确的!@马特 - Kanagavelu Sugumar
3个回答

1

这是一个很长的答案,请仔细阅读(至少阅读第一种情况和最后部分,如果您愿意可以跳过第二种情况中的解决方案)

情况1

问题1:

您的问题在于编译器无法证明TTestImplSomeOtherTestImpl相同。如果有另一个类,称为TestFoo,它实现了TestIfc,那么如果您调用TestIfc.<TestFoo>of(1)方法,您期望得到一个类型为TestFoo的对象,但实际上会得到一个TestImpl,这是错误的。编译器不希望您这样做,因此会抛出错误。您应该只需删除泛型,如下所示:

public static TestImpl of(int choice) {
  if(choice == 1) {
    return new TestImpl();
  } else {
    return new SomeOtherTestImpl();
  }
}

然后你可以安全地调用TestIfc.of

问题2:

这基本上是同样的问题。编译器无法证明SomeOtherTestImpl的类型参数TTestImpl相同。如果你有一个像这样的对象(其中TestFoo不扩展TestImpl,但实现了TestIfc)会怎么样?

SomeOtherTestImpl<TestFoo> testImpl = ...;

然后你尝试像这样调用of方法:
TestFoo testFoo = testImpl.of();

你能看出问题在哪里吗?你的of方法返回一个TestImpl,但你希望它返回一个TestFoo,因为TTestFoo。编译器阻止了你这样做。再次强调,你应该摆脱泛型:
public TestImpl of() {
  return new TestImpl(); //This works now
}



案例2

问题1

这个问题是由于您将类型参数命名为与类相同的名称 - Type。当您将类型参数的名称更改为,比如,T,它将起作用,因为现在Type是一个类的名称。在此之前,您的类型参数Type隐藏了类Type

然而,再次说明,没有必要使用类型参数,因为您可以这样做,并满足所有约束条件。

public class SubClass implements SuperIfc<Type> {
    @Override
    public Type create() {
        return Type.of();
    }
}

问题2:

未经检查的分配是因为您在执行时没有为SubClass提供类型参数

SuperIfc<Type> object = new SubClass();

这可以通过明确给SubClass指定Type来解决:
SuperIfc<Type> object = new SubClass<Type>();

或者通过放置一个空的钻石符号(这是我更喜欢的方法)。这第二种方法意味着编译器可以自动推断出通过 new Subclass<>(),你的意思是 new Subclass<Type>()

SuperIfc<Type> object = new SubClass<>();

为什么编译器会抱怨:

那个构造函数调用(new Subclass())基本上就像调用一个看起来像 public SubClass makeNewSubClass() {...} 的方法。事实上,让我们用下面这个语句替换上面的语句,这将导致相同的警告。

SuperIfc<Type> object = makeNewSubClass();

SubClass通常需要一个类型参数(称其为T),但在这里,它没有给出任何类型参数。这意味着T可以是任何东西,任何继承Type的类(由于约束条件SubClass<T extends Type>)。

您可能会认为,如果T始终是Type的子类,那么它应该没问题,因为上面object的类型是SuperIfc<Type>。假设有一个像这样的类 - class TypeFoo extends Type - 并且方法makeNewSubClass实际上返回类型为SubClass<TypeFoo>的对象(new SubClass<TypeFoo>())。

因此,您期望对象是 SuperIfc<Type>,但实际上它是 SubClass<TypeFoo>。您可能认为这没关系,毕竟,TypeFooType 的子类,对吧?嗯,Java 的泛型实际上是 不变的,这意味着对于编译器来说,SubClass<TypeFoo> 不是 SuperIfc<Type> 的子类 - 它认为它们是两个完全无关的类型。别问我 - 我不知道为什么 : )。甚至 SubClass<TypeFoo> 也不被认为是 SubClass<Type> 的相同或子类!
这就是为什么编译器会发出警告的原因 - 因为原始类型(当您使用new SubClass()而没有给出类型参数时,就是所谓的原始类型)可以表示任何内容。至于为什么它会发出警告而不是错误 - 泛型是在Java 5中引入的,因此对于那之前的代码,编译器只会发出警告。
问题3: 根据该错误,您提供的类型参数(TypeImpl)应该扩展Type。这是因为您将SubClass定义为class SubClass<T extends Type> ...。在这里,您将TypeImpl作为T的参数,因此TypeImpl必须扩展Type,所以您需要做的就是添加class TypeImpl extends Type来解决特定的错误。
然而,即使您这样做了,您仍会收到“不兼容的类型”错误,因为如我之前在讨论问题2时所说,SuperIfc<Type>并不被认为是SubClass<TypeImpl>的超类型,即使TypeImpl扩展了Type
您可以这样做。
SuperIfc<TypeImpl> object = new SubClass<TypeImpl>();

或者你可以这样做。
SuperIfc<? extends Type> object = new SubClass<TypeImpl>();

在第二个解决方案中,我们失去了关于SuperIfc<T>中的T是什么的信息;我们只知道它扩展了Type。第二个方案的好处是您可以稍后将任何SubClass对象重新分配给它(这在第一个版本中不起作用,因为它只允许SubClass<TypeImpl>)。
SuperIfc<? extends Type> object = new SubClass<TypeImpl>();
object = new SubClass<Type>(); //works great

Case 2的替代方案

你似乎想根据类型参数返回不同类型的对象。但这在运行时是不可能的,因为类型参数会被擦除。不过,有一些解决方法。

这里介绍一种使用函数接口的方法。它不需要强制转换,并确保类型安全。

//This is the functional interface. You can also use Supplier here, of course
interface Constructor<T> {
  T create();
}

class SubClass<T extends Type> implements SuperIfc<T> {

  public Constructor<T> ctor;

  public SubClass(Constructor<T> ctor) {
    this.ctor = ctor;
  }

  @Override
  public T create(Object label) {
    return ctor.create();
  }
}

class Type {}
class TypeImpl extends Type{}

通过使用 Constructor 对象,您将知道返回的 Type 对象始终是正确的类型。这很容易应用于您现有的代码 - 您不需要编写自己的 ConstructorImpl 类或任何其他内容。
现在,您可以编写以下内容,您只需要添加构造函数的方法引用 (Type::new)。
SuperIfc<Type> object1 = new SubClass<>(Type::new);

SuperIfc<? extends Type> object2 = new SubClass<>(TypeImpl::new);

使用lambda表达式,你也可以稍微复杂一些地编写这个代码块:
SuperIfc<TypeImpl> object = new SubClass<>(() -> new TypeImpl());
---
SuperIfc<? extends Type> object = new SubClass<>(() -> new TypeImpl());

1
在第一个of()方法中,该方法可以返回任何实现InformationIfc接口的类型,但是您的方法总是返回特定的实现 - InformationImpl - 这是不可接受的。
例如,如果您有另一个实现该接口的类SomeOtherInformationImpl,那么调用该方法的调用方将被允许编写:
SomeOtherInformationImpl i = InformationImpl.of();

但是你的方法没有返回 SomeOtherInformationImpl
第二个 of() 方法和第一个方法一样存在问题。 如果你用以下方式实例化了你的类:
InformationImpl i = new InformationImpl<SomeOtherInformationImpl>();
< p > of() 方法必须返回一个 SomeOtherInformationImpl,而不是一个 InformationImpl


在我的第二种方法中,我说可以在代码注释中声明它在类中。 - Kanagavelu Sugumar
@KanagaveluSugumar 哦,这种情况下第二种方法和第一种方法有相同的错误。 - Eran
加一的按钮还没有说服我,让我再看一遍你的答案。 - Kanagavelu Sugumar
我刚刚更新了我的答案,并提供了不返回特定实现的方法。问题仍然存在。 - Kanagavelu Sugumar
我也不明白调用者怎么能够写SomeOtherInformationImpl i = InformationImpl.of();?调用者只能期望InformationIfc i = InformationImpl.of();,对吧? - Kanagavelu Sugumar

1

第一种情况的问题。

问题1:返回类型为T,T需扩展TestIfc。

你为什么要在这里使用泛型?既然你有一个静态方法,我可以这样做。

TestIfc broken = TestIfc<SomeOtherImplementation>.of(0);

SomeOtherImplementation 不是一个 TestImpl。这是设计上的问题。你真正想要的是。

 public static TestIfc of(int choice)

下一步。

静态类 SomeOtherTestImpl<T extends TestIfc> 实现了 TestIfc 接口。

TestIfc 没有参数化,而 SomeOtherTestImp 是参数化的,但它与您正在实现的接口完全无关。更不用说,TestIfc 有一个与接口无关的 静态方法 of

如果我猜的话,我认为你想要。

interface TestIfc<T>{}
static class TestImpl implements TestIfc<TestImpl> {}
static class SomeOtherTestImpl<T extends TestIfc> implements TestIfc<T>{}

我能提供的最好翻译是:这是我所能想到的最好方案,因为不清楚你实际希望发生什么。 问题3的示例
public class SubClass<Type> implements SuperIfc<Type>

这段代码有问题,因为SubClass<Type>声明了Type是一个泛型参数的名称。它没有对类型进行任何限制,因此会出现方法未找到的错误。
public class SubClass<Type> implements SuperIfc

代码有误,创建了一个名为 Type 的泛型参数,与你的原始类型版本的 SuperIfc 没有关系。

public SubClass implements SuperIfc<Type>

这很好。

public class SubClass<T extends Type> implements SuperIfc<Type>
public class SubClass<T> implements SuperIfc<Type>

这两个都不错,但是 T 和 SuperIfc 参数没有关系,因此你的实现会出现问题。
public Type create(Object label);

第一个泛型参数指定了你将在整个类中使用的名称。

谢谢!@matt,plusOne;我会审核并回复。 - Kanagavelu Sugumar

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