在Java中创建一个存储通用类型的数组

40

假设我需要创建一个数组,其中存储整数的ArrayList,并且数组大小为10。

以下代码将完成此操作:


ArrayList<Integer>[] pl2 = new ArrayList[10]; 

问题1:

我认为更合适的代码应该是:

ArrayList<Integer>[] pl2 = new ArrayList<Integer>[10];    

为什么这不起作用?

问题2:

以下两者都能编译:

  1. ArrayList<Integer>[] pl2 = new ArrayList[10];
  2. ArrayList[] pl3 = new ArrayList[10];

pl2pl3 的引用声明而言,有何区别?


9
如果您想要类型安全性,请使用List<List<Integer>>。数组和泛型不太兼容。 - JB Nizet
当你已经知道大小时,为什么需要使用ArrayList。一个简单的数组可以很好地存储十个整数。 - Neo
@PaulBellora:因为投票关闭重复问题的人数不够(甚至包括;-)) - Joachim Sauer
@PaulBellora:听起来我们需要(找到?)一组高质量的答案来标记为重复。我现在太累了,无法验证链接问题的错误性,但它很可能是真的。 - Joachim Sauer
@JoachimSauer 好的,以下是一些重复的问题(按时间顺序):[1] [2] [3] [4] [5] [6] 当然,我最喜欢的是因为我回答了它:http://stackoverflow.com/questions/15957325 投票重新打开,以便我们可以选择更好的重复问题。 - Paul Bellora
@JoachimSauer和Paul,你们可以随时使用“其他”标记,并向mod解释你的原因...而不是经历重新打开然后关闭为重复项的整个过程(这似乎更加繁琐且易碎)。 - Jesse
11个回答

16

通用信息仅在编译时重要,它告诉编译器可以将哪种类型放入数组中,在运行时,所有通用信息都会被删除,因此重要的是如何声明通用类型。

引用自《Java编程思想》:

  

不能精确地说您不能创建通用类型的数组。 是的,编译器不允许您实例化通用类型的数组。但是,它将允许您创建对这种数组的引用。 例如:

List<String>[] ls; 

这可以通过编译器而不会出现错误。虽然您无法创建一个实际持有泛型的数组对象,但您可以创建一个非泛型类型的数组并将其强制转换:

//: arrays/ArrayOfGenerics.java 
// It is possible to create arrays of generics. 
import java.util.*; 

public class ArrayOfGenerics { 
    @SuppressWarnings("unchecked") 
    public static void main(String[] args) { 
        List<String>[] ls; 
        List[] la = new List[10]; 
        ls = (List<String>[])la; // "Unchecked" warning 
        ls[0] = new ArrayList<String>(); 
        // Compile-time checking produces an error: 
        //! ls[1] = new ArrayList<Integer>(); 

        // The problem: List<String> is a subtype of Object 
        Object[] objects = ls; // So assignment is OK 
        // Compiles and runs without complaint: 
        objects[1] = new ArrayList<Integer>(); 

        // However, if your needs are straightforward it is 
        // possible to create an array of generics, albeit 
        // with an "unchecked" warning: 
        List<BerylliumSphere>[] spheres = 
           (List<BerylliumSphere>[])new List[10]; 
        for(int i = 0; i < spheres.length; i++) 
           spheres[i] = new ArrayList<BerylliumSphere>(); 
    } 
}

一旦您有一个对List[]的引用,您可以看到它具有一些编译时检查。问题在于数组是协变的,因此List[]也是Object[],您可以使用它将ArrayList分配到您的数组中,而无需在编译时或运行时出现错误。

如果您知道您不会进行向上转型且您的需求相对简单,则可以创建一个泛型数组,这将提供基本的编译时类型检查。但是,与泛型容器相比,泛型数组几乎总是更好的选择。


5
您能否更详细地解释一下?我理解“erasure”的概念,但在这个语境下不太清楚。 - Geek
1
那又怎样呢?这并没有解释为什么那段代码无法编译。编译器可以使用或者简单地忽略那些信息,并且顺利编译。 - Bakuriu
1
数组和泛型组合使用不是很好。我们不能实例化参数化类型的数组。擦除会移除参数类型信息,而数组必须知道它们所保存的确切类型,以强制执行类型安全。——引自《Java编程思想》 - ltebean

13

问题1:

基本上,这在Java语言中是被禁止的。这在Java泛型限制的语言规范中有所涵盖。

当你使用

ArrayList<Integer>[] pl2 = new ArrayList[10];    // warning

您会收到编译器警告,因为以下示例将编译(每行代码都会生成警告):

ArrayList wrongRawArrayList = new ArrayList();      // warning
wrongRawArrayList.add("string1");                   // warning 
wrongRawArrayList.add("string2");                   // warning  

pl2[0] = wrongRawArrayList;                         // warning 

但是现在你的数组,原本应该包含Integer类型的ArrayList,却包含了完全错误的String对象的ArrayList

问题2:

正如先前回答的那样,声明p12为你提供了编译时检查,并使您无需在从ArrayList中获取项目时使用转换。

稍微修改之前的示例:

ArrayList<Integer>[] pl2 = new ArrayList[10];                // warning 

ArrayList<String> wrongArrayList = new ArrayList<String>();  // OK!
wrongArrayList.add("string1");                               // OK! 
wrongArrayList.add("string2");                               // OK!

pl2[0] = wrongArrayList;                                     // ERROR

现在,由于您正在使用泛型,这将无法编译。

但是,如果您使用

ArrayList[] pl2 = new ArrayList[10]; 

您将获得与第一个示例相同的结果。


你是不是想说 pl2 = wrongArrayList;?(即没有 [0] - DerMike
1
@DerMike 不,我确实是指 pl2[0]pl2ArrayList 数组,所以 pl2[0] 是对 ArrayList 的引用。 - Vladimir
1
是的,抱歉,你是正确的。 - DerMike

4

数组是协变的。这意味着它们在运行时保留其元素的类型。Java的泛型不是。它们使用类型擦除来掩盖正在进行的隐式转换。理解这一点非常重要。

您需要使用Array.newInstance()

此外,数组携带有关其组件类型的运行时类型信息,即所包含元素的类型。当将元素存储在数组中时,将使用有关组件类型的运行时类型信息,以确保不会插入任何“外来”元素。

有关详细信息,请查看此处


2

让我们先从问题2开始,然后再回到问题1:

问题2:

> ArrayList[] pl2 = new ArrayList[10]; ArrayList[] pl3 = new ArrayList[10];

就引用声明而言,p12和p13有什么区别?

在pl2中确保更好的类型安全性,而不是p13。

如果我写pl2:

pl2[0]=new ArrayList<String>();

它会给我一个编译器错误,指出“无法将 ArrayList<String> 转换为 ArrayList<Integer>”。

因此,它确保了编译时的安全性。

但是,如果我写了这样的代码:

pl3[0]=new ArrayList<String>();
pl3[1]=new ArrayList<Integer>();

如果从p13提取数据时发生不安全类型转换,那么它不会抛出任何错误,责任在于开发人员编写和检查代码以避免运行时发生不安全类型转换。

问题1:

这可能就是泛型的工作方式。在主数组初始化期间ArrayList<Integer>[] pl2 = new ArrayList[10],左侧ArrayList<Integer>[] pl2只有在你初始化索引位置上的ArrayList对象时才能确保类型安全:

pl2[0]=new ArrayList<Integer>();

右侧的主数组声明= new ArrayList[10]只是确保索引位置将保存ArrayList类型的项目。还要查看有关类型擦除概念的信息,可参考类型擦除

2
这不起作用,因为 泛型类不属于可具体化类型

有关数组创建表达式的JLS指出:

如果[class type]不表示可具体化类型(§4.7),则是编译时错误。否则,[class type]可以命名任何命名引用类型,甚至是抽象类类型(§8.1.1.1)或接口类型(§9)。

上述规则意味着数组创建表达式中的元素类型不能是参数化类型,除了无限制通配符之外的其他情况

可具体化类型的定义是:
由于在编译期间擦除了某些类型信息,因此并非所有类型都可在运行时获得。完全在运行时可用的类型称为可具体化类型。
当且仅当以下条件之一成立时,类型是可具体化的:
It refers to a non-generic class or interface type declaration.

It is a parameterized type in which all type arguments are unbounded wildcards (§4.5.1).

It is a raw type (§4.8).

It is a primitive type (§4.2).

It is an array type (§10.1) whose element type is reifiable.

It is a nested type where, for each type T separated by a ".", T itself is reifiable.

For example, if a generic class X<T> has a generic member class Y<U>, then the type X<?>.Y<?> is reifiable because X<?> is reifiable and Y<?> is reifiable. The type X<?>.Y<Object> is not reifiable because Y<Object> is not reifiable.

1

问题1。

嗯,这不是正确的语法。因此它不起作用。

问题2。

ArrayList<Integer>[] pl2 = new ArrayList[10];
ArrayList[] pl3 = new ArrayList[10];

由于pl2在编译时定义了通用类型,编译器将知道pl2只允许包含整数,如果您尝试分配非整数的内容,则将发出警告并且编译将失败。

在pl3中,由于没有通用类型,您可以将任何类型的对象分配给列表。


0
 ArrayList<Integer>[] pl2 = new ArrayList<Integer>[10];

这意味着当您从ArrayList检索数据时,无需进行强制转换 例如,在正常情况下

 ArrayList[] pl2 = new ArrayList[10];
 pl2.put(new Integer(10));
 Integer i = p12.get(0);    // this is wrong
 Integer i = (Integer)p12.get(0);    // this is true with casting

但是

 ArrayList<Integer>[] pl2 = new ArrayList<Integer>[10];   
 pl2.put(new Integer(10));
 Integer i = p12.get(0);    // this is true no need for casting

很抱歉,您的第一个语法无法编译。 - Geek
你漏掉了数组的ArrayList部分。OP不想创建任何整数数组。 - Nicktar

0

问题1

您无法创建参数化类型的数组

问题2

 ArrayList<Integer>[] pl2 = new ArrayList[10];

这意味着您告诉编译器,您将创建一个数组,该数组将存储整数的数组列表。您的数组列表将仅包含Integer对象。这就是泛型的作用。泛型使您的代码更安全可靠。如果您确定列表只应包含整数对象,则应始终使用此选项。

但是当您说

 ArrayList[] pl3 = new ArrayList[10];

这意味着ArrayList可以存储任何对象类型,如字符串、整数、自定义对象等。


0

泛型问题通常由编译器默认作为警告处理。

在编译后,由于类型擦除,它们都变成了ArrayList[] pl2 = new ArrayList[10],但编译器会警告你这并不好。

Java已经增加了泛型,为了向后兼容,你可以使用泛型与非泛型进行交换。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接