Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use a generic class with wildcard declaration?

I have the following member in my class:

List<? extends SomeObject> list;

When I try to do:

list.add(list.get(0));

I get:

Test.java:7: error: no suitable method found for add(CAP#1)
        list.add(list.get(0));
            ^
    method Collection.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
    method List.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends Object from capture of ? extends Object
    CAP#2 extends Object from capture of ? extends Object

My question is twofold:

  1. Why doesn't it compile? Why can't I pass get()'s result to add()?

  2. And how can I achieve this in another way without resorting to casting?


I understand that in a method with <T extends SomeObject> I can't just say:

T someObject = list.get(0);
list.add(someObject);

since my T could be another extension than the ? extension.

I also understand I can't say:

List<? extends SomeObject> list1;
List<? extends SomeObject> list2;
list1.add(list2.get(0));

But since the add and the get should work with the same generic type in list.add(list.get(0)) I don't understand why the compiler doesn't accept it.


What I really need is

[something of type T where T is whatever was used to instantiate list] someObject = list.get(0);
list.add(someObject);

so that I can later

list.add(someObject);

I don't think I should have to template my whole class to achieve this, should I?

class MyClass<T extends SomeObject> {
List<T> list;

and then later a method with

T someObject = list.get(0);

of course works, but screws other parts of my code.

So the first question is why doesn't this work, second question is what's the best workaround?

like image 282
Roel Avatar asked May 07 '19 12:05

Roel


People also ask

How do you use generic wildcards?

Guidelines for Wildcards. Upper bound wildcard − If a variable is of in category, use extends keyword with wildcard. Lower bound wildcard − If a variable is of out category, use super keyword with wildcard. Unbounded wildcard − If a variable can be accessed using Object class method then use an unbound wildcard.

What is a generic wildcard?

The question mark (?) is known as the wildcard in generic programming. It represents an unknown type. The wildcard can be used in a variety of situations such as the type of a parameter, field, or local variable; sometimes as a return type.

Why wildcards are used in generics?

Wildcards in Java are basically the question marks which we use in generic programming, it basically represents the unknown type. We use Java Wildcard widely in situations such as in a type of parameter, local variable, or field and also as a return type.

How a generic class can be declared?

A Generic Version of the Box Class To update the Box class to use generics, you create a generic type declaration by changing the code "public class Box" to "public class Box<T>". This introduces the type variable, T, that can be used anywhere inside the class.


1 Answers

My question is twofold, why can't I do:

list.add(list.get(0));

Because the compiler isn't smart enough to know that you're adding something from list back into list. The compiler doesn't consider list.get(0) to have anything to do with list once it is evaluated: it's just "some expression" of type ? extends SomeObject.

To solve this, add a method with its own type variable:

private <T> void addFirst(List<T> list) {
  list.add(list.get(0));
}

and replace the original list.add(list.get(0)); with an invocation of this:

addFirst(list);

This only defines a type variable on the method, and does not need to be visible outside the class, so you don't need a class-level type variable.


It's perhaps worth pointing out this is analogous to the Collections.swap method: that's using set rather than add, but, from a generics point of view, it's the same thing:

@SuppressWarnings({"rawtypes", "unchecked"})
public static void swap(List<?> list, int i, int j) {
    // instead of using a raw type here, it's possible to capture
    // the wildcard but it will require a call to a supplementary
    // private method
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

This takes an approach which is technically type-safe, and does avoid casts; but it's a bit gross, because it uses raw types.

I would imagine that it is only like this for backwards-compatibility reasons. Given a chance to write it again, you could just define a type variable as in the addFirst method above.

like image 153
Andy Turner Avatar answered Oct 19 '22 23:10

Andy Turner