Drew
Drew

Reputation: 83

Java Generics and unchecked cast

I'm struggling with this aspect of Generics in Java. Hopefully someone can help me see the ways.

I have a class that holds a List of objects. This code works, but I want to get rid of the cast. How can I make this more generic?

public class Executor {
    List<BaseRequest<BaseObj>> mRequests = new ArrayList<BaseRequest<BaseObj>>();

    public Executor() {
    }

    @SuppressWarnings("unchecked")
    public <T extends BaseObj> void add(final BaseRequest<T> request) {
        mRequests.add((BaseRequest<BaseObj>) request);
    }

    public void execute() {
        for (BaseRequest<BaseObj> r : mRequests) {
            // DO SOMETHING WITH r
        }
    }
}

Upvotes: 8

Views: 278

Answers (2)

aalku
aalku

Reputation: 2878

Warnings are not errors. Warnings are there so you check if you have an error because it may not be checked automatically. You should check it and then use the annotation to note that warning was already checked.

In your case it warns BaseRequest<T> is not equivalent to BaseRequest<BaseObj>.

Example:

public class NumberWrapper<N extends Number> {
    private N value;
    public void setValue(N value) {
        this.value = value;
    }
}

public class MainClazz {
    private NumberWrapper<Integer> wrappedNumber = new NumberWrapper<Integer>();
    public void run() {
        Number n = externalSource.getNumber();
        wrappedNumber.setValue(n); // <-- Error. What if getNumber returns a double?
    }
}

You can have this error ir not depending on how you complete/integrate the code you are showing.

Upvotes: 0

Raffaele
Raffaele

Reputation: 20885

In the posted snippet you need the cast because BaseRequest<? extends BaseObj> is not a subtype of BaseRequest<BaseObj>, and the cast can't be checked at runtime because of type erasure, and that's why the compiler warns you. But if you change the declaration of mRequests:

public class Executor  {

    List<BaseRequest<? extends BaseObj>> mRequests = new ArrayList<>();

    public Executor() {
    }

    public <T extends BaseObj> void add(final BaseRequest<T> request) {
        mRequests.add(request);
    }

    public void execute() {
        for (BaseRequest<? extends BaseObj> r : mRequests) {
            // DO SOMETHING WITH r
        }
    }
}

class BaseRequest<T> {}

class BaseObj {}

Let's resolve the problem step-by-step. You want to be able to call

req.add(new BaseRequest<ExtObj1>());
req.add(new BaseRequest<ExtObj2>());
req.add(new BaseRequest<ExtObj3>());

where ExtObj[1|2|3] extends BaseObj. Given the List interface:

List<T> {
  void add(T el);
}

we need to find a common supertype for BaseRequest<ExtObj1>, BaseRequest<ExtObj2> and BaseRequest<ExtObj3>. One supertype is BaseRequest<?> and another one is BaseRequest<? extends BaseObj>. I picked the second one because it's the most restrictive possible. You should know that in Java BaseRequest<ExtObj1> is not a subtype of BaseRequest<BaseObj> because generics are invariant.

Now that we have the right declaration for mRequests, finding the API for Executor.add() is straightforward. BTW, if the method body you need is really that simple, you don't even need the type parameter:

public void add(BaseRequest<? extends BaseObj> request) {
  mRequests.add(request);
}

Upvotes: 6

Related Questions