2

Some developers create arrays of generic type by creating an Object[] and casting to the generic type as in this example code:

public class ArrTest<E> {

  public void test(E a){
    E[] b = (E[])new Object[1];
    b[0] = a;
    System.out.println(b[0]);
  }

  public static void main(String[] args){
    ArrTest<String> t = new ArrTest<String>();
    t.test("Hello World");
  }
}

That example will work and just has a warning: Type safety: Unchecked cast from Object[] to E[].

Is it discouraged? Is this the best way to create an array of a generic type? Can this cause unexpected results or exceptions if I use this object extensively in my software?

Eugene
  • 102,901
  • 10
  • 149
  • 252
  • I created this self-answer mainly to answer this comment: http://stackoverflow.com/questions/2927391/whats-the-reason-i-cant-create-generic-array-types-in-java/2934330?noredirect=1#comment32449156_2934330 – José Roberto Araújo Júnior Feb 05 '14 at 12:37
  • Basically you're casting from superclass to subclass. Like creating an Animal and casting to Cat. For arrays this would sort of work, since an object array behaves pretty much the same regardless of its type, but the cast operation is still going to throw an exception at runtime (at least in the more generic non-generic case -- generics cause the cast to be no-oped). But if you returned the object the compiler would do an implicit `checkcast` and you'd get bit. – Hot Licks Feb 05 '14 at 12:45
  • 1
    Note that, in order to truly motivate your answer, you need an example where your generically-typed array *escapes its creating scope*. Only then does it begin to have issues with type errors. However, that doesn't happen either in your example or in the example of the question you have linked to. – Marko Topolnik Feb 05 '14 at 14:47

2 Answers2

5

In the question's example the b variable is not a String[] even though we casted it to E[] and defined that E is String while constructing the instance. It's an Object[]. That happens because Java doesn't know what type E is at runtime because, in this example, we didn't define a parent class for E. So, it will automatically have Object as its parent.

In other terms, public class ArrTest<E> is identical to public class ArrTest<E extends Object>.

Java doesn't know what E is at the runtime because it's unchecked. Unchecked means Java will not check if the E type is an extension or implementation of the defined parent class. So, the only thing Java knows about E at runtime is that <E extends Object>.

Therefore

E[] b = (E[]) new Object[1];

will execute as

Object[] b = (Object[]) new Object[1];

That's why the example will not throw a ClassCastException and will confuse the developer.

If we try to use b as a real String[] then Java will throw a ClassCastException since Java sees it as an Object[]. For example, if we change the method to:

public E[] test(E a){
  E[] b = (E[])new Object[1];
  b[0] = a;
  System.out.println(b[0]);
  return b;
}

public static void main(String[] args){
    ArrTest<String> t = new ArrTest<String>();
    String[] result = t.test("Hello World");
}

Now we will receive a ClassCastException in String[] result because the returned type will be Object[] and we are trying to store it in a String[] variable. Java will see the type difference and throw the exception.

That's why casting Object[] to a generic array is discouraged, it only leads to confusion.

Before writing this answer, I created a test case with some possible ways to create a generic array and I concluded that this is the best method:

public class ExampleType<A extends Number>{
    public <T extends A> T[] bestMethod(T[] array)
    {
        if(array.length < testSize)
            array = (T[]) Array.newInstance(array.getClass().getComponentType(), testSize); //Type safety: Unchecked cast from Object to T[]
        System.out.println("in this case: "+array.getClass().getComponentType().getSimpleName());
        return array;
    }
}

It's guaranteed to return an array of the same type as the array passed as an argument and it must be an instance of A defined in the ExampleType<A extends Number>. If you create an ExampleType of Integer you will need to use an Integer[] as the argument. If you don't want an array of Integer specifically but you want to store any type of number you could use a Number[] as the argument.

If you don't need generic types in the class you could simplify it to:

public <T> T[] bestMethod(T[] array)

Of if you want it to return subclasses of Number only:

public <T extends Number> T[] bestMethod(T[] array)

Here is my test case if you want to test it yourself:

public class Test {
    public static class ArrTest<E>
    {
        public void test(E a){
            E[] b = (E[])new Object[1];
            b[0] = a;
            System.out.println(b[0]);
        }
        public E[] test2(E a){
            E[] b = (E[])new Object[1];
            b[0] = a;
            System.out.println(b[0]+" "+b.getClass().getComponentType());
            return b;
        }
        public static void main(String[] args){
            ArrTest<String> t = new ArrTest<String>();
            t.test("Hello World");
            try{String[] result = t.test2("Hello World");}catch(Exception e){System.out.println(e);}
        }
    }

    public static void main(String[] args) {
        ArrTest.main(args);

        System.out.println("#############\nWe want an array that stores only integers, sampledata: 1, samplearray: Integer");
        test(new ExampleType<Integer>(Integer.class), 1, new Integer[0], new Integer[10]);

        System.out.println("#############\nWe want an array that stores any type of Number, sampledata: 2L, samplearray: Number");
        test(new ExampleType<Number>(Number.class), 2L, new Number[0], new Number[10]);

        System.out.println("#############\nWe want an array that stores any type of CustomNumberA, sampledata: CustomB(3L), samplearray: CustomNumberA");
        test(new ExampleType<CustomNumberA>(CustomNumberA.class), new CustomNumberB(3L), new CustomNumberA[0], new CustomNumberA[10]);

        System.out.println("#############\nWe want A to be any type of number but we want to create an array of CustomNumberA, sampledata: CustomB(3L), samplearray: CustomNumberA");
        test(new ExampleType<Number>(Number.class), new CustomNumberB(3L), new CustomNumberA[0], new CustomNumberA[10]);
    }

    public static <A extends Number> void test(ExampleType<A> testType, A sampleData, A[] smallSampleArray, A[] bigSampleArray)
    {
        Class<A> clazz = testType.clazz;
        System.out.println("#############\nStarting tests with ExampleType<"+clazz.getSimpleName()+">");
        System.out.println("============\nCreating with badMethod()...");
        A[] array;
        try
        {
            array = testType.badMethod();
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with alsoBadMethod("+sampleData+" ["+sampleData.getClass().getSimpleName()+"])...");
        try
        {
            array = testType.alsoBadMethod(sampleData);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with nearlyGoodMethod("+smallSampleArray.getClass().getSimpleName()+" len: "+smallSampleArray.length+")...");
        try
        {
            array = testType.nearlyGoodMethod(smallSampleArray);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with nearlyGoodMethod("+bigSampleArray.getClass().getSimpleName()+" len: "+bigSampleArray.length+")...");
        try
        {
            array = testType.nearlyGoodMethod(bigSampleArray);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with bestMethod("+smallSampleArray.getClass().getSimpleName()+" len: "+smallSampleArray.length+")...");
        try
        {
            array = testType.bestMethod(smallSampleArray);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
        System.out.println("============\nCreating with bestMethod("+bigSampleArray.getClass().getSimpleName()+" len: "+bigSampleArray.length+")...");
        try
        {
            array = testType.bestMethod(bigSampleArray);
            testType.executeTests(array);
        }
        catch(Exception e){ System.out.println(">> ERR: "+e); }
    }

    @RequiredArgsConstructor @ToString()
    public static class CustomNumberA extends Number{
        @Delegate final Long n;
    }

    public static class CustomNumberB extends CustomNumberA{
        public CustomNumberB(Long n) { super(n); }
    }

    @RequiredArgsConstructor
    public static class ExampleType<A>{
        private int testSize = 7;
        final Class<A> clazz;

        public A[] badMethod()
        {
            System.out.println("This will throw a ClassCastException when trying to return the array because Object is not a type of "+clazz.getSimpleName());
            A[] array = (A[]) new Object[testSize]; //Warning: Type safety: Unchecked cast from Object[] to A[]
            System.out.println("Array of "+array.getClass().getComponentType()+" created");
            return array;
        }

        public A[] alsoBadMethod(A sampleType)
        {
            System.out.println("Will not respect A type ("+clazz.getSimpleName()+"), will always use the highest type in sampleType and tell that it's A[] but it's not, in this case will return "+sampleType.getClass().getSimpleName()+"[] and said it was "+clazz.getSimpleName()+"[] while developing");
            A[] array = (A[]) Array.newInstance(sampleType.getClass(), testSize); //Type safety: Unchecked cast from Object to A[]
            return array;
        }

        public A[] nearlyGoodMethod(A[] array)
        {
            System.out.println("The only guarantee is that the returned array will be of something that extends A ("+clazz.getSimpleName()+") so the returned type is not clear, may be of A or of the type passed in the argument but will tell it's A[] but may not be");
            if(array.length < testSize)
                array = (A[]) Array.newInstance(array.getClass().getComponentType(), testSize); //Type safety: Unchecked cast from Object to A[]
            System.out.println("in this case: "+array.getClass().getComponentType().getSimpleName()+"[], expecting: "+clazz.getSimpleName()+"[]");
            return array;
        }

        public <T extends A> T[] bestMethod(T[] array)
        {
            System.out.println("It's guaranteed to return on array of the same type as the sample array and it must be an instance of A, so, this is the best method");
            if(array.length < testSize)
                array = (T[]) Array.newInstance(array.getClass().getComponentType(), testSize); //Type safety: Unchecked cast from Object to T[]
            System.out.println("in this case: "+array.getClass().getComponentType().getSimpleName()+"[], expecting: "+array.getClass().getComponentType().getSimpleName()+"[]");
            return array;
        }

        public void executeTests(A[] array)
        {
            tryToSet(array, 0, 1);
            tryToSet(array, 1, 2L);
            tryToSet(array, 2, 3.1);
            tryToSet(array, 3, 4F);
            tryToSet(array, 4, (byte)0x5);
            tryToSet(array, 5, new CustomNumberA(6L));
            tryToSet(array, 6, new CustomNumberB(7L));
        }

        public void tryToSet(A[] array, int index, Object value)
        {
            System.out.println("Trying to set "+value+" ("+value.getClass().getSimpleName()+") at "+index+" in a array of "+array.getClass().getComponentType().getSimpleName());
            try
            {
                if(array instanceof Object[]) ((Object[]) array)[index] = value;
                else array[index] = (A) value; //Type safety: Unchecked cast from Object to A
                System.out.println("## OK: Success: "+array.getClass().getComponentType().getSimpleName()+"["+index+"] = "+array[index]);
            }
            catch(Exception e){ System.out.println(">> ERR: "+e); }
        }
    }
}

And here are the test results... You can see that the bestMethod always returns the expected result.

http://pastebin.com/CxBSHaYm

Knyri
  • 2,879
  • 1
  • 15
  • 23
  • "That's why the example will not throw a ClassCastException and will confuse the developer." How is that confusing given it works exactly as the develop expects? – Peter Lawrey Feb 05 '14 at 14:37
  • @PeterLawrey why would someone cast Object[] to Object[] and say that it's String[]? The developer expects Object[] to be casted to String[] but it's not possible because Object is not a instanceof String. Did you try? – José Roberto Araújo Júnior Feb 05 '14 at 14:40
  • I would expect `(E[])` to cats to an `E[]` and since `E extends Object` this means it is an `Object` reference in reality. You can't access any method of String which is not in Object using `E` – Peter Lawrey Feb 05 '14 at 14:54
  • AFAICS it is only confusing to those who don't understand how generics work. Casting only changes the type of a reference, but the object referenced. – Peter Lawrey Feb 05 '14 at 14:56
  • @PeterLawrey That's what I'm saying, you won't have a E[] because it's an Object[], if you create a instance where the generic type is String and you do this: `return (E[]) new Object[0]` you'll cause an exception because Object[] cannot be cast to String[]. Again, did you try the test case? – José Roberto Araújo Júnior Feb 05 '14 at 15:06
  • In your use case, how is that any different to doing `return (String[]) new Object[0];`? Using `(E[])` isn't that hard or confusing if you know what you are doing. If you try to shoot yourself in the foot you can. – Peter Lawrey Feb 05 '14 at 15:43
  • So what are you suggesting you should do instead of `T[] ts = new T[n];` ? – Peter Lawrey Feb 05 '14 at 15:44
  • @PeterLawrey no, I suggest you read the answer fully before commenting because there I show how is the best way to create a `T[]`, and I also suggest you to test your codes before posting. `return (String[]) new Object[0];` throws `ClasCastException` because you can't cast `Object` to `String` because `Object` is not an instance of `String`. – José Roberto Araújo Júnior Feb 05 '14 at 15:55
  • The problem with your "best" method is you have to pass an array instance, or the alternative is to pass an array class or element type. If you could do this, why not just pass the array to be used rather than passing an array to use as the basis for creating another array. This add complexity which can result in no difference to the running of the code, or can just mean you fail faster (which is an improvement) but it's not always needed. – Peter Lawrey Feb 05 '14 at 16:05
4

The warning you got only notes that the compiler cannot ensure static type safety according to the rules specified by the Java Language Specification. In other words, it notes that static type safety has been breached.

That doesn't make this idiom unequivocally discouraged, though. Here is a fully legitimate case from JDK itself (in Grepcode format):

323  @SuppressWarnings("unchecked")
324 public <T> T[] toArray(T[] a) {
325 if (a.length < size)
326 // Make a new array of a's runtime type, but my contents:
327 return (T[]) Arrays.copyOf(elementData, size, a.getClass());
328 System.arraycopy(elementData, 0, a, 0, size);
329 if (a.length > size)
330 a[size] = null;
331 return a;
332 }

Although the downcast used is unchecked, the higher-level logic makes it clear that it is typesafe.

Marko Topolnik
  • 179,046
  • 25
  • 276
  • 399
  • 1
    I said it was discouraged to create an `Object[]` and cast it to `T[]` directly without checking, it's not actually discouraged but is wrong, may have situations that will not work, the case you provide doesn't do that, also, `Arrays.copyOf()` checks before if T[] is instance of Object[] before casting, if it's not Object[] it uses reflection just like in my answer. `T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength);` – José Roberto Araújo Júnior Feb 05 '14 at 14:02
  • If considered literally, your question asks, "Why is it discouraged to write a downcast which cannot possibly cause any type errors, either at runtime or at compile time?". I chose to interpret it as having a wider scope than that. – Marko Topolnik Feb 05 '14 at 14:11
  • `I said it was discouraged to create an Object[] and cast it to T[] directly without checking`---no, neither did you say that, nor does it apply to your example. Why would you possibly want to check the type of the object *known to be created by `new Object[]`*? – Marko Topolnik Feb 05 '14 at 14:16
  • `just like in my answer`---note that I am *answering a question* here, not *commenting on other people's answers*. – Marko Topolnik Feb 05 '14 at 14:21
  • Yes, I actually didn't say, you are right.. I commented my answer because I said that the best solution would be by reflection, and that's what's done inside Arrays.copyOf, it checks if the generic type (T[]) is an Object[] before creating an Object[] and casting to T[] otherwise the issues I explained in the answer could happens. Your answer is not bad, I asked badly, I'll edit the question and be more clear – José Roberto Araújo Júnior Feb 05 '14 at 14:38