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

时间:2017-09-12 08:51:32

标签: java generics inheritance abstract

我试图理解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() {..}
    

    我有警告信息:

      

    类型安全:返回类型列表&lt; Magican&gt;对于来自MagicanService类型的getMagicable()需要未经检查的转换以符合List&lt; Magicable&gt;来自MagicableService类型。

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

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

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

      

    类型不匹配:无法转换为List&lt; capture#2-of?扩展Magicable&gt;列出&lt; T&gt;

  3. 第三种情况导致上述两个代码中的编译错误。在我的情况下,我不认为它是否正确解决。

4 个答案:

答案 0 :(得分:2)

  
      
  1. 第一个案例
  2.   

只需声明您的方法:

    @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. 第二个案例
  2.   

编译错误是正常的,因为方法签名中的<? extends Magicable>意味着您不必关心列表中的内容,因为您可以将这些元素视为Magicable。在打电话时

    List<T> list = getMagicables();

你想在不知情的情况下照顾T型。换句话说,有3个用例:T是Magicable(OK),T是魔术师(错误因为getMagicables可能返回Witch列表)而T是Witch(也错了)。

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

因为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

答案 1 :(得分:1)

对于问题的一部分,你确实告诉我们,方法/ * 3 * /就足够了,你不需要代码那部分的泛型。但是你需要尊重可替代性:

您在#1中收到错误,因为子类型方法限制了返回类型的范围:MagicanMagicable但反之亦然。子类型中允许超级类型Magicable。子类型方法必须可替代超类型方法,在您的示例中不是这种情况。

#2中的错误是由于通配符?的性质造成的:? extends MagicableT extends Magicable不一定是同一类型。如果在类范围中声明T,例如class Magican<T> implements Magicable<T>(当然,在这种情况下你的接口需要声明T)你类型中出现的所有T都会引用同一个类。

答案 2 :(得分:1)

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)即使List<Magicable>List<Witch>Witch implements Magicable也不同。实施显示在getMagicables3

答案 3 :(得分:1)

在第一种情况下,抽象方法声明为使用泛型类型<T extends Magicable>,这意味着您的方法可以返回Magicable列表或任何实现它的类型。在您的实现中,您将返回具体类型Magican,这是一个Magicable。您可以放心地忽略该警告并添加@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变量声明中使用通配符。