【问题标题】:Java Collections using wildcard使用通配符的 Java 集合
【发布时间】:2008-09-16 06:02:43
【问题描述】:
public static void main(String[] args) {

    List<? extends Object> mylist = new ArrayList<Object>();

    mylist.add("Java"); // compile error

}

上面的代码不允许你向列表中添加元素,通配符只能用作方法中的签名,同样不能用于添加,而只能用于访问。 在这种情况下,上面的目的是什么??

【问题讨论】:

    标签: java


    【解决方案1】:

    假设你有一个接口和两个类:

    interface IResult {}
    class AResult implements IResult {}
    class BResult implements IResult {}
    

    然后你的类会返回一个列表作为结果:

    interface ITest<T extends IResult> {
      List<T> getResult();
    }
    
    class ATest implements ITest<AResult> {
      // look, overridden!
      List<AResult> getResult();
    }
    
    class BTest implements ITest<BResult> {
      // overridden again!
      List<BResult> getResult();
    }
    

    当您需要“协变返回”但您返回集合而不是您自己的对象时,这是一个很好的解决方案。最大的优点是,在独立于 ITest 接口使用 ATest 和 BTest 时,您不必强制转换对象。但是,当使用 ITest 接口时,您无法向返回的列表添加任何内容 - 因为您无法确定列表真正包含哪些对象类型!如果允许,您将能够将 BResult 添加到 List(返回为 List extends T>),这没有任何意义。

    所以你必须记住这个:列表 extends X> 定义了一个可以轻松覆盖的列表,但它是只读的。

    【讨论】:

      【解决方案2】:

      Joshua Bloch 在他的著作《Effective Java》(第二版)中解释了他所谓的使用泛型的生产者/消费者原则。 Josh 的解释应该告诉您为什么您的示例不起作用(编译)...

      第 5 章(泛型)可在此处免费获取:http://java.sun.com/docs/books/effective/generics.pdf

      有关本书(和作者)的更多信息,请访问:http://java.sun.com/docs/books/effective/

      【讨论】:

        【解决方案3】:

        对于使用通配符的 java 泛型,您可以使用上述声明,前提是您只打算从中读取。

        不允许添加/写入它,因为所有泛型类型必须在编译时剥离,并且在编译时编译器无法知道 List 只是字符串,(它可以是任何对象包括字符串!)

        但是,您可以从中读取,因为它们至少是对象。在 Java 集合中不允许混合不同的类型,以保持内容简洁易懂,这有助于确保这一点。

        【讨论】:

        • 不,这是不对的。至少从第二段的解释来看是题外话。这与类型擦除无关。
        • 第一段也不对。您可以从问题中声明的列表中删除或添加 null,如果通配符是使用超级绑定定义的,您还可以添加(非 null)引用。
        【解决方案4】:

        有界通配符类型的关键在于它们在方法签名中的使用以提高 API 的灵活性。例如,如果您实现了一个通用的Stack&lt;E&gt;,您可以提供一种方法来将许多元素推送到堆栈,如下所示:

        public void pushAll(Iterable<? extends E> elements) {
            for(E element : elements){
               push(e);
            }
        }
        

        与没有通配符的pushAll(Iterable&lt;E&gt; elements) 签名相比,它的优点是它允许将E 的子类型集合传递给方法——通常这是不允许的,因为Iterable&lt;String&gt; 有点违反直觉,而不是Iterable&lt;Object&gt; 的子类。

        【讨论】:

          【解决方案5】:

          这行得通:

          List<? super Object> mylist = new ArrayList<Object>();
          mylist.add("Java"); // no compile error
          

          来自 O'Reilly 的 Java Generics

          Get 和 Put 原则:当你只获取结构体的值时使用扩展通配符,当你只将值放入结构体时使用超级通配符,不要使用既获取又放置的通配符。

          【讨论】:

          • 我想你的意思是List&lt;? super String&gt;
          • 没有。我从上面复制了示例并将“扩展”替换为“超级”。就是这样!
          • PECS = 生产者扩展,消费者超级
          • 我们可以在这个列表中添加任何东西。它有效,但我看不到它的用途。
          【解决方案6】:

          List&lt;? extends Object&gt;,与List&lt;?&gt; 相同,实现了泛化所有类型List&lt;String&gt;List&lt;Number&gt;List&lt;Object&gt; 等的目的(因此所有类型都具有适当类型代替@ 987654327@)。所有这些类型的值都可以分配给List&lt;?&gt; 类型的变量(这与List&lt;Object&gt; 不同!)。

          通常,您不能将字符串添加到此类列表中。但是,您可以从列表中读取Object,并且可以将null 添加到其中。您还可以计算列表的长度等。这些操作保证适用于每种类型。

          有关通配符的详细介绍,请参阅论文Adding Wildcards to the Java Programming Language。这是一篇学术论文,但仍然很容易获得。

          【讨论】:

            【解决方案7】:

            Java 泛型:集合中的通配符

            1. 扩展
            2. 超级
            3. ?

            今天我将向您解释通配符的用处。理解这个概念有点困难

            现在假设你有一个抽象类,并且你有一个叫做paintObject()的抽象方法。

            Now you want to use different type of collection in every child class.
            

            下面是 AbstractMain 方法。

            我们为这个 Abstract Main 方法采取的步骤

            1.我们已经创建了抽象类

            2. 在参数中我们定义了 T(你可以使用任何字符) --在这种情况下,无论哪个类实现了这个方法,它都可以使用任何类型的类。 前任。类可以实现类似的方法 public void paintObject(ArrayList object) 或 public void paintObject(HashSet object)

            3. 而且我们还使用了 E extends MainColorTO -- 在这种情况下 E 扩展 MainColorTo -- 这显然意味着你想使用的任何类都必须是 MainColorTo 的子类

            4. 我们定义了一个抽象方法叫做paintObject(T object,E objectTO) --现在,无论哪个类是实现方法,该方法都可以在第一个参数上使用任何类,第二个参数该方法必须使用 MainColorTO 的类型

            public abstract class AbstractMain<T,E extends MainColorTO> {
                  public abstract void paintObject(T Object,E TO);  
            }
            

            现在我们将扩展上面的抽象类并在下面的类上实现方法 例如。

            public class MainColorTO {  
                 public void paintColor(){
                       System.out.println("Paint Color........");
                 } 
              }
            
            public class RedTO extends MainColorTO {
               @Override
               public void paintColor() {
               System.out.println("RedTO......");
             }
            }
            public class WhiteTO extends MainColorTO {
               @Override
               public void paintColor() {
                 System.out.println("White TO......");
               }
             }
            

            现在我们举两个例子。

            1.PaintHome.java

            public class PaintHome extends AbstractMain<ArrayList, RedTO> {
                @Override
                public void paintObject(ArrayList arrayList,RedTO red) {
                    System.out.println(arrayList);
            
                }
             }
            

            现在在上面的 PaintHome.java 中,您可以检查我们在第一个参数中使用了 ArrayList(因为我们可以采用任何类),在第二个参数中我们使用了 RedTO(这是扩展 MainColorTO)

            2.PaintCar.java

            public class PaintCar extends AbstractMain<HashSet, WhiteTO>{
                @Override
                public void paintObject(HashSet Object,WhiteTO white) {
                    System.out.println(Object);
            
                }
             }
            

            现在在上面的 PaintCar.java 中,您可以检查我们在第一个参数中使用了 HashSet(因为我们可以采用任何类),在第二个参数中我们使用了 WhiteTO(扩展 MainColorTO)

            要记住的要点 您不能在类级别使用 super 关键字,您只能在类级别定义中使用 extends 关键字

            public abstract class AbstractMain<P,E super MainColorTO> {
            
                public abstract void paintObject(P Object,E TO);
            
            }
            

            上面的代码会给你编译错误。

            【讨论】:

              猜你喜欢
              • 2011-11-13
              • 2011-09-11
              • 2022-12-12
              • 2017-04-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多