Java泛型和枚举,丢失模板参数 [英] Java Generics and Enum, loss of template parameters

查看:80
本文介绍了Java泛型和枚举,丢失模板参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个相当复杂的结构,它不按预期工作。这是我做的:

  public interface ResultServiceHolder {
< M,ID扩展Serializable,BO扩展BusinessObject& M,ID>> ResultService< M,ID,BO>的getService();
}

public enum ResultTypes实现ResultServiceHolder {
RESULT_TYPE_ONE {
@Override
public ResultOneService getService(){//未选中的转换?
return serviceInitializer.getResultOneService();
}
},
RESULT_TYPE_TWO {
@Override
public ResultTwoService getService(){//未经检查的转换?
return serviceInitializer.getResultTwoService();
}
},
RESULT_TYPE_THREE {
@Override
public ResultThreeService getService(){//未经检查的转换?
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
}
}

目的是根据枚举来泛化调用,而只是能够遍历枚举数组。



<$ p (ResultServiceHolder resultServiceHolder:ResultTypes.values()){
if(resultServiceHolder.equals(post.getPostResultTypeCode())){
返回resultServiceHolder.getService()。 createResultSearchCriteriaResponse(帖子ID);
}
}

这是正常工作和花花公子。但是,如果我会说

  ResultTypes.RESULT_TYPE_ONE.getService()。getRepository()

然后它是一个 BaseRepository< Object,Serializable> 而不是 BaseRepository< ResultTypeOne,Long> 。方法 resultTypeHolder.getService()返回 ResultService< M,ID,BO> ,但最终它成为对象可序列化



我在做什么错误? 如何保留通用参数类型?



我想补充一点,我确实意识到这个问题在某个地方没有勾选铸件。但是服务被定义为

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

我不知道为什么不推断类型。 p>

编辑:从技术上说,如果我明确推断它们,它会起作用:

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

但是它应该是自动的,为什么它不能自动工作?我应该提供一些包含类型的对象吗?为什么返回类型不足够?



EDIT2 ResultTypeOne

  @SuppressWarnings(serial)
@EntityListeners(EntityListener.class)
@ MappedSuperclass
public abstract class EntityBase实现Serializable {

但是它没有映射到边界的任何位置。



EDIT3 :非常感谢@Radiodef!理论解决方案结束如下,并且工作完全正常:

  public interface ResultServiceHolder< M,ID扩展Serializable, BO扩展了BusinessObject< M,ID>> {
ResultService< M,ID,BO>的getService();
}

public abstract class ResultTypes< M,ID扩展Serializable,BO扩展BusinessObject< M,ID>>
实现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);
}
}
}
}

考虑到解决方案变得多长时间,我将坚持使用枚举方法,只接受这种边界的损失。我不得不添加自己的 values()实现,而不是从强制执行这些边界获得。但是,这是一个有趣的理论练习,并再次感谢您的帮助。

解决方案

好的,首先你需要明白为什么你在做什么可能不是你认为它在做什么。我们来看一个更简单的例子。

  interface Face {
< T>列表与LT; T>得到();
}

你有一个通用的方法, get 。通用方法的类型参数取决于调用站点提供的内容。所以例如:

  Face f = ...; 
//此呼叫站点要求T为数字
列表< Number> l = f。< Number> get();

当您覆盖它像

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

这是你能够做的 (只因为擦除),但是你可能不应该。只允许向后兼容非通用代码。你应该听警告,不要这样做。这样做意味着,例如,我仍然可以来,并决定返回其他的东西:

 面对f = new Impl() ; 
//现在我造成堆污染,因为
//实际上返回给我一个List< String>
列表< Number> l = f。< Number> get();

这就是为什么有一个未经检查的转换。



你可能意味着要使用通用的界面声明:

 界面< T> {
列表< T>得到();
}

现在参数 T 取决于对象引用的类型。

 面部< Number> f = ...; 
// get必须返回List< Number>
列表< Number> l = f.get();

我们可以实现它像

 类Impl实现Face< String> {
@Override
public List< String> get(){return ...; }
}

此外,您不能访问枚举上的协方差返回类型。当你覆盖枚举常数的方法时,它的类是匿名的。一个匿名类没有名字,不能被引用。因此,程序员不能知道它的协变返回类型来使用它。此外,枚举不能声明泛型类型参数。所以你想要做的只是不可能使用枚举。



你可以使用一个类与 public static final 模拟一个通用枚举的实例:

  public abstract class SimEnum< T>实现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};
}
}

否则你需要彻底改变你的想法。 p>

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.

解决方案

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.

这篇关于Java泛型和枚举,丢失模板参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆