Java Generics and Enum, loss of template parameters

Solution 1:

Okay, first you need to understand why what you're doing is probably not what you think it's doing. Let's look at a simpler example.

interface Face {
    <T> List<T> get();
}

What you have there is a generic method, get. A generic method's type parameter depends on what is supplied by the call site. So for example like this:

Face f = ...;
// this call site dictates T to be Number
List<Number> l = f.<Number>get();

When you override it like

class Impl implements Face {
    @Override
    public List<String> get() { return ...; }
}

This is something you are able to do (only because of erasure) but you probably shouldn't. It's only allowed for backwards compatibility to non-generic code. You should listen to the warning and not do it. Doing it means that for example I can still come along and dictate it to return something else:

Face f = new Impl();
// now I've caused heap pollution because you
// actually returned to me a List<String>
List<Number> l = f.<Number>get();

This is why there is an unchecked conversion.

What you probably meant is to use a generic interface declaration:

interface Face<T> {
    List<T> get();
}

Now the argument to T depends on the type of the object reference.

Face<Number> f = ...;
// get must return List<Number>
List<Number> l = f.get();

We can implement it like

class Impl implements Face<String> {
    @Override
    public List<String> get() { return ...; }
}

Additionally, you cannot access covariant return types on an enum. When you override methods on an enum constant, its class is anonymous. An anonymous class has no name and cannot be referred to. Therefore the programmer cannot know its covariant return type to use it. Furthermore, an enum cannot declare generic type parameters. So what you are wanting to do is simply impossible with enum.

You can use a class with public static final instances to simulate a generic enum:

public abstract class SimEnum<T> implements Face<T> {
    public static final SimEnum<Number> A = new SimEnum<Number>() {
        @Override
        public List<Number> get() { return ...; }
    };
    public static final SimEnum<String> B = new SimEnum<String>() {
        @Override
        public List<String> get() { return ...; }
    };

    private SimEnum() {}

    public static SumEnum<?>[] values() {
        return new SimEnum<?>[] { A, B };
    }
}

Otherwise you need to drastically change your idea.

Solution 2:

Maybe use an interface/abstract class instead of an enum?

Enums cannot have type parameters but classes and interfaces can.

For example...

Interfaces

Entity.java

The "thing" interface...

import java.io.Serializable;

public interface Entity<K extends Serializable> {
    // TODO: Put entity type things here!
    // for example, things like "K getId();"
    // You may want an abstract base class for this interface that all Entitys extend
}

Repository.java

Does CRUD stuff with things...

import java.io.Serializable;

public interface Repository<K extends Serializable, V extends Entity<K>> {
    V getValue(K key);
    // Other CRUD stuff
}

Service.java

A Service is responsible for doing stuff with things...

public interface Service<K, V> {
    // Could have an abstract service class that has a repository and implements this for you...
    V get(K key);
    // Other "generic service" type stuff

}

Solid Classes

Entity1.java

Solid base class with String key...

public class Entity1 implements Entity<String> {
    // TODO implement Entity stuff...
}

Entity2.java

Solid base class with Integer key...

public class Entity2 implements Entity<Integer> {
    // TODO implement methods...
}

Entity1Service.java

Solid Entity1 Service

public class Entity1Service implements Service<String, Entity1> {

    // Would not have to implement this if you extended an abstract base Service class
    @Override
    public Entity1 get(String key) {
        return null;
    }

}

Entity2Service.java

Solid Entity2 Service

public class Entity2Service implements Service<Integer, Entity2> {

    // Wouldn't need this if you had abstract Service class either...
    @Override
    public Entity2 get(Integer key) {
        return null;
    }

}

ServiceHolder.java

Not an enum, but an interface - you could add methods to set the "service" from spring or something here...

import java.io.Serializable;

public abstract class ServiceHolder<K extends Serializable, V, S extends Service<K, V>> {

    public static final ServiceHolder<String, Entity1, Entity1Service> ENTITY_1_SERVICE = new ServiceHolder<String, Entity1, Entity1Service>() {};
    public static final ServiceHolder<Integer, Entity2, Entity2Service> ENTITY_2_SERVICE = new ServiceHolder<Integer, Entity2, Entity2Service>() {};

    private S service;

    private ServiceHolder() {
    }

    public S getService() {
        return service;
    }

    public void setService(S service) {
        this.service = service;
    }
}

The interesting bit

I think this is the sort of thing you wanted, please let me know if I misunderstood...

public class PleaseCompile {

    public static void main(String[] args) {
        Entity1 solid1 = ServiceHolder.ENTITY_1_SERVICE.getService().get("[KEY]");
        Entity2 solid2 = ServiceHolder.ENTITY_2_SERVICE.getService().get(42);

        ...
    }
}

Hope this helps...