Set the return type parameter based on the Class passed as an argument

Is there any way to set the return value of a method to Encoder<Cat> when Cat.class was passed as an argument?

I’m exploring reworking an API to be as generic as possible. I have a series of interfaces for data encoders but this api is for multiple applications. I want the application author to be able to define the "available encoders" so that their API reflects what’s available accurately.

For example, if app developer Bob wants to offer an encoder for the type Cat he might do:

encoderRegistry.register(Cat.class, new CatEncoder());

Then, someone using his apps’ API could get the encoder for a specific type and would see the appropriate type-based arguments:

encoderRegistry.get(Cat.class).encode(Cat cat);

I’m trying to avoid Object types because I want the API to be as user-friendly as possible and if the IDE pops up a method accepting Cat, there’s no question.

So far the only way I’ve been able to get close is by adding a type to the get method:

encoderRegistry.<IEncoder<Cat>>get(Cat.class)

This properly informs the return value for get so that I can use encode(Cat cat) but it’s verbose, and there’s no stopping me from using encoderRegistry.<IEncoder<Cat>>get(Dog.class)

>Solution :

You can set up the API to accept the right types, but internally you will need to type cast. Java’s type system is not strong enough to represent heterogenous type maps, so you will need to depend on the compiler to enforce safety at the API edges. Example:

  interface Encoder<T> {
    String encode(T value);
  }

  static class EncoderRegistry {
    Map<Class, Encoder> encoders = new HashMap<>();
    <T> void register(Class<T> clz, Encoder<? super T> encoder) {
      encoders.put(clz, encoder);
    };

    <T> Encoder<T> get(Class<T> clz) {
      return (Encoder<T>) (Encoder) encoders.get(clz);
    }
  }
  
  public static void run() {
    class Cat {}
    
    class CatEncoder implements Encoder<Cat> {
      @Override
      public String encode(Cat value) {
        return value.toString();
      }
    }

    var registry = new EncoderRegistry();
    registry.register(Cat.class, new CatEncoder());
    registry.get(Cat.class).encode(new Cat());
  }

Leave a Reply