1

I have a fairly complicated structure, and it is not working as intended. 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 purpose was to generalize the call based on enums, and rather, just be able to iterate on the array of enums.

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

And this is working fine and dandy. However, if I'd say

ResultTypes.RESULT_TYPE_ONE.getService().getRepository()

Then it is a BaseRepository<Object, Serializable> rather than a BaseRepository<ResultTypeOne, Long>. The method resultTypeHolder.getService() gives back ResultService<M, ID, BO>, but in the end, it becomes Object andSerializable.

What am I doing wrong? How can I retain the generic parameter types?

I'd like to add that yes, I do realize the problem is somewhere with the unchecked casting. But the services are defined as

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

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

EDIT: Technically, it works if I explicitly infer them:

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

But it ought to be automatic, why is it not working automatically? Am I supposed to provide it with some kind of object that contains the type? Why is the return type not enough for that?

EDIT2: The superclass of the ResultTypeOne is

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

But it is not mapped anywhere in the bounds.

EDIT3: A big thank you to @Radiodef! The theoretic solution ended up to be the following, and would work perfectly 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 lengthy the solution becomes, I'll stick with the enum approach, and just accept this loss of bounds. I lose more by having to add my own values() implementation than I gain from enforcing these bounds. However, this is an interesting theoretical exercise, and thank you again for your help.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428

3 Answers3

3

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.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • Thank you for answering. Enums would have been nicer, but I somewhat felt that in order to obtain a "context" for the generic types, I'd need to switch away from them. However, the answer you provided is a nice simulation of them, and therefore a solution to my problem. When I'll get back to work, I'll probably implement it this way - using `public static final` is still a much better solution than unchecked conversions and raw types, after all :) – EpicPandaForce Jan 31 '15 at 02:31
  • I think my initial idea is not wrong, though. This is all done to prevent having to explicitly check each "enum" in an if-else structure to call the 'right' service that corresponds to it. The "enum" should be the one to know, this way the code will be extensible without having to modify it in 4 different places, just the "enum" itself. I really will know better when I'll get back to work and access the code base, though. – EpicPandaForce Jan 31 '15 at 02:37
  • 1
    Overriding a generic method to use a specific parameterization is arguably wrong but I didn't use that word. Looks more like a misunderstanding. – Radiodef Jan 31 '15 at 02:40
  • Well technically I thought it was possible, and it isn't. If it had been possible the way I imagined it, it would have been amazing. :P – EpicPandaForce Jan 31 '15 at 02:51
  • Okay, I'm at work, one thing to note: you can't use the diamond operator with anonymous subclasses, apparently. So you need to specify the types for that as well. – EpicPandaForce Feb 02 '15 at 08:19
  • *"can't use the diamond operator with anonymous subclasses"* Thanks for the correction. – Radiodef Feb 03 '15 at 07:02
  • 1
    Interesting fact (in my opinion anyways), it'll actually be possible to use the diamond operator with anonymous subclasses in Java 9. – EpicPandaForce May 28 '15 at 20:42
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...

BretC
  • 4,141
  • 13
  • 22
  • Note: Personally, I think generics can be nasty! Try and avoid stupidly complicated generic expressions if possible - it can make things very unreadable when used in the wrong way... – BretC Jan 31 '15 at 01:06
0

You cannot do what you want to do.

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

And so do your enum-mapped getService() functions.

Everything related to types for generics is validated at compile-time.

Pimgd
  • 5,983
  • 1
  • 30
  • 45
  • I know it is erased at runtime, but technically my problem is that `` don't get *automatically* resolved to `` even though the service I give back in the `getService()` method have those parameters defined for them. Basically, I get raw types back by calling `getService()` at compile time. – EpicPandaForce Jan 30 '15 at 13:52
  • @EpicPandaForce and this is a problem how? – Pimgd Jan 30 '15 at 13:53
  • I thought it would not become a raw type, and it is. I am curious to know if it is possible to somehow make it not a raw type. – EpicPandaForce Jan 30 '15 at 14:03
  • See answer. You cannot prevent type erasure. – Pimgd Jan 30 '15 at 14:07
  • The validation at compile time doesn't work as I intended. That is what my question is about. – EpicPandaForce Jan 30 '15 at 14:16
  • @EpicPandaForce What does it not do that you do want it to do? It returns a `BaseRepository` instead of a `BaseRepository`? – Pimgd Jan 30 '15 at 14:20
  • If that's the case, then you just ran into the halting problem. Given problematic enough programming, it's not possible to run the code well enough to determine what value `ResultType` will hold, and thus it's not possible to determine what `ResultService` will be returned. The solution for the compiler is to then take the base type of all of them and simply give you a `ResultService`. – Pimgd Jan 30 '15 at 14:22
  • `BaseRepository` yep, this is what I get back, and also it tells me that returning `ResultOneService` instead of `ResultService` is an unchecked conversion. – EpicPandaForce Jan 30 '15 at 15:12