英文:
Java generics cast and usage
问题
I have kind of problem with generics inheritance. Below is the dependency tree:
public class Attributes {
}
public abstract class Feature<T extends Attributes> {
private T attributes;
public T getAttributes() {
return attributes;
}
public void setAttributes(T attributes) {
this.attributes = attributes;
}
}
public abstract class AbstractFeatureRepository<T extends Feature<? extends Attributes>> {
public abstract String getType();
public abstract boolean create(T feature);
}
And I have implementations of these feature repositories, like this:
public class NodeAttributes extends Attributes {
private String startPoint;
public String getStartPoint() {
return startPoint;
}
public void setStartPoint(String startPoint) {
this.startPoint = startPoint;
}
}
public class Node extends Feature<NodeAttributes> {
}
public class NodeRepository extends AbstractFeatureRepository<Node> {
public String getType() {
return "Node";
}
public boolean create(Node node) {
return true;
}
}
public class LinkAttributes extends Attributes {
private String uri;
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
}
public class Link extends Feature<LinkAttributes> {
}
public class LinkRepository extends AbstractFeatureRepository<Link> {
public String getType() {
return "Link";
}
public boolean create(Link link) {
return true;
}
}
I'm injecting these repositories with Spring to Controller via constructor (but in this example I'm manually creating in constructor for the sake of simplicity):
public class Controller {
private final Map<String, AbstractFeatureRepository<? extends Feature>> featureRepositories;
public Controller() {
this.featureRepositories = Arrays.asList(new NodeRepository(), new LinkRepository()).stream()
.collect(Collectors.toMap(AbstractFeatureRepository::getType, Function.identity()));
}
public Node createNode() {
Node newNode = new Node();
newNode.getAttributes().setStartPoint("Berlin");
createFeature("Node", newNode);
return newNode;
}
public Link createLink() {
Link newLink = new Link();
newLink.getAttributes().setUri("/home/local");
createFeature("Link", newLink);
return newLink;
}
private void createFeature(String type, Feature<? extends Attributes> feature) {
featureRepositories.get(type).create(feature);
}
}
All is good until I want to call "create" method in generic "createFeature" where I'm getting a compilation error that:
The method create(capture#5-of ? extends Feature) in the type AbstractFeatureRepository<capture#5-of ? extends Feature> is not applicable for the arguments (Feature<capture#6-of ? extends Attributes>)
What I'm doing wrong? Is this because potentially I can pass some other kind of "Feature" than particular "Repository" can work with? How then should I change my map Repositories in Controller so the compiler doesn't complain? I thought I should operate exact classes as a key for map but have no idea how to make all of this working. Any suggestions?
Thank you.
Update 1. I changed Map to
private final Map<Class<?>, AbstractFeatureRepository<? extends Feature>> featureRepositories;
Changed AbstractFeatureRepository so it now looks this way:
public abstract class AbstractFeatureRepository<T extends Feature> {
public abstract Class<T> getClazz();
public abstract boolean create(T feature);
}
And changed the methods in the controller:
public Link createLink() {
Link newLink = new Link();
createFeature(Link.class, newLink);
return newLink;
}
private <T extends Feature> void createFeature(Class<T> class1, T feature) {
AbstractFeatureRepository<? extends Feature> abstractFeatureRepository = featureRepositories.get(feature.getClass());
abstractFeatureRepository.create(abstractFeatureRepository.getClazz().cast(feature));
}
It still doesn't allow me to do that.
英文:
I have kind of problem with generics inheritance. Below is the dependency tree:
public class Attributes {
}
public abstract class Feature<T extends Attributes> {
private T attributes;
public T getAttributes() {
return attributes;
}
public void setAttributes(T attributes) {
this.attributes = attributes;
}
}
public abstract class AbstractFeatureRepository<T extends Feature<? extends Attributes>> {
public abstract String getType();
public abstract boolean create(T feature);
}
And I have implementations of these feature repositories, like this:
public class NodeAttributes extends Attributes {
private String startPoint;
public String getStartPoint() {
return startPoint;
}
public void setStartPoint(String startPoint) {
this.startPoint = startPoint;
}
}
public class Node extends Feature<NodeAttributes> {
}
public class NodeRepository extends AbstractFeatureRepository<Node> {
public String getType() {
return "Node";
}
public boolean create(Node node) {
return true;
}
}
public class LinkAttributes extends Attributes {
private String uri;
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
}
public class Link extends Feature<LinkAttributes> {
}
public class LinkRepository extends AbstractFeatureRepository<Link> {
public String getType() {
return "Link";
}
public boolean create(Link link) {
return true;
}
}
I'm injecting these repositories with Spring to Controller via constructor (but in this example I'm manually creating in constructor for the sake of simplicity):
public class Controller {
private final Map<String, AbstractFeatureRepository<? extends Feature>> featureRepositories;
public Controller() {
this.featureRepositories = Arrays.asList(new NodeRepository(), new LinkRepository()).stream()
.collect(Collectors.toMap(AbstractFeatureRepository::getType, Function.identity()));
}
public Node createNode() {
Node newNode = new Node();
newNode.getAttributes().setStartPoint("Berlin");
createFeature("Node", newNode);
return newNode;
}
public Link createLink() {
Link newLink = new Link();
newLink.getAttributes().setUri("/home/local");
createFeature("Link", newLink);
return newLink;
}
private void createFeature(String type, Feature<? extends Attributes> feature) {
featureRepositories.get(type).create(feature);
}
}
All is good untill I want to call "create" method in generic "createFeature" where I'm getting compilation error that
>The method create(capture#5-of ? extends Feature) in the type AbstractFeatureRepository<capture#5-of ? extends Feature> is not applicable for the arguments (Feature<capture#6-of ? extends Attributes>)
What I'm doing wrong? Is this because potentially I can pass some other kind of "Feature" than particular "Repository" can work with? How then should I change my map Repositories in Controller so compiler doesn't complain? I though I should operate exact classes as a key for map but have no idea how to make all of this working. Any suggestions?
Thank you.
Update 1. I changed Map to
private final Map<Class<?>, AbstractFeatureRepository<? extends Feature>> featureRepositories;
Changed AbstractFeatureRepository so it now looks this way:
public abstract class AbstractFeatureRepository<T extends Feature> {
public abstract Class<T> getClazz();
public abstract boolean create(T feature);
}
And changed the methods in controller:
public Link createLink() {
Link newLink = new Link();
createFeature(Link.class, newLink);
return newLink;
}
private <T extends Feature> void createFeature(Class<T> class1, T feature) {
AbstractFeatureRepository<? extends Feature> abstractFeatureRepository = featureRepositories.get(feature.getClass());
abstractFeatureRepository.create(abstractFeatureRepository.getClazz().cast(feature));
}
It still doesn't allow me to do that.
答案1
得分: 1
以下是您要翻译的内容:
这段代码:
featureRepositories.get(type)
返回一个对象,其类型是 java.util.Map
文档中 Map<K, V>
中的 V
。在您的代码中,这意味着该表达式的类型是 AbstractFeatureRepository<? extends Feature<? extends Attributes>>
。
让我们稍微简化一下,假设我们有 List<? extends Number>
。
这是有效的 Java 代码:
List<? extends Number> list = new ArrayList<Integer>();
这是 ? extends
的重点,确实如此。这是无效的:
List<Number> list = new ArrayList<Integer>();
现在,想象一下您在 List<? extends Number>
上调用:
List<? extends Number> list = new ArrayList<Integer>();
Number n = Double.valueOf(5.0);
list.add(n);
噢哦。在我的整数列表中有一个非整数。糟糕。
这就是为什么在这里根本不能调用 add()
。在 List<? extends whatever>
上不能调用 add
。 任何以 T
作为参数的方法,其中您的类型是 Foo<? extends T>
,都不能被调用*。
现在回到您的代码:
您有一个 AbstractFeatureRepository<? extends Feature<? extends Attributes>>
- 因此,从中调用 AbstractFeatureRepository
具有的任何带有 T
的方法都无法从中调用。而 create
就是这样一种方法。
解决方案有点棘手。您可以使用类型安全的容器,如果您以某种方式有一个表示 T
的类的引用(小心;事情可以是 T
,但不能是类:List<Integer>
是 T
,但只有 List.class
存在,您不能写 List<Integer>.class
! - 您可以使用以下方法:
public <T extends Attribute> void createFeature(Class<T> typeDesired, Feature<T> feature) {
featureRepositories.get(type).create(typeDesired.cast(feature));
}
这是一种方法。
总的来说,您的方法签名有问题:只是没有保证您的字符串类型的 String type
意味着与匹配该类型的存储库相对应的所需特征的种类 Feature<? extends Attribute>
。
第二个选项是使每个 AbstractFeatureRepository
负责处理类型不匹配。在这种情况下,您可以更新接口以读取 create(Object feature) throws TypeMismatchException
。或者,让它返回一个类型(public Class<T> getType()
),然后您可以返回到 cast
构造。
英文:
This code:
featureRepositories.get(type)
returns an object whose type is the V
in Map<K, V>
, as per the docs of java.util.Map
. In your code, that means that expression is of type AbstractFeatureRepository<? extends Feature<? extends Attributes>>
.
Let's simplify matters a tad, and assume we have List<? extends Number>
instead.
This is valid java code:
List<? extends Number> list = new ArrayList<Integer>();
that's sort of the point of ? extends
, really. This does not compile:
List<Number> list = new ArrayList<Integer>();
Now, imagine you called, on your List<? extends Number>
:
List<? extends Number> list = new ArrayList<Integer>();
Number n = Double.valueOf(5.0);
list.add(n);
uhoh. There is a non-integer in my list of integers. Whoops.
That's why you can't call add()
here, at all. You cannot call add
on a List<? extends whatever>
, at all. Any method that takes as argument a T
where your type is Foo<? extends T>
cannot be invoked*.
Let's go back to your code now:
You have a AbstractFeatureRepository<? extends Feature<? extends Attributes>>
- therefore any method that AbstractFeatureRepository
has that takes a T
cannot be invoked from this. at all. And create
is such a method.
The solution is a bit tricky. You can use a type-safe container, if you somehow have a reference to a class representing T (careful; things can be a T
that cannot be a Class: List<Integer>
is a T, but only List.class
exists, you can't write List<Integer>.class
! - You can use that:
public <T extends Attribute> void createFeature(Class<T> typeDesired, Feature<T> feature) {
featureRepositories.get(type).create(typeDesired.cast(feature));
}
is one way.
In general your method signature is problematic: There is just no guarantee that your stringly-typed String type
implies that the kind of feature desired Feature<? extends Attribute>
is handled by the repository matching that type.
A second option is to have each AbstractFeatureRepository
responsible to deal with type mismatches. In that case, you can update the interface to read create(Object feature) throws TypeMismatchException
instead. Or, have it return a type (public Class<T> getType()
) and you can go back to the cast
construct.
*) Well, you can invoke it, if you pass literally null
, as null is all data types. But that clearly isn't what you intend to do here, thus, not relevant.
答案2
得分: 0
如果你在一个Map中只有2个项(或者N个项,其中N是一个小数,你在注释中提到了4个),而且你使用固定的键来表示特定类型,那么使用Map会比必要的情况更加复杂。
Map必须具有同质类型,也就是说,所有的键都具有一个共同的超类型,所有的值都具有一个共同的超类型。你面临的问题是,你希望键的类型与值的类型相关联:这可以通过类型安全的异构容器来实现(本质上是:你构造了一个Map,以便你可以强制执行类型之间的关系约束)。但即使这样对于描述的问题也过于复杂。
改用两个(N个)字段代替:
public class Controller {
private final NodeRepository nodeRepository = new NodeRepository();
private final LinkRepository linkRepository = new LinkRepository();
}
这仍然有点像一个Map:键是字段,值是字段的值。
但这种方法的优点在于,你保留了仓库的具体类型,因此你可以将它们传递给方法:
private <A extends Attributes> void createFeature(AbstractFeatureRepository<A> repo, Feature<A> feature) {
repo.create(feature);
}
例如:
public Node createNode() {
Node newNode = new Node();
newNode.getAttributes().setStartPoint("Berlin");
// 或者更简单地使用 nodeRepository.create(newNode);
createFeature(nodeRepository, newNode);
return newNode;
}
public Link createLink() {
Link newLink = new Link();
newLink.getAttributes().setUri("/home/local");
// 或者更简单地使用 linkRepository.create(newNode);
createFeature(linkRepository, newLink);
return newLink;
}
英文:
If you've only got 2 things in a Map (or N things, where N is a small number, you mention 4 in a comment), and you use fixed keys to indicate a particular type, using a Map is making it harder than necessary.
Maps are necessarily homogeneously typed, that is, all the keys have a common supertype, and all the values have a common supertype. The issue that you're facing is that you want a key's type to relate to the value's type: this can be done with a type-safe heterogeneous container (essentially: a map which you construct such that you can impose the constraints on the relationships between the types). But even this is overly-complex for the problem described.
Use two (N) fields instead:
public class Controller {
private final NodeRepository nodeRepository = new NodeRepository();
private final LinkRepository linkRepository = new LinkRepository();
This is still sort-of a map: the key is the field, the value is the field value.
But the advantage of this is that you've retained the concrete types of the repositories, so you can pass these to the method:
private <A extends Attributes> void createFeature(AbstractFeatureRepository<A> repo, Feature<A> feature) {
repo.create(feature);
}
e.g.
public Node createNode() {
Node newNode = new Node();
newNode.getAttributes().setStartPoint("Berlin");
// Or, easier, nodeRepository.create(newNode);
createFeature(nodeRepository, newNode);
return newNode;
}
public Link createLink() {
Link newLink = new Link();
newLink.getAttributes().setUri("/home/local");
// Or, easier, linkRepository.create(newNode);
createFeature(linkRepository, newLink);
return newLink;
}
答案3
得分: 0
I've translated the content you provided into Chinese:
为了得到一个尽可能接近您原始代码的可工作解决方案,我进行了三到四次相对较小的重构。
最重要的变化出现在_Controller.createFeature()
_中...
private <T extends Feature<?>> void createFeature(Class<T> class1, T feature) {
AbstractFeatureRepository<T> abstractFeatureRepository = (AbstractFeatureRepository<T>)featureRepositories.get(feature.getClass());
abstractFeatureRepository.create(feature);
}
我认为这里的强制类型转换是最简单、最类型安全的解决方案。我之所以确信这个转换是类型安全的,是因为如果没有这个转换,您将会得到一个编译错误:
Controller.java:31: error: incompatible types: AbstractFeatureRepository<CAP#1> cannot be converted to AbstractFeatureRepository<T>
如果您仔细阅读错误消息的底部部分,您会发现_T extends Feature<?>
和CAP#1 extends Feature<?>
之间唯一的区别是两个类型变量的名称。它们都具有相同的上界(extends Feature<?>
)。这告诉我这个强制类型转换是类型安全的。因此,我用SuppressWarnings("unchecked")
_对该方法进行了注释。
为了确认这个解决方案是否可用,我用_toString()
填充了Node
和Link
类。在解决方案演示中调用Controller.createNode()
和Controller.createLink()
_会得到以下结果...
Node: [NodeAttributes - 起始点: '柏林']
Link: [LinkAttributes - URI: 'urn::foo::bar']
我必须承认,您试图解决的问题对我来说并不十分清晰。因此,我基于我对Java的一般知识做了一些假设。如果这个解决方案符合您的要求,请告诉我。
英文:
To arrive at a working solution that is as close to your original code as I could get it, I made three or four relatively minor refactors.
The most significant change though was in Controller.createFeature()
…
private <T extends Feature<?>> void createFeature(Class<T> class1, T feature) {
AbstractFeatureRepository<T> abstractFeatureRepository = (AbstractFeatureRepository<T>)featureRepositories.get(feature.getClass());
abstractFeatureRepository.create(feature);
}
The cast there is the simplest, most type safe solution in my opinion. The reason I'm convinced the cast is type safe is because of the compilation error you'd get if the cast weren't there:
Controller.java:31: error: incompatible types: AbstractFeatureRepository<CAP#1> cannot be converted to AbstractFeatureRepository<T>
AbstractFeatureRepository<T> abstractFeatureRepository = featureRepositories.get(feature.getClass());
where T is a type-variable:
T extends Feature<?> declared in method <T>createFeature(Class<T>,T)
where CAP#1 is a fresh type-variable:
CAP#1 extends Feature<?> from capture of ? extends Feature<?>
1 error
If you read the bottom part of the error message carefully, you'll see that the only difference between T extends Feature<?>
and CAP#1 extends Feature<?>
is the names of the two type variables. They both have the same upper bounds (extends Feature<?>
). That tells me it's reasonable to infer that a cast would be type safe.
So, I annotated that method with SuppressWarnings("unchecked")
.
To confirm that the solution is usable, I fleshed out Node
and Link
classes with toString()
. Calling Controller.createNode()
and Controller.createLink()
in the solution demo gets you…
Node: [NodeAttributes - Start Point: 'Berlin']
Link: [LinkAttributes - URI: 'urn::foo::bar']
I have to admit that what problem you're trying to solve isn't crystal clear to me. So I've made some assumptions based on only my general Java knowledge. Please let me know if the solution meets your requirements?
答案4
得分: 0
以下是我使用的方法:
public class Controller {
private final Map<Class<?>, AbstractFeatureRepository<? extends Feature>> featureRepositories;
public Controller(List<AbstractFeatureRepository<? extends Feature>> featureRepositories) {
this.featureRepositories = featureRepositories.stream()
.collect(Collectors.toMap(AbstractFeatureRepository::getClazz, Function.identity()));
}
public Node createNode() {
Node newNode = new Node();
createFeature(Node.class, newNode);
return newNode;
}
public Link createLink() {
Link newLink = new Link();
createFeature(Link.class, newLink);
return newLink;
}
private <T extends Feature> void createFeature(Class<T> clazz, T feature) {
AbstractFeatureRepository<T> repository = getRepository(clazz);
repository.create(feature);
}
@SuppressWarnings("unchecked")
private <T extends Feature, V extends AbstractFeatureRepository<T>> V getRepository(Class<T> clazz) {
return (V) featureRepositories.get(clazz);
}
public static void main(String[] args) {
Controller controller = new Controller();
controller.createLink();
}
}
这个代码虽然不完全满足无强制转换的要求(尤其是没有使用 @SuppressWarnings 注解),但对我来说是最不糟糕的方式,因为强制转换仅在控制器中的一个方法中执行,其余方法都不需要强制转换和 @SuppressWarnings 注解。
英文:
Here is the approach I used:
public class Controller {
private final Map<Class<?>, AbstractFeatureRepository<? extends Feature>> featureRepositories;
public Controller3(List<AbstractFeatureRepository<? extends Feature>> featureRepositories) {
this.featureRepositories = featureRepositories.stream()
.collect(Collectors.toMap(AbstractFeatureRepository::getClazz, Function.identity()));
}
public Node createNode() {
Node newNode = new Node();
createFeature(Node.class, newNode);
return newNode;
}
public Link createLink() {
Link newLink = new Link();
createFeature(Link.class, newLink);
return newLink;
}
private <T extends Feature> void createFeature(Class<T> clazz, T feature) {
AbstractFeatureRepository<T> repository = getRepository(clazz);
repository.create(feature);
}
@SuppressWarnings("unchecked")
private <T extends Feature, V extends AbstractFeatureRepository<T>> V getRepository(Class<T> clazz) {
return (V) featureRepositories.get(clazz);
}
public static void main(String[] args) {
Controller3 controller = new Controller3();
controller.createLink();
}
}
It doesn't satisfy completely no-cast approach(especially no @SuppressWarnings) but it is the least evil for me, since cast is done only in one method in controller, all the rest methods work no cast and no @SuppressWarnings.
答案5
得分: -1
尝试
private static <T extends AbstractFeatureRepository> void createFeature(Class<T> clazz, Feature<? extends Attributes> feature) {
((T) featureRepositories.get(clazz)).create(feature);
}
您应该相应地修改 featureRepositories
private static final Map<Class<?>, AbstractFeatureRepository<? extends Feature>> featureRepositories
但我不建议像这样使用泛型。
英文:
Try
private static <T extends AbstractFeatureRepository> void createFeature(Class<T> clazz, Feature<? extends Attributes> feature) {
((T) featureRepositories.get(clazz)).create(feature);
}
You should modify the featureRepositories accordingly
private static final Map<Class<?>, AbstractFeatureRepository<? extends Feature>> featureRepositories
But I don't recommend using generics like this.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论