Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different behaviour during converting a list of Strings to String array

I was working on Collection framework in Java , where i encountered a strange problem . I made 2 lists of Strings 1 with the help of ArrayList while second was made using Arrays.asList(T ...).

After creation of these two list i tried to convert these lists into String arrays with the list.toArray() ,
as list.toArray() method call returns an object array , so i had to explicitly cast to String[] .

After casting some strange behaviour is happening as :

Case #1 : ( where list was created using ArrayList) , gives runtime exception as java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

Case 2 : (where list as created using Arrays.asList(T ... ) ) runs fine .

here is the code

String [] str = null ,str1 = null ;
List<String> list = new ArrayList<String>();
list.add("a");
List<String> list1 = Arrays.asList("a");
str = (String[]) list.toArray();  // Runtime Exception 
str1 = (String[]) list1.toArray(); // Runs Fine 
like image 569
hellrocker Avatar asked Jun 24 '16 19:06

hellrocker


3 Answers

An ArrayList is backed by an Object[]. A copy of that array is returned with toArray().

Returns an array containing all of the elements in this list in proper sequence (from first to last element).

It make no guarantees on the type of array returned. But we know this from the exception's message. If you want it to return a String[], use the overloaded method provided for this reason.

 str = list.toArray(new String[0]);  

The cast becomes unnecessary.

The List implementation returned by Arrays.asList maintains a reference to the array (implicit or explicit) passed as its variable arity argument.

Returns a fixed-size list backed by the specified array.

The invocation

List<String> list1 = Arrays.asList("a");

creates a String[] and passes that as the argument to asList. This isn't specified clearly by the API documention, but backed by seems to indicate that it will return that same array. (Looking at the implementation, it returns a clone.) Since it is a String[], there is no error when casting and assigning it to a variable of that type.


In both cases, the appropriate solution is to use the overloaded List#toArray(T[]).


For fun, run the following and check the type of array that is returned.

List<String> list1 = (List) Arrays.<Object> asList("a");
System.out.println(list1.toArray().getClass());

Don't make assumptions. Always rely on the API documentation. If it isn't clear, try to find a better solution.

like image 181
Sotirios Delimanolis Avatar answered Sep 27 '22 18:09

Sotirios Delimanolis


The different calls to toArray are returning arrays with different component types. You can see this by running the following code:

    List<String> list = new ArrayList<String>();
    list.add("a");
    List<String> list1 = Arrays.asList("a");
    System.out.println(list.toArray().getClass());
    System.out.println(list1.toArray().getClass());

On any version of Java 8 or earlier, the result is

    class [Ljava.lang.Object;
    class [Ljava.lang.String;

Basically this output means Object[] and String[] respectively.

However, this is a bug. See JDK-6260652. Although it's not stated very clearly, Collection.toArray() must return an Object[] and not an array of some subtype of Object. There are a couple reasons for this.

First is that Collection.toArray() was introduced in JDK 1.2, long before generics were added to the language. There was no possibility of any collection implementation returning anything other than Object[], so for compatibility, all collections' toArray() implementations must return Object[].

The second reason is that a rather offhand comment in the specification for toArray(T[]) says:

Note that toArray(new Object[0]) is identical in function to toArray().

which again requires toArray() to return Object[] and not an array of some other type.

This bug has been fixed in JDK 9. Running the code snippet above on a recent JDK 9 build gives the following output:

    class [Ljava.lang.Object;
    class [Ljava.lang.Object;

The fact that Arrays.asList("a") uses a String[] for internal storage is an implementation detail. The bug where toArray() returned something other than Object[] is this implementation detail leaking out. (In fact, the array is created by the varargs machinery, using the method's type parameter as the array component type. Arrays.asList() just wraps the array it's given.)

As others have said, if you want to control the component type of the returned array, use the other overload toArray(T[]):

    String[] array = list.toArray(new String[0]);
    String[] array1 = list1.toArray(new String[0]);
like image 21
Stuart Marks Avatar answered Sep 27 '22 19:09

Stuart Marks


List<String> list = new ArrayList<String>();
list.add("a");
str = (String[]) list.toArray();

In this case your list invoke method toArray() of ArrayList class. Which looks like below, it returns Object []:

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

And elementData declare:

transient Object[] elementData;

And constructor method:

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

And DEFAULTCAPACITY_EMPTY_ELEMENTDATA:

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

There for, elementData is totaly Object [] and can't be casted to any type, String etc...

With Arrays.asList(T...), it returns java.util.Arrays$ArrayList class. And java.util.Arrays$ArrayList also has toArray() method. That subtle toArray() method makes some confuse :). Here is its implementation:

public Object[] toArray() {
    return a.clone();
}

And finally a field declare:

private final E[] a;

java.util.Arrays$ArrayList.toArray() able to return Object [] and actually E []. Hope this will help you :)

like image 21
Tran Tien Duc Avatar answered Sep 27 '22 19:09

Tran Tien Duc