我正在做一些实验,试图了解返回泛型的问题。以下程序运行无误,并在结尾打印“FOO gets BAR”。有谁能解释为什么? GetMap()成员不安全地强制转换HashMap< Integer,Integer>到Map< K,V>,在测试用例中是Map< String,String>。我所有的阅读建议我应该得到一个ClassCastException,但我没有得到一个。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GenericTrial
{
public static <K,V> Map<K,V> GetMap()
{
return (Map<K,V>)new HashMap<Integer,Integer>();
}
public static void main(String[] args)
{
try
{
Map<String,String> m = GetMap();
m.put("FOO", "BAR");
System.out.println("FOO gets " + m.get("FOO"));
}
catch (Exception e)
{
System.out.println("Got exception");
}
}
}
答案 0 :(得分:4)
由于Java type erasure,Map<K, V>
和Map<T, U>
之间在运行时没有区别。
泛型类型是纯粹的编译时概念。
答案 1 :(得分:3)
Java泛型使用type erasure,它是一个编译时构造。这一行:
return (Map<K,V>)new HashMap<Integer,Integer>();
将编译为:
return (Map)new HashMap();
您将在C#中获得异常,因为通用信息在运行时存在并且已经过正确验证。
(这是我不再使用Java的众多原因之一。)
答案 2 :(得分:2)
您的代码工作的原因是因为您在返回之前没有在Map<Integer, Integer>
中放置任何内容。由于Map
不知道它应该在运行时包含哪些类型(由于擦除),因此它在运行时接受String
到String
映射就好了。你有问题的地方就是这样做:
public static <K, V> Map<K, V> getMap() {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(1, 1);
return (Map<K, V>) map;
}
public static void main(String[] args) {
Map<String, String> map = getMap();
for (String key : map.keySet()) { // ClassCastException
System.out.println(map.get(key));
}
}
答案 3 :(得分:0)
我以为我会回来并发布一些我在调查过程中学到的东西。我现在知道有几种方法(部分地,部分地)解决类型擦除问题。
这里讨论一个:“Using TypeTokens to retrieve generic parameters”。我没有详细说明这一点;根据我的理解,要点是在构造一般对象的实例时的,例如LinkedList&lt; String&gt;,有关泛型类型(String)的信息尚未被删除,可以捕获并保存以供将来参考。这并不适用于我的需求。
我需要的是一种指定这样的类的方法:
class Foo
{
public <T> void setList(List<T> list, ...);
public <T> List<T> getList(...);
public <T> void setSet(Set<T> set, ...);
public <T> Set<T> getSet(...);
public <K,V> void setMap(Map<K,V> map, ...);
public <K,V> Map<K,V> getMap(...);
}
这样我可以说:
Map<String,Integer> map = fooInstance.getMap(...);
otherFooInstance.setMap(map);
其中T,K和V可以是任何类型,并且 - 因为我正在创建专门供客户使用的界面 - 所以安全地完成所有操作。作为简化,我愿意将类型T限制为非泛型类型 - 没有List&lt; Map&lt; String,Set&lt; Integer&gt;&gt;,Float&gt;玩意儿。
我提出的躲闪只取得了部分成功:
对于setXXXX(),我必须传入集合类的实例。类型擦除阻止我从集合本身获取列表类型,但我可以获取集合成员的类型。例如,以下例程可以传递给List或Set:
<T> Type GetType(Collection<T> c) {
Type t = null;
if (!c.isEmpty()) {
t = c.iterator().next().getClass();
}
return t;
}
并且Map可以传递给它:
<K,V> Type[] GetTypes(Map<K,V> m) {
Type[] t = null;
if (!m.isEmpty()) {
Map.Entry<K,V> entry = m.entrySet().iterator().next();
t = new Type[]{ entry.getKey().getClass(),
entry.getValue().getClass() };
}
return t;
}
当然,如果集合实例不包含任何条目,那么这个噱头就会失败......但在这种情况下,无论如何都不需要做任何事情。这部分也不错。
在getXXXX()方面,事情不太令人满意。我想出的最好的事情就是传递我希望返回的集合类型。
Set<String> set = fooInstance.getSet(String.class);
对于地图,我提供了两种方法:
Map<String,Long> map = fooInstance.getmap(String.class, Long.class);
Map<String,Long> map = fooInstance.getmap(new Type[]{String.class, Long.class});
显然,getXXXX()和setXXXX()函数,因为他们知道我期望的类型,可以处理类型,也可以不处理,在这种情况下,它们会返回错误值或抛出异常。 / p>
然而,问题在于它不安全。我可以写:
Set<String> set = fooInstance.getSet(String.class);
Set<Integer> otherSet = fooInstance.getSet(String.class);
并没有得到例外。我可以要求将要返回的集合对象传递给getXXXX()函数...并且它被填充,所以我可以弄清楚它包含哪些类型......但是yuk。
有人知道更好的解决方案吗?