sockeqwe
sockeqwe

Reputation: 15929

Generics: ClassCastException: java.lang.Object[] cannot be cast to

I I have a simple class that uses generics.

public class ResponseWorkerRunnable<Parameter, Result> implements Runnable {

private final ResponseWorker<Parameter, Result> worker;

/**
 * The parameters for {@link ResponseWorker#doInBackground(Object...)}
 */
private final Parameter[] params;

public ResponseWorkerRunnable(ResponseWorker<Parameter, Result> worker,
        Parameter... params) {

    uiThreadHandler = new Handler(Looper.getMainLooper());

    this.worker = worker;
    this.params = params;
}

@Override
public void run() {

    try {

        Result res = worker.doInBackground(params);
        postResultBack(res);

    } catch (Exception e) {
        postErrorBack(e);
    }

}
}

and my ResponseWorker:

public interface ResponseWorker<Parameter, Result> {

    public Result doInBackground(Parameter... param) throws Exception;
}

The problem is, that I get ClassCastException:

java.lang.ClassCastException: java.lang.Object[] cannot be cast to model.Table[]

I do something like this:

Table t = new Table();
ResponseWorker<Table, SuperTable> worker = ... ;

ResponseWorkerRunnable<Table, SuperTable> r = new ResponseWorkerRunnable<Table, SuperTable>(worker, t);

Than the ResponseWorkerRunnable will be scheduled and will run in the future with this exception:

java.lang.ClassCastException: java.lang.Object[] cannot be cast to model.Table[]

at this line in the ResponseWorkerRunnable run() method:

Result res = worker.doInBackground(params);

I have used the debugger to check the Parameter[] params field (in ResponseWorkerRunnable) and its set to Object[1]{Table@4237c0e0}

So its an array of object but ResponseWorker.doInBackground expects an Array of class Table.

How do I cast it correctly?

Result res = worker.doInBackground((Parameter[]) params);

Any other idea or hint what could be wrong?

------ UPDATE -------

I use a singleton class called ResponseWorkerExecutor schedule the ResponseWorkerRunnable (with a ThreadPool) to

    class ResponseWorkerExecutor {

public static <Parameter, Result> Future<?> submit(
            ResponseWorker<Parameter, Result> responseWorker, Parameter ... params) {

        return INSTANCE.executor
                .submit(new ResponseWorkerRunnable<Parameter, Result>(
                        responseWorker, params));

    }
}

So in my code I do something like this: I do something like this:

Table t = new Table();
// ResponseWorker implementation
ResponseWorker<Table, SuperTable> worker = ... ;

// Here is the problem with the args
ResponseWorkerExecutor.submit(worker, t);

Upvotes: 2

Views: 2582

Answers (4)

sockeqwe
sockeqwe

Reputation: 15929

So I have sold this problem with a workaround. I use List instead of Parameter[] or Parameter ... params .

There are already some help methods in java.util.Collections class like: Collections.singletonList(param);

So in my case that seems to me the best solution, since I have only a single line of code, where I have to put a single object in a List<> or to convert a array to a list. Hence this method is part of a little library the user of the library does not have to take care about it.

The solution with Arrays.copyOf(params, params.length, actualClass)); suggested by @gpeche needs an aditional Class as parameter, and at the end the user of the library have to add the class.

So I guess I found a compromise by using List instead of Parameter ... params

Upvotes: 0

gpeche
gpeche

Reputation: 22524

Due to the way generics work in Java (read here about type erasure) the actual Parameter class is being replaced by Object in the resulting bytecode, this is why your varargs array is Object[] and not Table[].

There is a workaround in this case that should work, it involves some changes to your code:

// Pass the actual class object to your Runnable, in this case t.getClass() -> Table.class
ResponseWorkerRunnable<Table, SuperTable> r = new ResponseWorkerRunnable<Table, SuperTable>(worker, t, t.getClass());

And then:

public class ResponseWorkerRunnable<Parameter, Result> implements Runnable {

    private final ResponseWorker<Parameter, Result> worker;

   /**
    * The parameters for {@link ResponseWorker#doInBackground(Object...)}
    */
   private final Parameter[] params;

   private final Class<?> actualClass;

   public ResponseWorkerRunnable(ResponseWorker<Parameter, Result> worker, Parameter... params, Class<?> actualClass) {

        uiThreadHandler = new Handler(Looper.getMainLooper());

        this.worker = worker;
        this.params = params;
        this.actualClass = actualClass;
    }

    @Override
    public void run() {

        try {

            Result res = worker.doInBackground((Parameter[]) Arrays.copyOf(params, params.length, actualClass));
            postResultBack(res);

        } catch (Exception e) {
            postErrorBack(e);
        }
    }
}

What this does is take the Object[] and copying its contents into a new, real Parameter[], whatever the actual class Parameter refers to. Then it makes the varargs call using this new array.

Upvotes: 2

Steve11235
Steve11235

Reputation: 2923

Using "Parameters" instead of the conventional "P" makes your code harder to read. What is happening is this. The type of this.params is correctly set to Parameter[]. If you passed a value to the constructor, then it would also be checked against Parameter[]. However, you didn't pass an argument, so the runtime creates an empty array for you. Unfortunately, it isn't smart enough to recognize the now erased type Parameter, so it creates an Object[]. I don't know if it should or not, but it isn't.

I understand what you are doing, and it makes sense. One way to "fix" the problem inside the constructor is to check the type of "params". Given that it is an array, you may not be able to use instanceof. Or, you can simply check to see if it empty. If you didn't receive a Parameter[], ignore "params" and create a new, emtpy Parameter[] and assign it to "this.params".

Upvotes: 1

Manuel Manhart
Manuel Manhart

Reputation: 5485

Use, that fixes it (at least thats what my Eclipse said ;-) )

public Result doInBackground(Parameter[] param);

If that fixed it, there seems to be a problem with the varags declaration and generics.

Upvotes: 0

Related Questions