2

Given the following example, why am I able to override the return type List<? extends IConfigUser> as List<ConfigUser> in getUserList() but cannot do the same for the parameter of setUserList()?

Isn't ConfigUser considered a supertype of IConfigUser in this case?

public class Test {
   public interface IConfigUser {
   }

   public interface IConfig {
      public List<? extends IConfigUser> getUserList();
      public void setUserList(List<? extends IConfigUser> list);
   }


   public class ConfigUser implements IConfigUser {
   }

   // The type Test.Config must implement the inherited abstract method
   // Test.IConfig.setUserList(List<? extends Test.IConfigUser>)
   public class Config implements IConfig {
      @Override
      public List<ConfigUser> getUserList() {
         return null;
      }

      // The method setUserList(List<ConfigUser> list) of type Test.Config
      // must override or implement a supertype method
      @Override
      public void setUserList(List<ConfigUser> list)
      {
      }
   }
}
Zhro
  • 2,279
  • 1
  • 18
  • 34

3 Answers3

2

You can achieve your goal by adding a generic type parameter to IConfig:

public class Test {
   public interface IConfigUser {
   }

   public interface IConfig<T extends IConfigUser> {
      public List<T> getUserList();
      public void setUserList(List<T> list);
   }


   public class ConfigUser implements IConfigUser {
   }

   public class Config implements IConfig<ConfigUser> {
      @Override
      public List<ConfigUser> getUserList() {
         return null;
      }

      @Override
      public void setUserList(List<ConfigUser> list)
      {
      }
   }
}
Eran
  • 359,724
  • 45
  • 626
  • 694
2

You can return a more specific type in an override, but you can't require that you accept a more specific type. Get rid of the generics, and you can override a method returning Object with a method returning String, but you can't override a method accepting an Object parameter with a method accepting a String parameter.

All of this is so that callers are compatible. Consider:

IConfig config = new Config();    
List<SomeOtherConfigUser> list = new ArrayList<SomeOtherConfigUser>();
list.add(new SomeOtherConfigUser());
config.setUserList(list);

Oops - your Config.setUserList is expecting every element to be a ConfigUser, not a SomeOtherConfigUser.

Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
1

You can return ("specialize") the return type of getUserList() due to covariance, i.e. if you call that method on a IConfig reference all you know is that you'll get a List<? extends IConfigUser> and a List<ConfigUser> is a List<? extends IConfigUser> so the requirements are satisfied.

If you call that on a Config reference the information is more concrete but the basic requirements are still met.

With setUserList(...) the situation is different: it allows you to pass any "subclass" of List<? extends IConfigUser> which can be a List<ConfigUser> but it also can be something else, e.g. a List<SomeConfigUser>.

Btw, since you don't know the concrete generic parameter of list in setUserList(List<ConfigUser> list) the compiler will also only allow you to read from that list, never add to it - for the same reason as above: you don't know what you get and whether adding a ConfigUser is allowed because the list could only allow SomeConfigUser instances to be added.

Thomas
  • 80,843
  • 12
  • 111
  • 143