我无法在 Java 中创建泛型数组类型的原因是啥?

Posted

技术标签:

【中文标题】我无法在 Java 中创建泛型数组类型的原因是啥?【英文标题】:What's the reason I can't create generic array types in Java?我无法在 Java 中创建泛型数组类型的原因是什么? 【发布时间】:2010-05-28 07:47:50 【问题描述】:

Java 不允许我们做的原因是什么

private T[] elements = new T[initialCapacity];

我可以理解 .NET 不允许我们这样做,因为在 .NET 中,值类型在运行时可以具有不同的大小,但在 Java 中,所有类型的 T 都将是对象引用,因此具有大小相同(如果我错了,请纠正我)。

是什么原因?

【问题讨论】:

你在说什么?您绝对可以在 .NET 中执行此操作。 -- 我在这里试图弄清楚为什么我不能在 Java 中做到这一点。 @BrainSlugs83 - 请添加一些代码示例或教程的链接来显示这一点。 另见 - ***.com/questions/21577493/… @MasterJoe2 OP 问题中的上述代码就是我所指的。它在 C# 中运行良好,但在 Java 中却不行。 - 问题表明它在两者中都不起作用,这是不正确的。 -- 不确定是否有进一步讨论的价值。 [因为这是一个尚未修复的错误。 ](i.stack.imgur.com/hlOJI.jpg) 【参考方案1】:

这是因为 Java 的数组(与泛型不同)在运行时包含有关其组件类型的信息。所以在创建数组的时候一定要知道组件的类型。由于您在运行时不知道T 是什么,因此无法创建数组。

【讨论】:

但是擦除呢?为什么不适用? ArrayList <SomeType>是怎么做到的呢? @Thumbz:你的意思是new ArrayList<SomeType>()?泛型类型在运行时不包含类型参数。创建时不使用类型参数。 new ArrayList<SomeType>()new ArrayList<String>()new ArrayList()生成的代码完全没有区别。 我在询问更多关于 ArrayList<T> 如何与其'private T[] myArray 一起使用的信息。在代码的某个地方,它必须有一个泛型类型 T 的数组,那怎么办? @Thumbz:它没有运行时类型T[] 的数组。它有一个运行时类型Object[] 的数组,或者 1) 源代码包含一个变量Object[](这是最新的 Oracle Java 源代码中的样子);或者 2) 源代码包含T[] 类型的变量,这是一个谎言,但不会因为T 在类范围内被擦除而导致问题。【参考方案2】:

引用:

泛型类型的数组不是 允许,因为它们不健全。这 问题是由于相互作用 Java 数组,不是静态的 声音,但动态检查, 使用泛型,它们是静态的 声音而不是动态检查。 以下是您可以如何利用 漏洞:

class Box<T> 
    final T x;
    Box(T x) 
        this.x = x;
    


class Loophole 
    public static void main(String[] args) 
        Box<String>[] bsa = new Box<String>[3];
        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3); // error not caught by array store check
        String s = bsa[0].x; // BOOM!
    

我们曾提议解决此问题 使用静态安全数组的问题 (又名方差)但被拒绝 为老虎。

--gafter

(我相信是Neal Gafter,但不确定)

在此处查看上下文:http://forums.sun.com/thread.jspa?threadID=457033&forumID=316

【讨论】:

请注意,我将其设为 CW,因为答案不是我的。 这解释了为什么它可能不是类型安全的。但是编译器可能会警告类型安全问题。事实是,它甚至不可能做到,原因几乎与你不能做到new T() 的原因相同。按照设计,Java 中的每个数组都将组件类型(即T.class)存储在其中;因此,您需要在运行时使用 T 的类来创建这样的数组。 您仍然可以使用new Box&lt;?&gt;[n],这有时可能就足够了,尽管它对您的示例没有帮助。 @BartKiers 我不明白...这仍然无法编译(java-8):Box&lt;String&gt;[] bsa = new Box&lt;String&gt;[3]; 我认为 java-8 和更高版本有什么变化吗? @Eugene,不允许使用特定泛型类型的数组,因为它们会导致类型安全性的损失,如示例中所示。在任何版本的 Java 中都不允许这样做。答案开头是“不允许使用泛型类型的数组,因为它们不正确。”【参考方案3】:

如果无法提供一个体面的解决方案,恕我直言,您最终会得到更糟糕的结果。

常见的解决方法如下。

T[] ts = new T[n];

被替换为(假设 T 扩展了 Object 而不是另一个类)

T[] ts = (T[]) new Object[n];

我更喜欢第一个例子,但更多的学术类型似乎更喜欢第二个,或者只是不想考虑它。

为什么不能只使用 Object[] 的大多数示例同样适用于 List 或 Collection(受支持),因此我认为它们是非常糟糕的论点。

注意:这是 Collections 库本身无法在没有警告的情况下编译的原因之一。如果您在没有警告的情况下无法支持此用例,那么泛型模型恕我直言,从根本上破坏了某些东西。

【讨论】:

你必须小心第二个。如果您将以这种方式创建的数组返回给期望 String[] 的人(或者如果您将其存储在可公开访问的类型为 T[] 的字段中,并且有人检索它),那么他们将得到一个 ClassCastException。 我否决了这个答案,因为您的首选示例在 Java 中是不允许的,而您的第二个示例可能会抛出 ClassCastException @JoséRobertoAraújoJúnior 很明显第一个示例需要替换为第二个示例。解释一下为什么第二个示例会抛出 ClassCastException 对您会更有帮助,因为这对每个人来说都不是显而易见的。 @PeterLawrey 我创建了一个自我回答的问题,说明为什么 T[] ts = (T[]) new Object[n]; 是个坏主意:***.com/questions/21577493/… @MarkoTopolnik 我应该得到一枚奖章,因为我回答了你所有的 cmets 来解释我已经说过的同样的事情,唯一与我最初的原因不同的是,我虽然他说 @987654327 @ 是一个有效的例子。我会保留投票,因为他的回答可能会给其他开发人员带来问题和困惑,而且也是题外话。另外,我将停止对此发表评论。【参考方案4】:

这是不可能的原因是Java纯粹在编译器级别实现其泛型,并且为每个类只生成一个类文件。 这称为Type Erasure。

在运行时,编译后的类需要使用相同的字节码来处理它的所有使用。所以,new T[capacity] 完全不知道需要实例化什么类型。

【讨论】:

【参考方案5】:

答案已经给出,但如果你已经有一个 T 的实例,那么你可以这样做:

T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);

希望,我能帮上忙, 费迪265

【讨论】:

这是一个不错的解决方案。但这会得到未经检查的警告(从 Object 转换为 T[])。另一个“较慢”但“无警告”的解决方案是:T[] ts = t.clone(); for (int i=0; i&lt;ts.length; i++) ts[i] = null; 另外,如果我们保留的是T[] t,那么它就是(T[]) Array.newInstance(t.getClass().getComponentType(), length);。我确实花了一些时间来弄清楚getComponentType()。希望这对其他人有所帮助。 @midnite t.clone() 不会返回 T[]。因为t 在这个答案中不是数组。【参考方案6】:

主要原因是Java中的数组是协变的。

有一个很好的概述here。

【讨论】:

我看不出你怎么能支持“new T[5]”,即使是不变的数组。 @DimitrisAndreou 嗯,整个事情是Java 设计中错误的喜剧。这一切都始于数组协方差。然后,一旦有了数组协方差,就可以将String[] 转换为Object 并在其中存储Integer。因此,他们不得不为数组存储添加运行时类型检查 (ArrayStoreException),因为在编译时无法发现该问题。 (否则,Integer 实际上可能会卡在 String[] 中,当您尝试检索它时会出现错误,这将是可怕的。) ... @DimitrisAndreou ... 然后,一旦你用运行时检查代替了更合理的编译时检查,你就会遇到类型擦除(也是一个不幸的设计缺陷——仅用于向后兼容) .类型擦除意味着您不能对泛型类型进行运行时类型检查。因此,为了避免数组存储类型问题,您根本不能拥有泛型数组。如果他们一开始只是简单地使数组保持不变,我们可以只进行编译时类型检查而不会与擦除发生冲突。 ... 我刚刚发现了 cmets 的五分钟编辑时间。 Object 在我的第一条评论中应该是 Object[]【参考方案7】:

我喜欢间接给出的答案 通过Gafter。但是,我认为这是错误的。我稍微改变了 Gafter 的代码。它编译并运行一段时间,然后在 Gafter 预测的地方轰炸

class Box<T> 

    final T x;

    Box(T x) 
        this.x = x;
    


class Loophole 

    public static <T> T[] array(final T... values) 
        return (values);
    

    public static void main(String[] args) 

        Box<String> a = new Box("Hello");
        Box<String> b = new Box("World");
        Box<String> c = new Box("!!!!!!!!!!!");
        Box<String>[] bsa = array(a, b, c);
        System.out.println("I created an array of generics.");

        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3);
        System.out.println("error not caught by array store check");

        try 
            String s = bsa[0].x;
         catch (ClassCastException cause) 
            System.out.println("BOOM!");
            cause.printStackTrace();
        
    

输出是

I created an array of generics.
error not caught by array store check
BOOM!
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at Loophole.main(Box.java:26)

所以在我看来,您可以在 java 中创建泛型数组类型。我是不是误解了这个问题?

【讨论】:

您的示例与我所要求的不同。您所描述的是数组协方差的危险。检查一下(对于 .NET:blogs.msdn.com/b/ericlippert/archive/2007/10/17/…) 希望你从编译器那里得到一个类型安全警告,是吗? 是的,我收到了类型安全警告。是的,我看到我的示例没有响应这个问题。 实际上,由于 a、b、c 的初始化草率,您会收到多个警告。此外,这是众所周知的,并且会影响核心库,例如java.util.Arrays.asList(T...)。如果你为 T 传递任何不可具体化的类型,你会收到一个警告(因为创建的数组的类型比代码假装的更不精确),而且它非常难看。如果该方法的作者得到警告,而不是在使用现场发出警告,那会更好,因为该方法本身是安全的,它不会将数组暴露给用户。跨度> 您没有在此处创建通用数组。编译器为您创建了一个(非泛型)数组。【参考方案8】:

来自Oracle tutorial:

您不能创建参数化类型的数组。例如,以下代码无法编译:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

以下代码说明了将不同类型插入数组时会发生什么:

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

如果你用通用列表尝试同样的事情,就会出现问题:

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

如果允许参数化列表的数组,则前面的代码将无法抛出所需的 ArrayStoreException。

对我来说,这听起来很弱。我认为任何对泛型有足够了解的人都会很好,甚至期望在这种情况下不会抛出 ArrayStoredException。

【讨论】:

这是真的,不能创建泛化数组不是因为类型擦除已经到位......因为类型在编译时被擦除,所以 List 类的新实例化只有在没有它的情况下才有可能泛型类型...因此 如何允许这样的使用...一种方法是停止进行类型擦除+为不同的泛型类型保留不同版本的类字节码,如 C++ 模板中......然后上面泛化数组实例化的语法完全有效............List[] arrayOfLists = new List[2]; // 完全有效,因为 List 接口本身是编译器擦除泛型参数类型信息后唯一剩下的东西。【参考方案9】:

我知道我在这里聚会有点晚了,但我想我可能能够帮助任何未来的谷歌员工,因为这些答案都没有解决我的问题。不过,Ferdi265 的回答帮助很大。

我正在尝试创建自己的链接列表,因此以下代码对我有用:

package myList;
import java.lang.reflect.Array;

public class MyList<TYPE>  

    private Node<TYPE> header = null;

    public void clear()    header = null;  

    public void add(TYPE t)    header = new Node<TYPE>(t,header);    

    public TYPE get(int position)   return getNode(position).getObject();  

    @SuppressWarnings("unchecked")
    public TYPE[] toArray()        
        TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());        
        for(int i=0 ; i<size() ; i++)   result[i] = get(i); 
        return result;
    


    public int size()
         int i = 0;   
         Node<TYPE> current = header;
         while(current != null)    
           current = current.getNext();
           i++;
        
        return i;
      

在 toArray() 方法中为我创建了一个泛型类型的数组:

TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());    

【讨论】:

【参考方案10】:

就我而言,我只是想要一个堆栈数组,如下所示:

Stack<SomeType>[] stacks = new Stack<SomeType>[2];

由于这是不可能的,我使用以下解决方法:

    围绕 Stack 创建了一个非泛型包装类(比如 MyStack) MyStack[] stacks = new MyStack[2] 运行良好

丑陋,但 Java 很高兴。

注意:正如 BrainSlugs83 在对问题的评论中提到的,在 .NET 中完全有可能拥有泛型数组

【讨论】:

【参考方案11】:

这是因为泛型是在创建后添加到 java 中的,所以它有点笨拙,因为 java 的原始制造者认为在创建数组时会在创建数组时指定类型。所以这不适用于泛型,所以你必须这样做 E[] 数组=(E[]) 新对象[15]; 这会编译,但会发出警告。

【讨论】:

【参考方案12】:

class 可以声明一个 T[] 类型的数组,但它不能直接实例化这样的数组。相反,一种常见的方法是实例化一个 Object[] 类型的数组,然后进行窄转换为 T[] 类型,如下所示:

  public class Portfolio<T> 
  T[] data;
 public Portfolio(int capacity) 
   data = new T[capacity];                 // illegal; compiler error
   data = (T[]) new Object[capacity];      // legal, but compiler warning
 
 public T get(int index)  return data[index]; 
 public void set(int index, T element)  data[index] = element; 

【讨论】:

【参考方案13】:

肯定有一个很好的解决方法(也许使用反射),因为在我看来这正是ArrayList.toArray(T[] a) 所做的。我引用:

public &lt;T&gt; T[] toArray(T[] a)

返回一个包含所有 此列表中的元素以正确的顺序排列;的运行时类型 返回的数组是指定数组的数组。如果列表适合 指定数组,在其中返回。否则,一个新数组是 分配有指定数组的运行时类型和大小 这个列表。

因此,一种解决方法是使用此函数,即在数组中创建所需对象的ArrayList,然后使用toArray(T[] a) 创建实际数组。它不会很快,但你没有提到你的要求。

那么有人知道toArray(T[] a) 是如何实现的吗?

【讨论】:

List.toArray(T[]) 之所以有效,是因为您实际上是在运行时为其提供了组件类型 T (您为其提供了所需数组类型的实例,它可以从中获取数组类,然后是组件类 T)。使用运行时的实际组件类型,您始终可以使用Array.newInstance() 创建该运行时类型的数组。您会发现在许多询问如何在编译时创建类型未知的数组的问题中都提到了这一点。但是 OP 专门问 为什么 你不能使用 new T[] 语法,这是一个不同的问题【参考方案14】:

如果我们不能实例化泛型数组,为什么语言有泛型数组类型?没有对象的类型有什么意义?

我能想到的唯一原因是可变参数 - foo(T...)。否则他们可能已经完全清除了泛型数组类型。 (好吧,他们实际上并不需要为可变参数使用数组,因为可变参数在 1.5 之前不存在。这可能是另一个错误。)

所以这是个谎言,你可以通过可变参数实例化泛型数组!

当然,泛型数组的问题仍然存在,例如

static <T> T[] foo(T... args)
    return args;

static <T> T[] foo2(T a1, T a2)
    return foo(a1, a2);


public static void main(String[] args)
    String[] x2 = foo2("a", "b"); // heap pollution!

我们可以用这个例子来实际演示generic数组的危险。

另一方面,我们使用泛型可变参数已有十年了,但天还没塌下来。所以我们可以说这些问题被夸大了。这没什么大不了的。如果允许显式创建泛型数组,我们就会到处出现错误;但我们已经习惯了擦除的问题,我们可以忍受它。

我们可以指向foo2 来驳斥关于规范使我们远离他们声称让我们远离的问题的说法。如果 Sun 有更多的时间和资源开发 1.5,我相信他们可以达成更令人满意的解决方案。

【讨论】:

【参考方案15】:

正如其他人已经提到的,您当然可以通过some tricks 创建。

但不推荐。

因为 type erasure 和更重要的 covariance in 数组只允许将子类型数组分配给超类型数组,这会迫使您在尝试取回值时使用显式类型转换导致运行时ClassCastException 这是泛型试图消除的主要目标之一:Stronger type checks at compile time。

Object[] stringArray =  "hi", "me" ;
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException

更直接的例子可以在Effective Java: Item 25找到。


协方差:如果 S 是 T 的子类型,则 S[] 类型的数组是 T[] 的子类型

【讨论】:

【参考方案16】:

T 值 []; // 好的

但是,你不能实例化一个 T 的数组 // vals = new T[10]; // 无法创建 T 数组

你不能创建一个 T 数组的原因是没有办法 编译器知道要实际创建什么类型的数组。

【讨论】:

【参考方案17】:

试试这个:

List<?>[] arrayOfLists = new List<?>[4];

【讨论】:

以上是关于我无法在 Java 中创建泛型数组类型的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在Java中创建泛型类型的实例?

在 kotlin 中创建泛型类的新实例的正确方法是啥?

如何在颤振/飞镖中创建泛型类型的对象?

java创建泛型数组

如何在 Java 中创建一个泛型数组?

无法在Repository类中创建非泛型LINQ函数