Java Generics and Enum, loss of template parameters

I have a rather complicated structure and it does not work properly. This is what I did:

public interface ResultServiceHolder { <M, ID extends Serializable, BO extends BusinessObject<M, ID>> ResultService<M, ID, BO> getService(); } public enum ResultTypes implements ResultServiceHolder { RESULT_TYPE_ONE { @Override public ResultOneService getService() { //unchecked conversion? return serviceInitializer.getResultOneService(); } }, RESULT_TYPE_TWO { @Override public ResultTwoService getService() { //unchecked conversion? return serviceInitializer.getResultTwoService(); } }, RESULT_TYPE_THREE { @Override public ResultThreeService getService() { //unchecked conversion? return serviceInitializer.getResultThreeService(); } }; protected ServiceInitializer serviceInitializer; protected void setServiceInitializer(ServiceInitializer serviceInitializer) { this.serviceInitializer = serviceInitializer; } @Component public static class ServiceInitializer { @Autowired private ResultOneService resultOneService; @Autowired private ResultTwoService resultTwoService; @Autowired private ResultThreeService resultThreeService; @PostConstruct public void init() { for(ResultTypes resultType : ResultTypes.values()) { resultType.setServiceInitializer(this); } } //getters } } 

The goal was to summarize an enumeration-based call, but rather just the ability to iterate over an array of enumerations.

  for(ResultServiceHolder resultServiceHolder : ResultTypes.values()) { if(resultServiceHolder.equals(post.getPostResultTypeCode())) { return resultServiceHolder.getService().createResultSearchCriteriaResponse(postId); } } 

And it works great and dandy. However, if I said

 ResultTypes.RESULT_TYPE_ONE.getService().getRepository() 

Then it is BaseRepository<Object, Serializable> , and not BaseRepository<ResultTypeOne, Long> . The resultTypeHolder.getService() method returns ResultService<M, ID, BO> , but in the end it becomes Object and Serializable .

What am I doing wrong? How to save common parameter types?

I would like to add that yes, I understand that the problem is somewhere with uncontrolled casting. But services are defined as

 public interface ResultTypeOneService extends ResultService<ResultTypeOne, Long, ResultTypeOneBO> { } 

And I don't know why types are not inferred.

EDIT . Technically this works if I explicitly do them:

 ResultTypes.RESULT_TYPE_ONE.<ResultTypeOne, Long, ResultTypeOneBO>getService().getRepository() 

But it has to be automatic, why doesn't it work automatically? Should I provide it with some kind of object that contains a type? Why is return type not enough for this?

EDIT2 : Superclass ResultTypeOne equals

 @SuppressWarnings("serial") @EntityListeners(EntityListener.class) @MappedSuperclass public abstract class EntityBase implements Serializable { 

But it does not appear anywhere within.

EDIT3 : Thanks a lot @Radiodef! The theoretical solution was as follows: and will work just fine:

 public interface ResultServiceHolder<M, ID extends Serializable, BO extends BusinessObject<M, ID>> { ResultService<M, ID, BO> getService(); } public abstract class ResultTypes<M, ID extends Serializable, BO extends BusinessObject<M, ID>> implements ResultServiceHolder<M, ID, BO> { public static ResultTypes<?, ?, ?>[] values() { return new ResultTypes<?, ?, ?>[] {RESULT_ONE, RESULT_TWO, RESULT_THREE}; } public static final ResultTypes<ResultOne, Long, ResultOneBO> RESULT_ONE = new ResultTypes<ResultOne, Long, ResultOneBO>("Result One") { @Override public ResultOneService getService() { return serviceInitializer.resultOneService; } }; public static final ResultTypes<ResultTwo, Long, ResultTwoBO> RESULT_TWO = new ResultTypes<ResultTwo, Long, ResultTwoBO>("Result Two") { @Override public ResultTwoService getService() { return serviceInitializer.resultTwoService; } }; public static final ResultTypes<ResultThree, Long, ResultThreeBO> RESULT_THREE = new ResultTypes<ResultThree, Long, ResultThreeBO>("Result Three") { @Override public ResultThreeService getService() { return serviceInitializer.resultThreeService; } }; protected String name; protected ServiceInitializer serviceInitializer; private ResultTypes(String name) { this.name = name; } protected void setServiceInitializer(ServiceInitializer serviceInitializer) { this.serviceInitializer = serviceInitializer; } @Component static class ServiceInitializer { @Autowired private ResultOneService resultOneService; @Autowired private ResultTwoService resultTwoService; @Autowired private ResultThreeService resultThreeService; @PostConstruct public void init() { for (ResultTypes resultType : ResultTypes.values()) { resultType.setServiceInitializer(this); } } } } 

I think, because of how long the solution will be, I stick to the enum approach and just accept this loss of boundaries. I lose more by adding my own implementation of values() than I get from adhering to these boundaries. However, this is an interesting theoretical exercise, and thanks again for your help.

+1
java spring generics enums
source share
3 answers

Well, first you need to understand why what you are doing is probably not what you think. Consider a simpler example.

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

You have a general method, get . A generic method type parameter depends on what is provided by the calling site. For example, for example:

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

When you redefine it as

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

This is what you can do (just because of erasing), but you probably shouldn't . This allowed for backward compatibility with non-generic code. You must listen to the warning and not do it. Doing this means that, for example, I can still come and dictate to him 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 an uncontrolled conversion occurs.

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

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

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

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

We can implement it as

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

In addition, you cannot access covariant return types in an enumeration. When you override methods in the enum constant, its class is anonymous. An anonymous class does not have a name and cannot be referenced. Therefore, the programmer cannot know his covariant return type in order to use it. In addition, an enumeration cannot declare generic type parameters. So what you want to do is simply impossible with an enumeration.

You can use the class with instances of public static final to simulate a common enumeration:

 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 radically change your idea.

+2
source share

Maybe use an interface / abstract class instead of enum?

Enumerations cannot have type parameters, but classes and interfaces can.

For example...

Interfaces

Entity.java

Interface "thing" ...

 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 have things ...

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

Service.java

The service is responsible for doing things 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 } 

Continuous 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 enumeration, but an interface - you can add methods to install a "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; } } 

Interesting bit

I think this is what 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 ...

+2
source share

You cannot do what you want.

List<String> and List<Integer> erase face type at runtime.

Your functions with enumeration getService() enumerated by enum are also displayed.

Everything related to generic types is checked at compile time.

0
source share

All Articles