Java Error: New Generic TreeNode Array

2019-03-02 09:08发布

问题:

I have generic class of TreeNode:

public class TreeNode<E> {
public E key;
public int num_of_children;
public TreeNode<E> [] children;


public TreeNode(int num_of_children)
{
    this.num_of_children = num_of_children;
    children = new TreeNode[num_of_children];// Why not: new TreeNode<E>[num_of_children]?
}

public TreeNode<E> clone()
{
    TreeNode<E> node = new TreeNode<E>(num_of_children);
    return node;
}

}

When I try to do:children = new TreeNode<E> [num_of_children];

I get error. But "new TreeNode[num_of_children]" works. I read about type erasure, and I don't understand why TreeNode<E>[] doesn't work. Why is that? Please enlighten me!

回答1:

Things like new TreeNode<String>[] and new TreeNode<E>[] are disallowed by Java. The only things you can do are new TreeNode[] and new TreeNode<?>[] (unbounded wildcard parameter).

The reason for this is a little complicated, but instructive. Arrays in Java know their component type at runtime, and every time you put something in, it checks to see if it's an instance of the component type, and if not, throws an exception (this is related to how array types are covariant and therefore inherently unsafe at compile time).

Object[] foo = new Integer[5];
foo[2] = "bar"; // compiles fine, but throws ArrayStoreException at runtime

Now add generics. The problem with a generic component type is that, there is no way for you to check if an object is an instance of say, TreeNode<Integer> at runtime (as opposed to TreeNode<String>), since generics are erased from runtime types. It can only check TreeNode, but not the component type. But programmers might have expected this checking and exception throwing behavior from arrays, since it normally works. So to avoid this surprise failure, Java disallows it. (In most code, you won't run into this problem anyway because you won't be mixing objects of the same type but different type parameters. But it is theoretically possible to come up.)

Of course, you can simply work around the problem by creating an array of raw or wildcard parameter type, and then casting to the proper type, e.g. (TreeNode<Integer>)new TreeNode[5]. What's the difference? Well, that's an unchecked cast, which generates a warning, and you, the programmer, takes responsibility for all the unsafe things that might happen later. If it does something unexpected, the compiler can say, "we told ya so!".



回答2:

Because the Java Language Specification writes:

An array creation expression creates an object that is a new array whose elements are of the type specified by the PrimitiveType or ClassOrInterfaceType.

It is a compile-time error if the ClassOrInterfaceType does not denote a reifiable type (§4.7). Otherwise, the ClassOrInterfaceType may name any named reference type, even an abstract class type (§8.1.1.1) or an interface type (§9).

The rules above imply that the element type in an array creation expression cannot be a parameterized type, other than an unbounded wildcard.

It is not clear to me why they require this. Certainly, the component type of the array must be available at runtime, and it would be misleading for the programmer if it were different from the type specified in the source code. Consider:

E[] a = new E[10];

Here, it would be bad if the compiler used the erasure of E as the array component type, as the programmer might well depend upon the array to check that nothing but instances of E is stored in it.

It's less clear what harm would come from allowing:

List<E>[] lists = new List<E>[10];

The only thing that comes to mind is that assigning an array element would amount to an unchecked cast, because the array would check the element is a List, but not that it is a List<E>, and thus fail to throw an ArrayStoreException.

In practice, you can safely suppress this warning as long as you remain aware that the array will not check the type parameters of its component type.