【问题标题】:Java Generics and Enum, loss of template parametersJava 泛型和枚举,模板参数丢失
【发布时间】:2015-01-30 11:30:35
【问题描述】:

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

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
    }
}

目的是基于枚举泛化调用,而只是能够迭代枚举数组。

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

这工作得很好而且花花公子。但是,如果我说

ResultTypes.RESULT_TYPE_ONE.getService().getRepository()

那么它是BaseRepository&lt;Object, Serializable&gt; 而不是BaseRepository&lt;ResultTypeOne, Long&gt;。方法resultTypeHolder.getService()返回ResultService&lt;M, ID, BO&gt;,但最后变成ObjectSerializable

我做错了什么? 如何保留泛型参数类型?

我想补充一点,是的,我确实意识到问题出在未经检查的强制转换的某个地方。但是服务被定义为

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

而且我不知道为什么没有推断出类型。

编辑:从技术上讲,如果我明确推断它们,它就可以工作:

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

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

EDIT2ResultTypeOne 的超类是

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

但它没有映射到边界内的任何地方。

EDIT3:非常感谢@Radiodef!理论上的解决方案最终如下,并且可以正常工作:

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);
            }
        }
    }
}

我认为由于解决方案变得如此冗长,我将坚持使用enum 方法,并接受这种界限的损失。我不得不添加我自己的values() 实现比我从执行这些界限中获得的损失更多。不过,这是一个有趣的理论练习,再次感谢您的帮助。

【问题讨论】:

  • 这不只是一个典型的类型擦除问题吗?
  • @EvanKnowles 可能是这样,但我希望这些类型不会被删除。我的通用 &lt;M, ID, BO&gt; getService() 方法出了点问题,无法将 ResultTypeOneService 解析为 &lt;ResultTypeOne, Long, ResultTypeOneBO&gt;
  • @EvanKnowles 是否可以在调用getService() 中保留类型参数?
  • 好吧,我迷路了。我不知道如何使这些类型起作用。
  • ResultTypeOne 的超类是什么?

标签: java spring generics enums


【解决方案1】:

好的,首先您需要了解为什么您正在做的事情可能不是您认为的那样。让我们看一个更简单的例子。

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

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

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

当你像这样覆盖它时

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

这是您能够做到(只是因为擦除),但您可能不应该。它只允许向后兼容非泛型代码。你应该听从警告而不是去做。这样做意味着例如我仍然可以过来并指示它返回其他内容:

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();

这就是存在未经检查的转换的原因。

您的意思可能是使用通用接口声明:

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

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

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

我们可以像这样实现它

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

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

您可以使用具有public static final 实例的类来模拟通用枚举:

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 };
    }
}

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

【讨论】:

  • 感谢您的回答。枚举本来会更好,但我有点觉得为了获得泛型类型的“上下文”,我需要离开它们。但是,您提供的答案是对它们的很好的模拟,因此可以解决我的问题。当我重新开始工作时,我可能会以这种方式实现它 - 毕竟,使用public static final 仍然是比未经检查的转换和原始类型更好的解决方案:)
  • 我认为我最初的想法并没有错。这样做是为了避免必须显式检查 if-else 结构中的每个“枚举”以调用与其对应的“正确”服务。应该知道“枚举”,这样代码将是可扩展的,而无需在 4 个不同的地方修改它,只是“枚举”本身。不过,当我重新开始工作并访问代码库时,我真的会知道得更多。
  • 覆盖通用方法以使用特定参数化可以说是错误的,但我没有使用这个词。看起来更像是一个误会。
  • 从技术上讲,我认为这是可能的,但事实并非如此。如果它可能像我想象的那样,那将是惊人的。 :P
  • 有趣的事实(无论如何在我看来),实际上可以在 Java 9 中将菱形运算符与匿名子类一起使用。
【解决方案2】:

也许使用接口/抽象类而不是枚举?

枚举不能有类型参数,但类和接口可以。

例如...

接口

Entity.java

“事物”界面...

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

CRUD 是否与事物有关...

import java.io.Serializable;

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

Service.java

Service 负责处理事情......

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

}

实体类

Entity1.java

带有字符串键的实体基类...

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

Entity2.java

带有整数键的实体基类...

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

Entity1Service.java

实体 Entity1 服务

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

实体 Entity2 服务

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

不是一个枚举,而是一个接口——你可以添加方法来设置来自 spring 的“服务”或这里的东西......

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;
    }
}

有趣的一点

我认为这是你想要的,如果我误解了,请告诉我......

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);

        ...
    }
}

希望这会有所帮助...

【讨论】:

  • 注意:我个人认为泛型可能很讨厌!如果可能的话,尽量避免愚蠢复杂的泛型表达式——如果以错误的方式使用,它会使事情变得非常难以理解......
【解决方案3】:

你不能做你想做的事。

List&lt;String&gt;List&lt;Integer&gt; 在运行时进行面部类型擦除。

您的枚举映射 getService() 函数也是如此。

与泛型类型相关的所有内容都在编译时进行验证。

【讨论】:

  • 我知道它在运行时会被删除,但从技术上讲,我的问题是 &lt;M, ID, BO&gt; 没有得到自动解析为 &lt;ResultTypeOne, Long, ResultTypeOneBO&gt;,即使我提供了服务getService() 方法为它们定义了这些参数。基本上,我通过在编译时调用 getService() 来获取原始类型。
  • @EpicPandaForce 这是个问题怎么办?
  • 我认为它不会变成原始类型,它确实是。我很想知道是否有可能以某种方式使其不是原始类型。
  • 查看答案。您无法阻止类型擦除。
  • 编译时的验证没有按我的预期工作。这就是我的问题所在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多