在 Java 中的抽象方法中使用通配符和声明泛型类型的区别



我正在尝试理解Java中的泛型类型,理论上它看起来可以理解,但是当我需要将其应用于实际代码时,我遇到了问题。我想声明将返回泛型类型的抽象方法。假设我有一个名为 Magicable 的空接口,并且 2 个类实现了它:Magican 和 Witch。 现在我想知道这 3 个声明有什么区别:

/*1*/protected abstract <T extends Magicable> List<T> getMagicables();
/*2*/protected abstract List<? extends Magicable> getMagicables();
/*3*/protected abstract List<Magicable> getMagicables();
  1. 在第一种情况下,当我想在扩展抽象类的某个类中实现此方法的主体时,我遇到了问题:

    @Override
    protected List<Magican> getMagicable() {..}
    

    我有警告消息:

    类型安全:返回类型 List对于 MagicanService类型的 getMagicable(),需要未经检查的转换以符合 List来自 MagicableService 类型。

  2. 第二种情况下,我没有这个警告,但我在抽象类中遇到了问题,我在上面声明了抽象方法:

    public void <T extends Magicable> T getOneFromList() {
    List<T> list = getMagicables();
    //.....
    }
    

    在这种情况下,我在getMagicables()调用中遇到编译错误:

    类型不匹配:无法从 List<capture#2-of> 扩展到 List

  3. 第三种情况会导致上述两个代码位置的编译错误。我认为在我的情况下,这是否是正确的解决方案。

  1. 第一种情况

只需声明您的方法:

@Override
protected <T extends Magicable> List<T> getMagicables() {
List<T> list = ...
return list
}

如果你真的想要这个:

@Override
protected List<Magican> getMagicable() {..}

您可能必须在类定义中声明泛型 T

public abstract class AbstractKlass<T extends Magicable> {
protected abstract List<T> getMagicables();
}

然后在您的子类中:

public class MySubClass extends AbstractKlass<Magican> {
@Override
protected List<Magican> getMagicables() {
...
}
}
  1. 第二种情况

编译错误是正常的,因为方法的签名<? extends Magicable>意味着从您可以将这些元素视为可魔术的那一刻起,您就不在乎列表中的内容。拨打电话时

List<T> list = getMagicables();

您想在不知情的情况下照顾 T 型。换句话说,有3个用例:T是Magicable(OK),T是Magician(错误,因为getMagicables可能会返回女巫列表)和T是Witch(也错了)。

  1. 为什么我使用? extends Magicable而不仅仅是在列表中Magicable

因为List<Magician>List<? extends Magicable>的亚型,但不是List<Magicable>的亚型。这对于方法的参数很有用。

public void doIt(List<? extends Magicable> list) {
// you can't add a Magician here
}

可用作

List<Witch> list = ...
doIt(list);

但如果你有

public void doIt(List<Magicable> list) {
// you can add a Magician here
}

您不能将其用作

List<Witch> list = ...
doIt(list); // compile error

对于问题的部分,您确实向我们展示了方法/* 3 */就足够了,您不需要该部分代码的泛型。但是您需要尊重可替代性:

您会在 #1 中得到错误,因为子类型方法限制了返回类型的范围:MagicanMagicable,反之则不然。子类型中允许使用超类型的Magicable。子类型方法必须可替换为超类型方法,但示例中的情况并非如此。

#2 中的错误是由于通配符?的性质:? extends MagicableT extends Magicable不必是同一类型。如果在类作用域中声明T,例如.classMagican<T> implements Magicable<T>(在这种情况下,您的接口需要声明 T),则类型中所有出现的T都将引用同一个类。

public abstract class AbstractMagicable<T extends Magicable> {
abstract List<T> getMagicables1();
abstract List<? extends Magicable> getMagicables2();
abstract List<Magicable> getMagicables3();
}
class MagicableWitch extends AbstractMagicable<Witch> {
@Override
List<Witch> getMagicables1() {
return null;
}
@Override
List<? extends Magicable> getMagicables2() {
return getMagicables1();
}
@Override
List<Magicable> getMagicables3() {
return Collections.singletonList(new Witch());
}   
}
class MagicableMagician extends AbstractMagicable<Magician> {
@Override
List<Magician> getMagicables1() {
return null;
}
@Override
List<? extends Magicable> getMagicables2() {
return getMagicables1();
}
@Override
List<Magicable> getMagicables3() {
return Collections.singletonList(new Magician());
}
}

1)当您在使用时想将其替换为真实姓名时,请使用T。例如class MagicableWitch extends AbstractMagicable<Witch>.

在这里,Witch替换了 T,因此abstract List<T> getMagicables1();在其具体类中更改为List<Witch> getMagicables1()

2) ?在要替换的类将在运行时可用时使用。

3)即使Witch implements MagicableList<Magicable>List<Witch>也不同。实现如getMagicables3所示 .

在第一种情况下,抽象方法被声明为使用泛型类型<T extends Magicable>这意味着您的方法可以返回 Magicable 列表或实现它的任何类型。在您的实现中,您将返回具体类型 Magican,这是一个神奇的。您可以安全地忽略警告并添加@SuppressWarning("unchecked")以禁用警告。需要注意的是,任何扩展类的类都将仅限于返回Magican列表。

在第二种情况下,声明List<T> list = getMagicables();抛出错误,因为您的方法不返回List<T>而是返回不同事物的List<? extends Magicable'。由于泛型的工作方式,当您声明使用未绑定通配符的返回类型时,调用您的方法的任何代码都必须具有可接受的匹配类型,在您的情况下为List<? extends Magicable>List<?>

关于第三种情况,您的抽象方法返回一个List<Magicable>,而您的实现返回一个List<Magic>。这似乎违反直觉,但你不能用 Java 中的泛型做这样的事情:List<Magicable> list = ArrayList<Magic>。这可能看起来很奇怪,因为数组允许您声明类似Magicable[] magics = new Magican[3];.这是一个常见的误解,因为数组是协变的,而泛型是不变的。协变的意思是,如果你有两个类SuperSub extends SuperSub[] is a subtype of Super[]。对于泛型,因为它们是不变的,所以这两者之间没有关系,List<Sub>不是List<Super>的子类型。

如果要返回泛型类型,只需使用与扩展抽象类的类中protected <T extends Magicable> List<T> getMagicable()的第一个大小写相同的类型声明。在返回的类型中使用通配符是一个非常糟糕的主意,因为您强制类用户在其 List 变量声明中使用通配符。

最新更新