tags:

views:

387

answers:

3

Hi everyone,

Just got a question about generics, why doesn't this compile when using a generic List? If its not possible, anyway around it? Much appreciate any answer.

// Interface used in the ServiceAsync inteface.
public interface BaseObject
{
    public String getId();
}

// Class that implements the interface
public class _ModelDto implements BaseObject, IsSerializable
{
    protected String id;

    public void setId(String id)
    {
        this.id = id;
    }

    public String getId()
    {
        return id;
    }
}

// Interface used in the ServiceAsync inteface.
public interface MyAsync<T>
{
     // Nothing here.
}

// Service interface use both interfaces above.
public interface ServiceAsync
{
    public void getList(MyAsync<List<? extends BaseObject>> callback);
}

public class MyClass
{
    ServiceAsync service = (some implementation);
    MyAsync<List<_ModelDto>> callBack = new MyAsync<List<_ModelDto>>()
    {

    };

    service.getList(callBack);  // This does not compile, says arguments are not applicable????
}
+2  A: 

The '?' in generic types can be pretty confusing. Honestly I'm not sure why this won't compile. It has to do with using the '?' in a nested generic type. But I do know some ways to work around it.

Is there a reason that the declaration of the MyAsync in MyClass has to reference _ModelDto? It would work if you changed it to look like this:

   ServiceAsync service = (some implementation);
   MyAsync<List<? extends BaseObject>> callBack = new MyAsync<List<? extends BaseObject>>() 
   {

   };

   service.getList(callBack);

If you need to reference the type _ModelDto directly you could change the definition of ServiceAsync and it will fix the problem.
Change it to look like this:

public interface ServiceAsync<T extends BaseObject>
{
    public void getList(MyAsync<List<T>> callback);
}


Then add the parameter type to the declaration in MyClass

public class MyClass 
{
   public void method() 
   {
      ServiceAsync<_ModelDto> service = (some implementation);
      MyAsync<List<_ModelDto>> callBack = new MyAsync<List<_ModelDto>>() 
      {

      };

      service.getList(callBack);
   }
}
Aaron
+5  A: 

The fact that your MyAsync interface doesn't contain any method signatures and doesn't have a particularly informative name is a code smell from my perspective, but I'll assume that this is just a dummy example. As it is written, getList() couldn't ever have any reasonable implementation that used the callback in any way; remember that type erasure will erase this method signature to getList(MyAsync callback);

The reason that this doesn't compile is that your bound is wrong. MyAsync<List<? extends BaseObject>> gives T as List<? extends BaseObject>, a list of some unknown type.

It looks to me like what you want is for the getList method itself to be generic:

public interface ServiceAsync {
    public <T extends BaseObject> void getList(MyAsync<List<T>> callback);
}

public class MyClass {
    public void foo() {
        ServiceAsync service = null;
        MyAsync<List<_ModelDto>> callBack = new MyAsync<List<_ModelDto>>() {};

        service.getList (callBack);  // This compiles
    }
}
Kris Nuttycombe
I think the problem is indeed that `MyAsync<List<_ModelDto>>` is not a subtype of `MyAsync<List<? extends BaseObject>>`, even though `_ModelDto` extends (implements) `BaseObject`. But I don't have clue why exactly that is. Any idea?
Martin Probst
For the same reason why MyAsync<Integer> is not a subtype of MyAsync<Number>, even though Integer is a subtype of Number. I tried to clarify this in my answer.
Bruno De Fraine
+1  A: 

This has got to do with the subtyping rules for parametrized types. I'll explain it in three steps:

Non-nested case

When you have the following subtype relation (where <: is the symbol for "is a subtype of"):

_ModelDto <: BaseObject

The following relation does not hold:

List<_ModelDto> <: List<BaseObject>

But the following relations do:

List<_ModelDto> <: List<? extends _ModelDto> <: List<? extends BaseObject>

This is the reason why Java has wildcards: to enable these kind of subtype relations. All of this is explained in the Generics tutorial. If you understand this, we can continue with the nested case...

Nested case

Let's do exactly the same, but with one more level of nesting. Starting from the subtype relation:

List<_ModelDto> <: List<? extends BaseObject>

The following relation does not hold, for exactly the same reasons as above:

MyAsync<List<_ModelDto>> <: MyAsync<List<? extends BaseObject>>

This is precisely the conversion you are trying to do when calling service.getList(callBack), and since the subtype relation does not hold, the conversion fails.

However, as above, you do have the following relations:

MyAsync<List<_ModelDto>>
  <: MyAsync<? extends List<_ModelDto>>
  <: MyAsync<? extends List<? extends BaseObject>>

Solution

So you should write the signature of getList as follows to make the call work:

public void getList(MyAsync<? extends List<? extends BaseObject>> callback);

The difference will be that the body of getList will be constrained with how it can use the callback. If MyAsync contains the following members:

public interface MyAsync<T> {
    T get();
    void set(T t);
}

Then, the body of getList will be able to get a list from the callback. However, it cannot set the list (except setting it to null), because it does not know exactly what kind of list is represented by the ?.

In contrast, with your original signature, set is available, and that is why the compiler cannot allow your argument.

Bruno De Fraine