java 避免返回通配符类型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10507466/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Avoiding Returning Wildcard Types
提问by sethro
I have a class with a collection of Wildcard Types that is a singleton, something like:
我有一个带有通配符类型集合的类,它是一个单例,例如:
public ObliviousClass{
private static final ObliviousClass INSTANCE = new ObliviousClass();
private Map<Key, Type<?>> map = new HashMap<Key, Type<?>>();
public void putType(Key key, Type<?> type){
map.put(type);
}
// returns the singleton
public static ObliviousClass getInstance(){
return INSTANCE;
}
}
I'd like to be able to add different Parameterized types to this collection in client code:
我希望能够在客户端代码中向这个集合添加不同的参数化类型:
void clientMethod(){
ObliviousClass oc = ObliviousClass.getInstance();
Type<Integer> intType = ...
Type<String> stringType = ...
oc.putType(new Key(0), intType);
oc.putType(new Key(1), stringType);
}
Up to this point, as I understand it, everything is ok. But a client also needs to be able to get a Type<?>
provided the Key
. So a method something like the following would be added to ObliviousClass
:
到目前为止,据我所知,一切正常。但是客户还需要能够获得Type<?>
提供的Key
. 因此,将添加类似于以下内容的方法ObliviousClass
:
public Type<?> getType(Key key){
return map.get(key);
}
But in my handy copy of Effective Java, I read:
但在我手边的Effective Java 中,我读到:
Do not use wildcard types as return types.
不要使用通配符类型作为返回类型。
I understand the issue, as the client would have to cast the returned Type<?>
. But I really do not want to make ObliviousClass
a generic type, ObliviousClass<T>
, because then my client code above would not work...
我理解这个问题,因为客户必须将返回的Type<?>
. 但我真的不想创建ObliviousClass
一个泛型类型ObliviousClass<T>
,因为那样我上面的客户端代码将无法工作......
Is there a better design for what I am trying to do?-My current solution is to provide a static method for the client; something along the lines of:
我正在尝试做的事情有更好的设计吗?-我目前的解决方案是为客户端提供静态方法;类似的东西:
public static <T> void getType(ObliviousClass instance, Key key, Type<T> dest){
dest = (Type<T>)instance.getType(key);
}
I searched around, but wasn't able to find an answer that totally cleared my confusion.
我四处寻找,但无法找到完全消除我困惑的答案。
采纳答案by erickson
Here's a type-safe way to store multiple instances of a given type in a map. The key is that you need to provide a Class
instance when retrieving values in order to perform runtime type-checking, because static type information has been erased.
这是在映射中存储给定类型的多个实例的类型安全方法。关键是您需要Class
在检索值时提供一个实例以执行运行时类型检查,因为静态类型信息已被删除。
class ObliviousClass {
private final Map<Key, Object> map = new HashMap<Key, Object>();
public Object put(Key key, Object value)
{
return map.put(key, value);
}
public <T> T get(Key key, Class<? extends T> type)
{
return type.cast(map.get(key));
}
}
Usage would look like this:
用法如下所示:
oc.put(k1, 42);
oc.put(k2, "Hello!");
...
Integer i = oc.get(k1, Integer.class);
String s = oc.get(k2, String.class);
Integer x = oc.get(k2, Integer.class); /* Throws ClassCastException */
回答by Bohemian
Simply type your class:
只需输入您的课程:
public ObliviousClass <T> {
private Map<Key, Type<T>> map = new HashMap<Key, Type<T>>();
public void putType(Key key, Type<T> type){
map.put(type);
}
public Type<T> getType(Key key){
map.get(key);
}
}
FYI, at this point you have the delegation patternin play.
仅供参考,此时您已经使用了委托模式。
Your example client code would need to declare twoinstances of ObliviousClass
: ObliviousClass<String>
and ObliviousClass<Integer>
.
您的示例客户端代码需要声明:和 的两个实例。ObliviousClass
ObliviousClass<String>
ObliviousClass<Integer>
Edit:
编辑:
If you musthave a mixed bag of Types, you can impose a type on your method, but you'll get a compiler warning for an unsafe cast:
如果您必须拥有多种类型,您可以在您的方法上强加一个类型,但您会收到一个编译器警告,提示不安全的类型转换:
public class ObliviousClass {
private final Map<Key, Type<?>> map = new HashMap<Key, Type<?>>();
public void putType(Key key, Type<?> value) {
map.put(key, value);
}
@SuppressWarnings("unchecked")
public <T> Type<T> getType1(Key key, Class<T> typeClass) {
return (Type<T>)map.get(key);
}
@SuppressWarnings("unchecked")
public <T> Type<T> getType2(Key key) {
return (Type<T>) map.get(key);
}
}
Clients can type the calls to these methods like this:
客户端可以像这样键入对这些方法的调用:
Type<Integer> x = obliviousClass.getType1(key, Integer.class);
Type<Integer> y = obliviousClass.<Integer>getType2(key);
Take your pick as to which one you prefer and use that.
选择您喜欢的那个并使用它。
回答by dave
For those landing on this question these many years later, this is not how Java generics are designed to be used. (I was going to comment but had more to details.)
对于多年后遇到这个问题的人来说,这不是 Java 泛型的设计用途。(我打算发表评论,但有更多细节。)
The generic pattern manages a singleparent class per type ID rather than multiple different classes. If we consider the simpler List<T>, a list of strings OR integers (as List<String> or List<Integer>) is how generics are defined. One class per type. This way, there is a consistent type when the values are referenced. Storing unrelated types would be the same as List<Object>. Only the programmer can know when multiple types are stored and how to retrieve them with casting.
通用图案管理一个单一每类型ID,而不是多个不同的类的父类。如果我们考虑更简单的 List<T>,一个字符串或整数列表(如 List<String> 或 List<Integer>)就是泛型的定义方式。每类一班。这样,引用值时就有了一致的类型。存储不相关的类型与 List<Object> 相同。只有程序员才能知道何时存储了多种类型以及如何通过强制转换来检索它们。
It would be ok to store subclasses to a parent class, but when accessed from the collection without casting, the parent class contact is all that is known. For instance, a generic collection defined with an interface like Map<String, Runnable>. However, only the run() method is visible even if other public methods are added to implementations (unless the programmer explicitly casts). To access additional methods, casting is necessary.
将子类存储到父类是可以的,但是当从集合中访问而不进行强制转换时,父类的联系是全部已知的。例如,使用 Map<String, Runnable> 之类的接口定义的通用集合。但是,即使将其他公共方法添加到实现中,也只有 run() 方法是可见的(除非程序员显式转换)。要访问其他方法,必须进行强制转换。
This is a limitation in Java. A language could be defined to know the L-Value type - even Java. But it wasn't. When new features are added, there are many backward compatible considerations [Sun and] Oracle take into account. Code compiled with generics was designed to run on older JVMs with type erasure. Java uses type erasure at compile time once it has determined that the generics are consistently reference. The bytecode uses Object as if the instance was (sort of) defined as List. If the choice was made to abandon backward compatibility, like Java 9 and 11, then multiple types mighthave been workable.
这是 Java 中的一个限制。可以定义一种语言来了解 L 值类型——甚至是 Java。但事实并非如此。添加新功能时,[Sun 和] Oracle 会考虑许多向后兼容的考虑。使用泛型编译的代码旨在在具有类型擦除功能的旧 JVM 上运行。一旦确定泛型是一致引用,Java 就会在编译时使用类型擦除。字节码使用 Object 就好像实例被(有点)定义为 List。如果选择放弃向后兼容性,例如 Java 9 和 11,那么多种类型可能是可行的。
回答by Juh_
Your ObliviousClass
, by design, doesn't know the parameterized type of the item it holds. So to be type safe, you should avoid such design :-\
您的ObliviousClass
,按照设计,不知道它所持有的项目的参数化类型。所以为了类型安全,你应该避免这样的设计:-\
But if you want to keep it, first things is that you will have to cast. There is no way out of this. But the way you do it is very error prone. For example:
但如果你想保留它,首先你将不得不铸造。没有办法解决这个问题。但是你这样做的方式很容易出错。例如:
oc.put(k1, intType);
oc.put(k2, strType);
Type<Integer> tint = oc.get(k1, Integer.class)
Type<String> tstr = oc.get(k1, String.class) // typo in k2: compile fine
And worst, due to type erasure, it will fail at runtime only once you actually use tstr
, not when you get it from ObliviousClass
.
最糟糕的是,由于类型擦除,它只会在您实际使用时才会在运行时失败tstr
,而不会在您从ObliviousClass
.
So you can improve safety by tracking the parameterized type in some other way. For example, you could associate the key to the type, not losing it:
因此,您可以通过以其他方式跟踪参数化类型来提高安全性。例如,您可以将键与类型相关联,而不是丢失它:
@Value // lombok
class Key<T> {
private int index;
}
class Type<T> {}
class ObliviousClass {
// side note: static final can be public safely
public static final ObliviousClass instance = new ObliviousClass();
private List<Type<?>> map = new ArrayList<>();
public <T> Key<T> appendType(Type<T> type){
// here, I found it nicer that obliviousClass generates and return the key
// otherwise use: "public <T> void appendType(key<T> key, Type<T> type)"
// that binds parametrized type of both key and type arguments
map.add(type);
return new Key<>(map.size() - 1);
}
public <T> Type<T> get(Key<T> key){
return (Type<T>) map.get(key.index);
}
}
Then you can use it such as:
然后你可以使用它,例如:
Type<Integer> intType = new Type<>();
Type<String> strType = new Type<>();
Key<Integer> k1 = ObliviousClass.instance.appendType(intType);
Key<String> k2 = ObliviousClass.instance.appendType(strType);
Type<Integer> t1 = ObliviousClass.instance.get(k1);
Type<String> t2 = ObliviousClass.instance.get(k2);
Type<String> t3 = ObliviousClass.instance.get(k1); // won't compile