Andreas Rayo Kniep
Andreas Rayo Kniep

Reputation: 6542

How to create generic Java interface typed with another generic Java interface?

The goal of our approach is to introduce interfaces to our existing DAO and model classes. The model classes are identified by resource-IDs of various types and the resource-IDs are not just random numbers, but carry semantics and behavior. Thus, we have to represent resource-IDs by objects rather than primitive types.

Current approach for resource-IDs:

interface ResourceId<T> {
  T get();
}
class UserId implements ResourceId<String> {
  public String get();
}

Current approach for our resources/models:

interface Resource<I extends ResourceId> {
  I id();
}
class User implements Resource<UserId> {
  public UserId id();
}


I am struggling finding a working solution for our DAO classes. Here are some approaches I tried but failed:

=== Option 1 ===
fails with:
error: > expected
multiple levels of generic types seem forbidden in Java

interface Dao<R extends Resource<I extends ResourceId>> {
  R findById(I id);
  void save(R u);
}
class UserDao implements Dao<User> {
  public User findById(UserId id);
  public void save(User u);
}

=== Option 2 ===
fails with:
UserDao is not abstract and does not override abstract method <R>save(R) in Dao
Also Dao<UserId> looks stupid. UserDao should be a Dao<User> object.

interface Dao<I extends ResourceId> {
  <R extends Resource<I>> R findById(I id);
  <R extends Resource<I>> void save(R u);
}
class UserDao implements Dao<UserId> {
  public User findById(UserId id);
  public void save(User u);
}

=== Option 3 ===
fails with:
UserDao is not abstract and does not override abstract method <I>findById(I) in Dao Even if it worked, I is not bound by the ResourceId actually implemented by R.

interface Dao<R extends Resource> {
  <I extends ResourceId> R findById(I id);
  void save(R u);
}
class UserDao implements Dao<User> {
  public User findById(UserId id);
  public void save(User u);
}

=== Option 4 ===
compiles.
However #findById in UserDao would have to take a generic parameter of type ResourceId instead of UserId. Also, inside of the implementation of #findById we would have to cast the result of #get() to String.
Generally the problem is that the type of ResourceId is not bound by the ResourceId actually implemented by R.

interface Dao<R extends Resource> {
  R findById(ResourceId id);
  void save(R u);
}
class UserDao implements Dao<User> {
  public User findById(ResourceId id);
  public void save(User u);
}

=== Option 5 ===
compiles.
However Dao<User, UserId> looks stupid. The information which ResourceId we want to use (i.e. UserId) is already available in the implementation of the Resource (i.e. User). Is there no cleaner way?

interface Dao<R extends Resource, I extends ResourceId> {
  R findById(I id);
  void save(R u);
}
class UserDao implements Dao<User, UserId> {
  public User findById(UserId id);
  public void save(User u);
}

Any ideas how to properly solve this?

Upvotes: 1

Views: 297

Answers (1)

Marco R.
Marco R.

Reputation: 2720

Your option # 1 will work just fine by tweaking it appropriately. First add the ResourceId generic type specification to your Dao declaration:

    static interface Dao<I extends ResourceId<?>, R extends Resource<I>> {
        R findById(I id);
        
        void save(R u);
    }

... and then do the same with the Dao subclasses:

    static class UserDao implements Dao<UserId, User> {
        public User findById(UserId id) { return null; }
        
        public void save(User u) {}
    }

Since you don't need to know what is the generic type of a ResourceId (<T>) within the context of the Resource class, you can just dismiss it there with a wildcard:

    static interface Resource<I extends ResourceId<?>> {
        I id();
    }

Finally, the full code would compile as:

public class NestedGenerics {
    
    static interface ResourceId<T> {
        T get();
    }
    
    static class UserId implements ResourceId<String> {
        public String get() { return null; } 
    }
    
    static interface Resource<I extends ResourceId<?>> {
        I id();
    }
    
    static class User implements Resource<UserId> {
        public UserId id() { return null; } 
    }
    
    static interface Dao<I extends ResourceId<?>, R extends Resource<I>> {
        R findById(I id);
        
        void save(R u);
    }
    
    static class UserDao implements Dao<UserId, User> {
        public User findById(UserId id) { return null; }
        
        public void save(User u) {}
    }
}

Complete code on GitHub

Hope this helps.

Upvotes: 2

Related Questions