Home > Net >  Java restricting Class<> to specific types
Java restricting Class<> to specific types

Time:03-16

I am very new to Java generics and Class<> clazz type, I have an interface

public interface MyInterface {
void doSomething(String arg1);
}

which is implemented by multiple classes

class MyInterfaceImplFirst implements MyInterface {
  private arg;
  public MyInterfaceImplFirst(String arg) {
    this.arg = arg;
  }

  @Override
  void doSomething(String arg1) {
    //...
  }
}

class MyInterfaceImplSecond implements MyInterface {
  private arg;
  public MyInterfaceImplSecond(String arg) {
    this.arg = arg;
  }

  @Override
  void doSomething(String arg1) {
    //...
  }
}

I have a class that accepts Class<MyInterface> type

public class Dummy {
  public void load(Class<MyInterface> clazz) {
    //...
  }

  public static void main(String[] args) {
    Dummy dummy = new Dummy();
    //The below does not compile
    dummy.load(MyInterfaceImplFirst.class);
  }
}

If I change the load method to public void load(Class clazz) it works fine, can someone tell me how to restrict arguments to load method to be only of subtypes(impl) of MyInterface ?

CodePudding user response:

PECS (Producer-Extends, Consumer-Super) is the problem. Given a List<Number>, you can, of course, call .add(someDouble). Therefore, a List<Integer> cannot be passed when a List<Number> is required. This 'write' concept just doesn't apply to Class<X>, but java doesn't 'know' that and therefore won't let you pass a Class<SomeImpl> when a Class<TheInterface> is required.

PECS is kind of a misnomer; it's written from the point of view of the genericsed type (so, List, Class, etc). If the class only produces stuff and will not be consuming anything of the generic type, which is the case here, PECS states you should be using extends.

Hence, public void load<Class<? extends MyInterface> clazz) is what you want.

A few notes:

  • Generics serves to link things. If you declare a generic variable and you use it in 0 or 1 places, as the misleading comment from @RobSpoor erroneously suggests, then you're messing up or hacking java's type system (there are legitimate cases where that's a good idea, but quite rare). Given that you don't need that T anywhere, you'd be messing up. Just use ? instead.
  • Mixing generics and Class is usually bad. There are things Class can represent, such as int (Class<?> c = int.class; is valid java), which generics can't (List<int> is not valid java), and vice versa things generics can represent that class cant. You cannot have an instance of java.lang.Class that represents List<String> (only List - the raw type). Class<List<String>> isn't a thing; List<String>.class does not work. List<List<String>> does work.
  • Whatever made you think: I know! I'll make an interface, use generics, and make a Class<? extends MyInterface> - that was likely a mistake. You didn't explain what made you think you needed this. Most likely, the right answer is a factory. A factory is, essentially, an abstraction for constructors.
  • Related