在Java中进行对象转换而不出现未经检查的警告

7
我写了一个类,其中包含一个<String, Object>的映射表。我需要它来保存任意对象,但有时我需要将其中一些对象转换,所以我会做一些类似于以下的操作:
HashMap<String, Object> map = new HashMap<String, Object>();                                                                                 
Object foo = map.get("bar");                                                                                                                                                                                                         
if (foo instanceof HashMap) {                                                                                                                                                                                                        
    ((HashMap<String, Integer>) foo).put("a", 5);                                                                                                                                                                                    
}            

这会发出警告

Stuff.java:10: warning: [unchecked] unchecked cast
found   : java.lang.Object
required: java.util.HashMap<java.lang.String,java.lang.Integer>
        ((HashMap<String, Integer>) foo).put("a", 5);

我怀疑这与泛型的使用有关。我可以使用@SupressWarnings("unchecked")来消除错误,但我想知道是否有更好的方法。或者说,我收到警告意味着我应该重新考虑我的做法。我应该做些什么,还是只使用@SupressWarnings?


4
细节虽小却至关重要:(exception|error) ≠ 警告。 - BalusC
正如ChssPly76所指出的那样,这实际上不应该生成“未经检查的转换”警告。我在Eclipse中进行了测试,它确实没有给出特定的警告。您能否发布一个SSCCE(一个仅演示问题的带有main()方法的类),以便我们更好地理解发生了什么? - BalusC
6个回答

4

编辑(基于问题澄清)

将类型转换为HashMap<String,Integer>(顺便说一句,使用Map而不是HashMap可能是更好的选择)是一个不同的故事。由于类型擦除,在这种情况下很遗憾无法避免未经检查的警告。但是,您可以将其用作非泛型映射:

if (foo instanceof Map) {                                                                                                                                                                                                        
  ((Map) foo).put("a", 5);                                                                                                                                                                                    
}

你显然需要使用“gets”来实现,但会失去(感知上的)类型安全,但不会有未检查的警告。
这个故事肯定还有更多。下面是代码:
Map<String, Object> map = Maps.newHashMap(); // or new HashMap<String, Object>();
Object foo = map.get("bar");
if (foo instanceof Widget) {
  ((Widget) foo).spin();
}

这个代码并不会给我生成未检查的警告。我也想不出为什么会有警告。如果你预先知道"bar"总是返回一个widget,那么这样做:

Widget widget = (Widget) map.get("bar");
widget.spin();

这也完全可以正常工作。我在这里漏掉了什么吗?


2
正在输入类似这样的内容。我怀疑 OP 正在传递或分配给一个原始的 Map,然后,当然,就会生成未经检查的警告。 - Alexander Pogrebnyak
哦,你说得很对。抱歉,我实际上没有测试这个片段。经过更多的测试,我认为问题只会在尝试转换为泛型时出现。我会发布一个新的片段来触发警告。 - swampsjohn
关于您的编辑:未参数化的Map将会产生一个新的有关原始类型需要进行参数化的警告。不确定哪一个更令人讨厌 ;) - BalusC
@BalusC - 不应该。你没有创建一个新的映射实例,而是进行了强制转换。在Eclipse / java 1.5中对我有效。 - ChssPly76

4
如果其他方法(多态实现,类型转换)不适用,则可以按照《Effective Java》第三版中第33条:考虑类型安全的异构容器所述实现一个异构容器。容器的责任是确保类型安全。
public class Container{
  private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
  public <T> void set(Class<T> klass, T thing) {
    favorites.put(klass, thing);
  }
  public <T> T get(Class<T> klass) {
    return klass.cast(favorites.get(klass));
  }
}

你的示例中存在问题,因为你使用了一个 HashMap<K,V> 作为入口类型。这不能用类字面量表示为类型标记。因此,你需要实现某种形式的超级类型标记
public abstract class TypeReference<T> {}

然后,您的客户端代码将为每个所需的类型令牌扩展TypeReference:

TypeReference<?> typeToken = new TypeReference<HashMap<String, Integer>>{};

类型信息在运行时是可访问的。容器实现必须针对类型令牌(TypeReference子类)的实际类型参数进行类型检查。
这是一个完整的解决方案,但需要大量工作来实现。我所知道的任何集合库都不支持带有类型引用的容器。

这是一个有趣的方法 (+1),但是我发现它有两个问题:(1) 它要求我预先知道我要请求的值的类型,这并不总是适用的;(2) 在某个级别上(大约在2-3之间,您的容差可能会有所不同),这开始看起来比直接转换或 @SuppressWarnings 更丑陋。 (1) 从技术上讲可以解决,因为类型信息是保留的,但是这又涉及到处理ParameterizedType等不好看的内容。 - ChssPly76
这就是Java。为什么我们不能拥有有用的类型/方法/字段字面量,而只有一次性的类字面量呢?非常感谢Sun。 - Thomas Jung
类型引用很丑陋。但即使是Sun也必须使用它们。我忘记了确切的位置,但在JEE 6中的某个地方,它被用于获取某些注释类型。这相当丑陋。 - Thomas Jung

1
也许我收到警告的事实意味着我应该重新考虑我的做法。
你说得对。逻辑上的步骤是创建一个 Map<String, Widget> 而不是 Map<String, Object>。如果由于某种原因这不是一个选择,那么你可以做如下操作:
Widget w = Widget.class.cast(foo);
w.spin();

这不再产生编译器警告,但这仍然不一定意味着您的混合对象的Map是一个好的实践。

编辑:正如ChssPly76指出的那样,这实际上不应该生成"unchecked cast"警告。我在Eclipse中进行了测试,它确实没有产生特定的警告。您能发布一个SSCCE(一个只包含main()的类,纯粹演示问题)吗,以便我们更好地了解情况?

编辑2:所以您正在使用可能包含通用结构(例如映射)的映射。 这就解释了一切。嗯,除了重新设计结构外,我没有看到其他选择,只能使用 @SuppressWarnings("unchecked") 注释。


1

我认为根本问题在于Object类

HashMap<String, Object> map;

如果您想要移除类型转换警告,那么您需要指定一个基类/接口。
例如,您可以这样做。
Map<String, Animal> map = new LinkedHashMap<String, Animal>(); 
Animal pet = map.get("pet"); 
pet.feed();

替代

Map<String, Object> map = new LinkedHashMap<String, Object>();
Object pet = map.get("pet");
if (pet instance of Dog)
{
        ((Dog)pet).feedDog();
}
if (pet instance of Cat)
{
        ((Cat)pet).feedCat();
}

地图的主要用途是将相似的事物放在一起。

如果您想真正地将不同的东西放在一起,考虑编写一个新类。


1

如果您的Map保存的是相同类型的对象(例如所有小部件),那么您可以使用Map<String,Widget>来消除强制转换和警告。

然而,如果您保存的是任意类型的对象,则说明您有更深层次的设计问题。如果您知道基于名称对象将是什么类型(例如,“bar”始终会得到一个Widget),则考虑使用具有名为Widget getBar()的方法的对象,而不是Map

如果您不知道从地图中获取“bar”时它将是什么,那么您就有了更深层次的设计问题,并且应该考虑使用一些面向对象的原则来减少耦合。


只有一些是小部件。 "bar" 只是一个例子,任何键都可以有一个小部件。 - swampsjohn
1
我强烈反对您所说的使用多态映射值需要存在“更深层次的设计问题”的观点。有许多情况可能需要使用它,比如EAV / dynabean / ResultSet这些东西可能是最常见的情况。当然,并不总是必须以这种方式通过API公开事物,但这就是它们在幕后的基本映射。 - ChssPly76
是的,我正在做一些类似于ResultSet的事情。 - swampsjohn

0

不确定您如何使用这些对象,但有:

for(Map.Entry<String, Widget> entry = map.entrySet())
{
     entry.getValue().spin();
}

现在那个会触发未经检查的转换警告。 - ChssPly76

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接