【问题标题】:Difference between using wildcards and declaring generic type in abstract method in JavaJava 抽象方法中使用通配符和声明泛型类型的区别
【发布时间】:2018-02-20 15:44:51
【问题描述】:

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

    我有警告信息:

    类型安全:来自 MagicanService 类型的 getMagicable() 的返回类型 List 需要未经检查的转换才能符合来自 MagicableService 类型的 List

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

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

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

    类型不匹配:无法从 List 扩展为 List

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

【问题讨论】:

  • List&lt;Witch&gt; witches = getMagicables(Witch.class); 是可行的 - 1. (Class&lt;T&gt; type) .... type.cast(obj) ....
  • #1 不太可能是任何人都会使用的一种方法。由于T 的类型参数由调用该方法的代码给出,并且由于我们无法创建T 的新实例,因此该方法唯一可以在没有某种未经检查的强制转换的情况下返回一个空列表.因此,为什么例如Collections.emptyList 和类似的地方是唯一可以找到这种方法的地方。

标签: java generics inheritance abstract


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:
    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&lt;Witch&gt;

    这里 Witch 已替换 T,因此 abstract List&lt;T&gt; getMagicables1(); 在其具体类中更改为 List&lt;Witch&gt; getMagicables1()

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

    3) List&lt;Magicable&gt;List&lt;Witch&gt; 是不同的,即使 Witch implements Magicable。实现见getMagicables3 .

    【讨论】:

      【解决方案3】:

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

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

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

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

      【讨论】:

        【解决方案4】:
        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. 第二种情况

        编译错误是正常的,因为方法签名中的&lt;? extends Magicable&gt; 意味着从您可以将这些元素视为Magicable 的那一刻起,您就不关心列表中的内容。打电话时

            List<T> list = getMagicables();
        

        你想在不知不觉中处理类型 T。换句话说,有 3 个用例:T 是 Magicable(OK)、T 是 Magician(错误,因为 getMagicables 可能会返回 Witch 列表)和 T 是 Witch(也错误)。

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

        因为List&lt;Magician&gt;List&lt;? extends Magicable&gt; 的子类型,但不是List&lt;Magicable&gt; 的子类型。这对于方法的参数很有用。

            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
        

        【讨论】:

          猜你喜欢
          • 2020-11-02
          • 2012-06-12
          • 1970-01-01
          • 2013-02-15
          • 1970-01-01
          • 2014-03-08
          • 2010-10-23
          • 2012-12-10
          • 1970-01-01
          相关资源
          最近更新 更多