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<Whatever>();
【讨论】:
【参考方案2】:ArrayList<Integer> a = new ArrayList<Number>();
不起作用,因为 Number 是 Integer 的超类这一事实并不意味着 List<Number>
是 List<Integer>
的超类。泛型在编译过程中被移除,并且在运行时不存在,因此无法实现集合的父子关系:元素类型的信息被简单地移除。
ArrayList<? extends Object> a1 = new ArrayList<Object>();
a1.add(3);
我无法解释为什么它不起作用。这真的很奇怪,但这是事实。真正的语法<? extends Object>
主要用于方法的返回值。即使在这个例子中Object o = a1.get(0)
也是有效的。
ArrayList<?> a = new ArrayList<?>()
这不起作用,因为您无法实例化未知类型的列表...
【讨论】:
"真正的语法<? extends Object>
主要用于方法的返回值。"方法几乎不应该返回带有通配符类型参数的泛型类型。通配符类型参数主要用于方法的参数。【参考方案3】:
您不能将List<Number>
分配给List<Integer>
类型的引用,因为List<Number>
允许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<Integer>
类型保证它包含的任何内容都是Integer
。这就是为什么您可以在不强制转换的情况下从中获得Integer
的原因。如您所见,如果编译器允许将另一个类型的 List
(例如 Number
)分配给 List<Integer>
,则该保证将被破坏。
将List<Integer>
分配给List<?>
或List<? extends Number>
等类型的引用是合法的,因为?
表示“给定类型的某些未知子类型”(其中类型为Object
在?
和 Number
在? extends Number
的情况下)。
由于?
表示您不知道List
将接受什么特定类型的对象,因此向其中添加除null
之外的任何内容都是不合法的。但是,您可以从中检索任何对象,这是使用? extends X
有界通配符类型的目的。请注意,? super X
有界通配符类型正好相反……List<? super Integer>
是“一些未知类型的列表,至少是Integer
的超类型”。虽然您不知道List
的确切类型(可能是List<Integer>
、List<Number>
、List<Object>
),但您确实知道无论它是什么类型,都可以添加Integer
。
最后,new ArrayList<?>()
是不合法的,因为当您创建像ArrayList
这样的参数化类的实例时,您必须提供一个特定 类型参数。你真的可以在你的例子中使用任何东西(Object
,Foo
,没关系)因为你永远无法添加任何东西,除了null
,因为你直接将它分配给@987654357 @参考。
【讨论】:
【参考方案4】:你有奇怪的期望。如果您给出了导致您找到它们的一系列论据,我们可能会发现其中的缺陷。事实上,我只能对泛型做一个简短的介绍,希望能触及您可能误解的要点。
ArrayList<? extends Object>
是一个 ArrayList,其类型参数已知为 Object
或其子类型。 (是的,类型边界中的扩展具有直接子类以外的含义)。由于只有引用类型可以是类型参数,所以这实际上等价于ArrayList<?>
。
也就是说,您可以将ArrayList<String>
放入使用ArrayList<?>
声明的变量中。这就是为什么a1.add(3)
是编译时错误。 a1
的声明类型允许 a1
成为 ArrayList<String>
,不能添加 Integer
。
显然,ArrayList<?>
不是很有用,因为您只能在其中插入 null。这可能就是 Java Spec forbids it 的原因:
如果有任何一个,这是一个编译时错误 类中使用的类型参数 实例创建表达式是 通配符类型参数
ArrayList<ArrayList<?>>
相比之下是一种功能数据类型。您可以将各种 ArrayLists 添加到其中,并检索它们。并且由于ArrayList<?>
仅包含但不是通配符类型,因此上述规则不适用。
【讨论】:
【参考方案5】:关键在于引用和实例之间的区别,以及引用可以承诺什么以及实例真正可以做什么。
ArrayList<A> a = new ArrayList<A>();
这里的a
是对特定类型实例的引用——恰好是A
s 的数组列表。更明确地说,a
是对接受A
s 并产生A
s 的数组列表的引用。 new ArrayList<A>()
是A
s 的数组列表的一个实例,即接受A
s 并产生A
s 的数组列表。
ArrayList<Integer> a = new ArrayList<Number>();
这里,a
是对Integers
的数组列表的引用,即可以接受Integer
s 并将产生Integer
s 的数组列表。它不能指向Number
s 的数组列表。 Number
s 的数组列表不能满足ArrayList<Integer> a
的所有承诺(即Number
s 的数组列表可能会产生不是Integer
s 的对象,即使它当时是空的)。
ArrayList<Number> a = new ArrayList<Integer>();
这里,a
的声明表明a
将完全引用Number
s 的数组列表,也就是说,完全是一个将接受Number
s 并将产生Number
s 的数组列表。它不能指向Integer
s 的数组列表,因为a
的类型声明说a
可以接受任何Number
,但Integer
s 的数组列表不能只接受任何Number
,它只能接受Integer
s。
ArrayList<? extends Object> a= new ArrayList<Object>();
这里的a
是对类型系列的(通用)引用,而不是对特定类型的引用。它可以指向属于该家族成员的任何列表。然而,这个很好的灵活引用的权衡是,如果它是一个特定于类型的引用(例如非泛型),他们不能保证它可以提供的所有功能。在这种情况下,a
是对将产生Object
s 的数组列表的引用。 但是,与特定类型的列表引用不同,这个a
引用不能接受任何Object
。 (即并非a
可以指向的类型家族的每个成员都可以接受任何Object
,例如Integer
s 的数组列表只能接受Integer
s。)
ArrayList<? super Integer> a = new ArrayList<Number>();
同样,a
是对一系列类型(而不是单个特定类型)的引用。由于通配符使用super
,这个列表引用可以接受Integer
s,但不能产生Integer
s。换句话说,我们知道a
可以指向的类型家族的任何成员都可以接受Integer
。但是,并非该家庭的每个成员都能产生Integer
s。
PECS - Producer extends
, Consumer super
- 这个助记符帮助您记住使用 extends
意味着泛型类型可以产生特定类型(但是不能接受)。使用super
意味着泛型类型可以使用(接受)特定类型(但不能产生它)。
ArrayList<ArrayList<?>> a
一个数组列表,其中包含对作为数组列表类型家族成员的任何列表的引用。
= new ArrayList<ArrayList<?>>(); // correct
数组列表的一个实例,它包含对作为数组列表类型家族成员的任何列表的引用。
ArrayList<?> a
对任何数组列表(数组列表类型家族的成员)的引用。
= new ArrayList<?>()
ArrayList<?>
指的是数组列表类型家族中的任何类型,但您只能实例化特定类型。
另见How can I add to List<? extends Number> data structures?
【讨论】:
【参考方案6】:认为?
表示"unknown"
。因此,"ArrayList<? extends Object>"
是说“一种(或只要它)扩展对象的未知类型”。因此,有必要说,arrayList.add(3)
将把你知道的东西放入未知中。即“忘记”。
【讨论】:
以上是关于Java 泛型 - ArrayList 初始化的主要内容,如果未能解决你的问题,请参考以下文章
请问,Java中,泛型数组的数组怎么初始化?(就是ArrayList数组)