如何安全地返回通用对象

时间:2010-11-12 19:13:13

标签: java generics

我正在做一些实验,试图了解返回泛型的问题。以下程序运行无误,并在结尾打印“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");
     }
    }
}

4 个答案:

答案 0 :(得分:4)

由于Java type erasureMap<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不知道它应该在运行时包含哪些类型(由于擦除),因此它在运行时接受StringString映射就好了。你有问题的地方就是这样做:

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。

有人知道更好的解决方案吗?