#998 False positive ClassCastExceptionWithToArray with generics

PMD-5.0.3
wont-fix
None
PMD
4-Minor
Bug
2014-01-11
2012-03-22
Anonymous
No

pmd reports "This usage of the Collection.toArray() method will throw a ClassCastException." for the first code line of this "method":

  public static <E> E[] toArrayWrapped(Collection<E> c) {
    E[] retVal = (E[]) c.toArray();
    return retVal;
  }

But this is not true. It would probably be true if E were a concrete type, but for generic types with erasure Object it is not, and in fact such a cast seems to be the shortest way of getting an array of type E.

Discussion

  • Andreas Dangel

    Andreas Dangel - 2013-03-29
    • Description has changed:

    Diff:

    --- old
    +++ new
    @@ -1,8 +1,11 @@
     pmd reports "This usage of the Collection.toArray() method will throw a ClassCastException." for the first code line of this "method":
    
    +~~~~~
    +:::java
       public static <E> E[] toArrayWrapped(Collection<E> c) {
         E[] retVal = (E[]) c.toArray();
         return retVal;
       }
    +~~~~~
    
     But this is not true. It would probably be true if E were a concrete type, but for generic types with erasure Object it is not, and in fact such a cast seems to be the shortest way of getting an array of type E.
    
    • status: open --> wont-fix
    • assigned_to: Andreas Dangel
    • module: --> PMD
    • milestone: --> PMD-5.0.3
    • priority: 5 --> 4-Minor
    • type: --> Bug
    • affects_version: -->
     
  • Andreas Dangel

    Andreas Dangel - 2013-03-29

    Yes, you won't get a class cast exception on this line.
    However, this utility method is still questionable for me.

    Consider this test class:

    import java.util.ArrayList;
    import java.util.Collection;
    import org.junit.Test;
    
    public cass ToArrayTest {
        @Test
        public void testWrap() {
            Collection<String> c = new ArrayList<String>();
            c.add("String1");
            c.add("String2");
            Object o = BasicRulesTest.<String> toArrayWrapped(c);
            System.out.println(o);            // [Ljava.lang.Object;@199836ed
            System.out.println(o.getClass()); // class [Ljava.lang.Object;
    
            // ClassCastException here: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
            String[] array = BasicRulesTest.<String> toArrayWrapped(c);
        }
    
        public static <E> E[] toArrayWrapped(Collection<E> c) {
            E[] retVal = (E[])c.toArray();
            return retVal;
        }
    }
    

    As you see, you'll get your class cast exception later on, as your method "toArrayWrapped" won't always return the correct array type.
    Btw. - it works, if you use Arrays.asList("foo", "bar") because the collection implementation internally uses then a String[] array. ArrayList however uses a Object[] array to store the data.

    The correct way to convert a collection to an array is still using Collection.toArray(T[] a) see javadoc.

    That's why I'm closing this bug as won't fix. Feel free to reopen it, if you see a different solution.

     

Log in to post a comment.