Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics wildcarding with both "extends" and "super"

I am working on a project where services need to be added to a component. The Service class is an interface without any methods. Here is an example how my services work:

public interface Service { }

public interface CarWash extends Service {
  void washCar(Car car);
}

public interface CarRepair extends Service {
  void repairCar(Car car);
}

Now there are many implementations of these services. A single class may implement multiple services, as this garage class:

public class Garage implements CarWash, CarRepair {
  @Override
  public void washCar(Car car) { /* .. */ }
  @Override
  public void repairCar(Car car) { /* .. */ }
}

When adding a service to a component, I do not want to need to use the service for all tasks, but for example use the Garage only for washing cars (CarWash) but not for repairing them (CarRepair). Therefore I specify the tasks as classes, just like this:

void addService(Service service, Class<? extends Service> task);

To check whether the service could actually perform the task, I used generics:

<T extends Service> addService(T service, Class<? super T> task);

This works well but does not check if the provided task is actually a task (a class that implements Service), so this would work:

addService(myTask, Object.class);

I am looking for a way to specify that service needs to implement (extend) the task AND the task is extending the Service interface, like this (does not compile):

<T extends Service> addService(T service, Class<? super T extends Service> task);
like image 882
Frithjof Avatar asked Apr 04 '15 21:04

Frithjof


People also ask

Can a generic class be extended?

We can add generic type parameters to class methods, static methods, and interfaces. Generic classes can be extended to create subclasses of them, which are also generic.

Can I use polymorphism in generics?

The polymorphism applies only to the 'base' type (type of the collection class) and NOT to the generics type.


2 Answers

I think that <T extends Service, S extends T> void addService(S service, Class<T> clazz) sounds like it meets your criteria:

public static class Foo {                                                                                   
  public interface Service { }                                                                          

  public interface CarWash extends Service {                                                            
    void washCar();                                                                                     
  }                                                                                                     

  public interface CarRepair extends Service {                                                          
    void repairCar();                                                                                   
  }                                                                                                     

  static <T extends Service, S extends T> void addService(S service, Class<T> clazz) {}                 

  public static void main(String[] args) {                                                              
    addService(null, CarWash.class);  // Fine.                                                                  
    addService(null, Object.class);   // Compilation error.                                                          
  }
}

(I added some statics and removed Car from method signatures, since I don't have a definition of those to compile)

like image 139
Andy Turner Avatar answered Oct 16 '22 13:10

Andy Turner


This may also be fine, depending on how you use the type of service:

<T extends Service> void addService(T service, Class<T> task)

If the service is a subtype of the type represented by task, then it can always be upcasted.

addService(new Garage(), CarWash.class); // OK, Garage is a CarWash

The only problem I am having is the varargs that force all array elements to be of the same type, and therefore I cannot write addService(null, CarWash.class, CarRepair.class);

This is actually a difficult problem for Java generics. (C++ can do this with variadic templates, which is a feature Java is very unlikely to get.)

So one way you can solve this in Java is with run time validation e.g.:

<T extends Service> void addService(
        T service, Class<? super T> tasks...) {
    for(Class<? super T> task : tasks)
        if(!Service.class.isAssignableFrom(tasks))
            throw new IllegalArgumentException();
}

(Or use Class<? extends Service> and check that task.isInstance(service).)

But I know we don't really like that. ; )

Java does have something called an intersection type (where if we have a type <? extends T & U>, the T & U part is called an intersection type), but an intersection type cannot combine super and extends and they are otherwise pretty limited in what else they can do.

like image 22
Radiodef Avatar answered Oct 16 '22 13:10

Radiodef