0

Imagine this two sample codes:

public class TestCompile<T> {
    private T[] array;

    public static void main(String[] args) {

    }
}

public class TestNoCompile<T> {
    private T[] array = new T[5];

    public static void main(String[] args) {

    }
}

The first class TestCompile has no errors at compilation time and the second one TestNoCompile is not able to compile.

I understant why the second one doesnt compile since the arrays in Java are covariant and the type erasure is not compatible with that. But I cant understant why the first example compiles, why can I declare a generic array if then I cant initialize it?

On the other hand I cant unserstant this other example:

public class Example<T> {
    private T[] array;

    public static void main(String[] args) {
        Example<Integer> example = new Example<>();
        example.method(new Integer[5]);
    }

    public void method(T[] array) {
        array[0] = 1; //This line doesnt compile.
    }

Here it seems Im able to initialize a generic array in the method method(...) but then Im not able to store any value in it. Which is the explanation of this behaviour?

Juan
  • 1,700
  • 1
  • 12
  • 29
  • Compiler cannot infer the type of T at this point. Assigning 1 you are saying that it is a numeral, and it could not be true, if, for example, you do Example example = new Example<>(); – VeryNiceArgumentException Dec 15 '19 at 16:47
  • First part of question is duplicate of [How to create a generic array in Java?](https://stackoverflow.com/q/529085/5221149) – Andreas Dec 15 '19 at 16:50

2 Answers2

1

Because of type-erasure, a generic array becomes an Object[] at runtime, so you need to create it as such, cast it, and acknowledge that what you're doing is not safe:

@SuppressWarnings("unchecked")
private T[] array = (T[]) new Object[5];

As for the array[0] = 1 statement, the problem is that array is a T[], and that T can be anything, so the code isn't valid.

What is you changed the code in main as follows?

Example<String> example = new Example<>();
example.method(new String[5]);

The array[0] = 1 statement is now obviously not valid, and remember, declaring a Example<String> could easily be done elsewhere at the same time you have Example<Integer> in main.

The code in method must be value for all possible T's.

Andreas
  • 138,167
  • 8
  • 112
  • 195
  • Im not able to understant the first part of the explanation. If once compiled the code looks like this "private Object[] array = new Object[5]"; why do I need to do a cast to (T[])? – Juan Dec 15 '19 at 16:54
  • @Juan Because in the source code the type is `T[]`, not `Object[]`, and you still have to honor the type-safety rules of the Java syntax. – Andreas Dec 15 '19 at 17:09
1

There's nothing wrong with the type T[] itself. It's perfectly fine to have a variable of type T[] and you can assign any value of type T[] to that variable fine, without any warnings. The question is how do you get a value of type T[].

I think you've answered your own question later when you showed that, for example, you can have a value of type T[] passed in from the outside, into a method (or a constructor) of your class. And in the caller's scope in your example, T is a concrete type (Integer), so the caller can create a T[] in its scope fine and pass it in.

As you have found, you can't create a value of type T[] (other than null) without a warning inside the class (where T is generic). This is because arrays know their component type at runtime (because arrays in Java check at runtime every element that is stored into the array is an instance of the component type), so to create an array object, you need to provide the component type of the array you want to create at runtime, and inside the class, you don't know what T is at runtime. So new T[5] is not a valid expression.

In Andreas's answer, they create an array of type Object[], and then cast it to T[], but this is basically lying to the compiler. Obviously, if T is any type other than Object, this cast is incorrect. For example, String[] foo = (String[]) new Object[5]; throws a class cast exception at runtime. However, T is erased to Object inside the class, so it does not immediately throw a class cast exception. You get an unchecked cast warning to warn you that you might not get an exception even if the cast is incorrect, so you may have a variable whose compile-time type is incompatible with its runtime type, and you may unexpectedly get a class cast exception somewhere else later. For example, if you have a method that returns the array to the outside of the class as type T[], and the place outside the class has a concrete type for T, it will cause a class cast exception where there is no cast:

public class Example<T> {
    private T[] array = (T[]) new Object[5];

    public T[] getArray() {
        return array;
    }

    public static void main(String[] args) {
        Example<Integer> example = new Example<>();
        Integer[] foo = example.getArray(); // class cast exception
    }
}

Your statement that you cannot store any value in the array is incorrect. You can store values in it, but you can only store values of type T. Inside the class, you don't know what T is, so where are you going to get a value of type T? You would either have to use null, or you have to get it from outside the class:

public class Example<T> {
    private T[] array;

    public Example(T[] a) {
        array = a;
    }

    public void set(int i, T x) {
        array[i] = x;
    }

    public static void main(String[] args) {
        Example<Integer> example = new Example<>(new Integer[5]);
        example.set(0, 1);
    }
}
newacct
  • 110,405
  • 27
  • 152
  • 217