Java 泛型 - ArrayList 初始化

Posted

技术标签:

【中文标题】Java 泛型 - ArrayList 初始化【英文标题】:Java generics - ArrayList initialization 【发布时间】:2011-05-28 10:27:33 【问题描述】:

众所周知,arraylist init。应该是这样的

ArrayList<A> a = new ArrayList<A>();
ArrayList<Integer> a = new ArrayList<Number>(); // compile-time error

那么,为什么 java 允许这些?

1. ArrayList<? extends Object> a1 = new ArrayList<Object>();
2. ArrayList<?> a2 = new ArrayList<Integer>();

那么,如果它们是正确的,为什么不允许这些?

1. a1.add(3);
2. a2.add(3);

编译器消息是:ArrayList 类型中的方法 add(int, capture#1-of ? extends Object) 不适用于参数 (int)

更一般的

  1. a1.add(null e);
  2. a2.add(? e);

我读到了这个,但很高兴收到你的来信。谢谢

另一个有趣的地方是:

 ArrayList<ArrayList<?>> a = new ArrayList<ArrayList<?>>(); // correct
 ArrayList<?> a = new ArrayList<?>(); // wrong. I know it's reason but I have some 
question in my mind that mentioned above 

【问题讨论】:

你能做类似a1.add(new Integer(3));的事情吗? @Gabe 你只能做 a1.add(null)。 a1.add(3) 应该转换为 Integer 对象,但编译器只看到 2.add(?e) 并且这里只允许 add(null) How can I add to List<? extends Number> data structures?的可能重复 【参考方案1】:

这在很大程度上与多态性有关。当你分配

X = new Y();

X 可能比 Y 更不“具体”,但反之亦然。 X 只是你用来访问 Y 的句柄,Y 是真正实例化的东西,

您在此处收到错误,因为 Integer 是 Number,但 Number 不是 Integer。

ArrayList<Integer> a = new ArrayList<Number>(); // compile-time error

因此,您调用的 X 的任何方法都必须对 Y 有效。由于 X 更普遍,它可能共享一些,但不是所有 Y 的方法。尽管如此,任何给定的参数都必须对 Y 有效。

在您使用 add 的示例中,int(小 i)不是有效的对象或整数。

ArrayList<?> a = new ArrayList<?>();

这不好,因为您实际上无法实例化包含 ? 的数组列表。你可以这样声明一个,然后在new ArrayList&lt;Whatever&gt;();

【讨论】:

【参考方案2】:
ArrayList<Integer> a = new ArrayList<Number>(); 

不起作用,因为 Number 是 Integer 的超类这一事实并不意味着 List&lt;Number&gt;List&lt;Integer&gt; 的超类。泛型在编译过程中被移除,并且在运行时不存在,因此无法实现集合的父子关系:元素类型的信息被简单地移除。

ArrayList<? extends Object> a1 = new ArrayList<Object>();
a1.add(3);

我无法解释为什么它不起作用。这真的很奇怪,但这是事实。真正的语法&lt;? extends Object&gt; 主要用于方法的返回值。即使在这个例子中Object o = a1.get(0) 也是有效的。

ArrayList<?> a = new ArrayList<?>()

这不起作用,因为您无法实例化未知类型的列表...

【讨论】:

"真正的语法&lt;? extends Object&gt;主要用于方法的返回值。"方法几乎不应该返回带有通配符类型参数的泛型类型。通配符类型参数主要用于方法的参数。【参考方案3】:

您不能将List&lt;Number&gt; 分配给List&lt;Integer&gt; 类型的引用,因为List&lt;Number&gt; 允许Integer 以外的数字类型。如果您被允许这样做,则允许执行以下操作:

List<Number> numbers = new ArrayList<Number>();
numbers.add(1.1); // add a double
List<Integer> ints = numbers;
Integer fail = ints.get(0); // ClassCastException!

List&lt;Integer&gt; 类型保证它包含的任何内容都是Integer。这就是为什么您可以在不强制转换的情况下从中获得Integer 的原因。如您所见,如果编译器允许将另一个类型的 List(例如 Number)分配给 List&lt;Integer&gt;,则该保证将被破坏。

List&lt;Integer&gt; 分配给List&lt;?&gt;List&lt;? extends Number&gt; 等类型的引用是合法的,因为? 表示“给定类型的某些未知子类型”(其中类型为Object?Number? extends Number 的情况下)。

由于? 表示您不知道List 将接受什么特定类型的对象,因此向其中添加除null 之外的任何内容都是不合法的。但是,您可以从中检索任何对象,这是使用? extends X 有界通配符类型的目的。请注意,? super X 有界通配符类型正好相反……List&lt;? super Integer&gt; 是“一些未知类型的列表,至少是Integer 的超类型”。虽然您不知道List 的确切类型(可能是List&lt;Integer&gt;List&lt;Number&gt;List&lt;Object&gt;),但您确实知道无论它是什么类型,都可以添加Integer

最后,new ArrayList&lt;?&gt;() 是不合法的,因为当您创建像ArrayList 这样的参数化类的实例时,您必须提供一个特定 类型参数。你真的可以在你的例子中使用任何东西(ObjectFoo,没关系)因为你永远无法添加任何东西,除了null,因为你直接将它分配给@987654357 @参考。

【讨论】:

【参考方案4】:

你有奇怪的期望。如果您给出了导致您找到它们的一系列论据,我们可能会发现其中的缺陷。事实上,我只能对泛型做一个简短的介绍,希望能触及您可能误解的要点。

ArrayList&lt;? extends Object&gt; 是一个 ArrayList,其类型参数已知为 Object 或其子类型。 (是的,类型边界中的扩展具有直接子类以外的含义)。由于只有引用类型可以是类型参数,所以这实际上等价于ArrayList&lt;?&gt;

也就是说,您可以将ArrayList&lt;String&gt; 放入使用ArrayList&lt;?&gt; 声明的变量中。这就是为什么a1.add(3) 是编译时错误。 a1 的声明类型允许 a1 成为 ArrayList&lt;String&gt;,不能添加 Integer

显然,ArrayList&lt;?&gt; 不是很有用,因为您只能在其中插入 null。这可能就是 Java Spec forbids it 的原因:

如果有任何一个,这是一个编译时错误 类中使用的类型参数 实例创建表达式是 通配符类型参数

ArrayList&lt;ArrayList&lt;?&gt;&gt; 相比之下是一种功能数据类型。您可以将各种 ArrayLists 添加到其中,并检索它们。并且由于ArrayList&lt;?&gt;包含不是通配符类型,因此上述规则不适用。

【讨论】:

【参考方案5】:

关键在于引用和实例之间的区别,以及引用可以承诺什么以及实例真正可以做什么。

ArrayList<A> a = new ArrayList<A>();

这里的a 是对特定类型实例的引用——恰好是As 的数组列表。更明确地说,a 是对接受As 并产生As 的数组列表的引用。 new ArrayList&lt;A&gt;()As 的数组列表的一个实例,即接受As 并产生As 的数组列表。

ArrayList<Integer> a = new ArrayList<Number>(); 

这里,a 是对Integers 的数组列表的引用,即可以接受Integers 并将产生Integers 的数组列表。它不能指向Numbers 的数组列表。 Numbers 的数组列表不能满足ArrayList&lt;Integer&gt; a 的所有承诺(即Numbers 的数组列表可能会产生不是Integers 的对象,即使它当时是空的)。

ArrayList<Number> a = new ArrayList<Integer>(); 

这里,a 的声明表明a 将完全引用Numbers 的数组列表,也就是说,完全是一个将接受Numbers 并将产生Numbers 的数组列表。它不能指向Integers 的数组列表,因为a 的类型声明说a 可以接受任何Number,但Integers 的数组列表不能只接受任何Number,它只能接受Integers。

ArrayList<? extends Object> a= new ArrayList<Object>();

这里的a 是对类型系列的(通用)引用,而不是对特定类型的引用。它可以指向属于该家族成员的任何列表。然而,这个很好的灵活引用的权衡是,如果它是一个特定于类型的引用(例如非泛型),他们不能保证它可以提供的所有功能。在这种情况下,a 是对将产生Objects 的数组列表的引用。 但是,与特定类型的列表引用不同,这个a 引用不能接受任何Object。 (即并非a 可以指向的类型家族的每个成员都可以接受任何Object,例如Integers 的数组列表只能接受Integers。)

ArrayList<? super Integer> a = new ArrayList<Number>();

同样,a 是对一系列类型(而不是单个特定类型)的引用。由于通配符使用super,这个列表引用可以接受Integers,但不能产生Integers。换句话说,我们知道a 可以指向的类型家族的任何成员都可以接受Integer。但是,并非该家庭的每个成员都能产生Integers。

PECS - Producer extends, Consumer super - 这个助记符帮助您记住使用 extends 意味着泛型类型可以产生特定类型(但是不能接受)。使用super 意味着泛型类型可以使用(接受)特定类型(但不能产生它)。

ArrayList<ArrayList<?>> a

一个数组列表,其中包含对作为数组列表类型家族成员的任何列表的引用。

= new ArrayList<ArrayList<?>>(); // correct

数组列表的一个实例,它包含对作为数组列表类型家族成员的任何列表的引用。

ArrayList<?> a

对任何数组列表(数组列表类型家族的成员)的引用。

= new ArrayList<?>()

ArrayList&lt;?&gt; 指的是数组列表类型家族中的任何类型,但您只能实例化特定类型。


另见How can I add to List<? extends Number> data structures?

【讨论】:

【参考方案6】:

认为? 表示"unknown"。因此,"ArrayList&lt;? extends Object&gt;" 是说“一种(或只要它)扩展对象的未知类型”。因此,有必要说,arrayList.add(3) 将把你知道的东西放入未知中。即“忘记”。

【讨论】:

以上是关于Java 泛型 - ArrayList 初始化的主要内容,如果未能解决你的问题,请参考以下文章

请问,Java中,泛型数组的数组怎么初始化?(就是ArrayList数组)

如何用Jpype创建HashMap和ArrayList

保存ArrayLists时由泛型引起的Java编译器警告[重复]

在java中将多个项目添加到已经初始化的arraylist

java怎么用一行代码初始化ArrayList

Java 集合类框架的最佳实践有哪些?