Java泛型和枚举,模板参数丢失
Java Generics and Enum, loss of template parameters
我有一个相当复杂的结构,它没有按预期工作。这就是我所做的:
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<Object, Serializable>
而不是BaseRepository<ResultTypeOne, Long>
。方法resultTypeHolder.getService()
回馈ResultService<M, ID, BO>
,但最后却变成了Object
和Serializable
.
我做错了什么? 如何保留通用参数类型?
我想补充一点,是的,我确实意识到问题出在未经检查的转换上。但是服务被定义为
public interface ResultTypeOneService
extends ResultService<ResultTypeOne, Long, ResultTypeOneBO> {
}
而且我不知道为什么不能推断类型。
编辑:从技术上讲,如果我明确地推断它们,它就会起作用:
ResultTypes.RESULT_TYPE_ONE.<ResultTypeOne, Long, ResultTypeOneBO>getService().getRepository()
但它应该是自动的,为什么它不自动工作?我应该为它提供某种包含该类型的对象吗?为什么 return 类型还不够?
EDIT2:ResultTypeOne
的超类是
@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()
实现比我从强制执行这些界限中得到的更多。然而,这是一个有趣的理论练习,再次感谢您的帮助。
你不能做你想做的事。
List<String>
和 List<Integer>
运行时面部类型擦除。
您的枚举映射 getService()
函数也是如此。
与泛型类型相关的一切都在编译时验证。
也许使用 interface/abstract class 而不是枚举?
枚举不能有类型参数,但 classes 和接口可以。
例如...
接口
Entity.java
"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
CRUD 是否包含事物...
import java.io.Serializable;
public interface Repository<K extends Serializable, V extends Entity<K>> {
V getValue(K key);
// Other CRUD stuff
}
Service.java
服务负责处理事物...
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
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...
}
实体1Service.java
实体实体 1 服务
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;
}
}
实体2Service.java
Solid 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
不是一个枚举,而是一个接口 - 你可以添加方法来设置 "service" 来自 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);
...
}
}
希望这对您有所帮助...
好的,首先您需要了解为什么您正在做的事情可能不是您认为的那样。让我们看一个更简单的例子。
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 ...; }
}
这是你能够做的事情(只是因为擦除)但你可能不应该。它只允许向后兼容非通用代码。你应该听警告而不是去做。这样做意味着例如我仍然可以过来并将它口授给 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 ...; }
}
此外,您无法访问枚举的协变 return 类型。当您覆盖枚举常量上的方法时,它的 class 是匿名的。匿名 class 没有名字,无法引用。因此程序员无法知道它的协变 return 类型来使用它。此外,枚举不能声明泛型类型参数。所以你想做的事情用枚举是不可能的。
您可以使用带有 public static final
个实例的 class 来模拟通用枚举:
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 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<Object, Serializable>
而不是BaseRepository<ResultTypeOne, Long>
。方法resultTypeHolder.getService()
回馈ResultService<M, ID, BO>
,但最后却变成了Object
和Serializable
.
我做错了什么? 如何保留通用参数类型?
我想补充一点,是的,我确实意识到问题出在未经检查的转换上。但是服务被定义为
public interface ResultTypeOneService
extends ResultService<ResultTypeOne, Long, ResultTypeOneBO> {
}
而且我不知道为什么不能推断类型。
编辑:从技术上讲,如果我明确地推断它们,它就会起作用:
ResultTypes.RESULT_TYPE_ONE.<ResultTypeOne, Long, ResultTypeOneBO>getService().getRepository()
但它应该是自动的,为什么它不自动工作?我应该为它提供某种包含该类型的对象吗?为什么 return 类型还不够?
EDIT2:ResultTypeOne
的超类是
@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()
实现比我从强制执行这些界限中得到的更多。然而,这是一个有趣的理论练习,再次感谢您的帮助。
你不能做你想做的事。
List<String>
和 List<Integer>
运行时面部类型擦除。
您的枚举映射 getService()
函数也是如此。
与泛型类型相关的一切都在编译时验证。
也许使用 interface/abstract class 而不是枚举?
枚举不能有类型参数,但 classes 和接口可以。
例如...
接口
Entity.java
"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
CRUD 是否包含事物...
import java.io.Serializable;
public interface Repository<K extends Serializable, V extends Entity<K>> {
V getValue(K key);
// Other CRUD stuff
}
Service.java
服务负责处理事物...
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
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...
}
实体1Service.java
实体实体 1 服务
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;
}
}
实体2Service.java
Solid 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
不是一个枚举,而是一个接口 - 你可以添加方法来设置 "service" 来自 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);
...
}
}
希望这对您有所帮助...
好的,首先您需要了解为什么您正在做的事情可能不是您认为的那样。让我们看一个更简单的例子。
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 ...; }
}
这是你能够做的事情(只是因为擦除)但你可能不应该。它只允许向后兼容非通用代码。你应该听警告而不是去做。这样做意味着例如我仍然可以过来并将它口授给 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 ...; }
}
此外,您无法访问枚举的协变 return 类型。当您覆盖枚举常量上的方法时,它的 class 是匿名的。匿名 class 没有名字,无法引用。因此程序员无法知道它的协变 return 类型来使用它。此外,枚举不能声明泛型类型参数。所以你想做的事情用枚举是不可能的。
您可以使用带有 public static final
个实例的 class 来模拟通用枚举:
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 };
}
}
否则你需要彻底改变你的想法。