3

Ran into an issue with generics and array types that I am unable to solve. It boils down to this. In the following code, how can I convert a generic List into an Array of the same generic type, while using a factory method ("T convert(String value)") to convert each individual element of the input generic List:

@Test
public void test(){
    List<String> integers = Arrays.asList("1", "2", "3", "4");

    Integer[] integerArray = new TypeConverter<Integer[]>(Integer[].class).convert(integers);

    assertEquals(4, integerArray.length);
    assertEquals(1, integerArray[0].intValue());
    assertEquals(2, integerArray[1].intValue());
    assertEquals(3, integerArray[2].intValue());
    assertEquals(4, integerArray[3].intValue());
}

public class TypeConverter<T>{
    Class<T> type;

    public TypeConverter(Class<T> type) {
        this.type = type;
    }

    T convert(List<String> values){

        List output = new ArrayList();

        for (String value : values) {
            //have to use Object.class here since I cant get the non-array type of T:
            output.add(new TypeConverter(type.getComponentType()).convert(value));
        }

        return (T) output.toArray();
    }

    T convert(String value){
        //convert to an int;
        if(type == Integer.class){
            return (T) new Integer(Integer.parseInt(value));
        }
        return null;
    }
}

As you can see, my naive approach was to simply use the toArray Method, and cast the result like so:

(T) value.toArray()

but this results in a ClassCastException:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer

Is there a way to solve this that I am not seeing or should I take another approach?

Edit

Here's the concrete code that I am trying to fix. Specifically the visitArray() method:

https://github.com/johncarl81/transfuse/blob/master/transfuse/src/main/java/org/androidtransfuse/analysis/adapter/AnnotationTypeValueConverterVisitor.java

2

5 Answers 5

2

You may use the alternate version of List.toArray that takes as a parameter the type of the array you want to get.

toArray Method

You may create an empty array with some method of the Array class.

Array.newInstance

So having the expected type you just use Array.newInstance(type, 0); and pass in the result to the toArray method.

Edit: Since your generic type is an array, you need to get the type of the components, try this:

Object[] array = (Object[]) Array.newInstance(type.getComponentType(), 0);
return (T) output.toArray(array);

Your value conversion method has a little something I'll let you figure out how to solve :)

Cheers!

Sign up to request clarification or add additional context in comments.

5 Comments

What would I pass into the toArray() method?
return (T) output.toArray(Array.newInstance(type, 0)); doesn't compile. Arraay.newInstance(type, 0) returns the type Object and List.toArray() requires an array type T[]
I'll create for you a sample implementation and edit the post.
There you have, you only need to fix the string to integer conversion method in order the test to pass.
Excellent +1. This works like a charm in my example and the code I was trying to fix. Fixed that issue in the conversion method. Thanks Juan.
2

Don't try to cast to T, try casting to T[], as you are handling an array of T's.

1 Comment

That is one possibility, but I was trying to aim at a more generalized solution.
2

I suggest ditching the reflection and reference arrays.

Slightly abusing inheritance:

public abstract class StringConverter<T> {

    public List<T> convert(List<String> values) {
        List<T> output = new ArrayList<>();
        for (String value : values) {
            output.add(convert(value));
        }
        return output;
    }

    public abstract T convert(String value);
}

public static StringConverter<Integer> toInteger() {
    return new StringConverter<>() {
        public Integer convert(String value) {
             return Integer.parseInt(value);
        }
    };
}

4 Comments

That's a sweet solution. And much nicer than having to have a convert() routine with a big if/else for all the types you want to handle.
Tom, here's my problem, I don't have control over the return type of the method as I am implementing an interface. Here's the relevant code: github.com/johncarl81/transfuse/blob/master/transfuse/src/main/…
@TomHawtin-tackline what do you mean "starting from there"? Do you have a better solution in mind?
This made me take a step back find a solution using a similar technique you described. Thanks.
1

This works for me:

import java.util.*;
import java.lang.reflect.*;

public class Foo {
    public static void main(String[] args) {
        new Foo().test();
    }

    public void test(){
        List<String> integers = Arrays.asList("1", "2", "3", "4");

        Integer[] integerArray = new TypeConverter<Integer>(Integer.class).convert(integers);

        System.out.println(Arrays.deepToString(integerArray));

    }

    public class TypeConverter<T>{
        Class<T> type;

        public TypeConverter(Class<T> type) {
            this.type = type;
        }

        T[] convert(List<String> values){

            List<T> output = new ArrayList<>();

            for (String value : values) {
                output.add(convert(value));
            }

            return output.toArray((T[]) Array.newInstance(type, output.size()));
        }

        T convert(String value){
            if(type == Integer.class){
                return (T) new Integer(Integer.parseInt(value));
            }
            else if(type == Long.class){
                return (T) new Long(Long.parseLong(value));
            }
            return null;
        }

    }
}

2 Comments

Very similar to what @Hidde was suggesting.
As you can see from the concrete example I added to the question, I not have control over the return type of the TypeConverter (AnnotationTypeValueConverterVisitor) as I am actually implementing an interface. Suggestions?
0

return (T) values.toArray(); should be return (T)output.toArray( new Integer[0])

no, you have to hard code new Integer[0]

4 Comments

how could I do this based on the type T or Class<T> type?
java generic can't do that, you see even java api do tricks like toArray( new Integer[0]).
In this contrived example this hardcoded approach would work, but I require a more generalized solution.
no. the only alternate is do it like List.toArray(T[]). that is, pass in an array of target type..... Check the code of List.toArray(T[]).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.