【问题标题】:Casting from Object in Java without getting an unchecked warning从 Java 中的对象强制转换而不会收到未经检查的警告
【发布时间】:2009-11-21 02:58:53
【问题描述】:

我写了一个类,其映射为<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?

【问题讨论】:

  • 小而重要的细节:(异常|错误)!=警告。
  • 正如 ChssPly76 所指出的,这实际上不应该产生“未经检查的演员表”警告。我在 Eclipse 中对其进行了测试,它确实没有给出特定的警告。您能否发布一个 SSCCE(一个带有 main() 的类,纯粹展示问题),以便我们更好地了解发生了什么?

标签: java generics casting


【解决方案1】:

已编辑(基于问题说明)

投射到HashMap&lt;String, Integer&gt;(顺便说一句,使用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) map.get("bar");
widget.spin();

也可以很好地工作。我在这里遗漏了什么吗?

【讨论】:

  • 正在按照这些思路输入一些内容。我怀疑 OP 正在传递或分配给原始 Map ,然后当然会生成未经检查的警告。
  • 呵呵,你说的很对。对不起,我实际上并没有测试sn-p。经过更多测试后,我认为仅在尝试转换为泛型时才会出现问题。我将发布一个会触发警告的新 sn-p
  • 至于您的编辑:未参数化的 Map 将对需要参数化的原始类型发出新警告。不知道在旁观者眼中哪个更烦人;)
  • @BalusC - 不应该。您不是在创建新的地图实例,而是在投射。它适用于 Eclipse / java 1.5
【解决方案2】:

如果其他所有内容(多态实现、强制转换)都不适用,您可以按照“Effective Java”第 3 版中Item 33: Consider type-safe heterogeneous containers 中的描述实现异构容器。容器的职责是确保类型安全。

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&lt;K,V&gt; 作为条目类型。这不能用类文字表示为类型标记。所以你必须实现某种形式的super type token

public abstract class TypeReference<T> {}

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

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

类型信息可在运行时访问。然后容器实现必须针对类型标记(TypeReference 的子类)的actual type parameters of 进行类型检查。

这是一个完整的解决方案,但需要执行大量工作。我所知道的任何集合库都不支持带有类型引用的容器。

【讨论】:

  • 这是一种有趣的方法 (+1),但我发现它有两个问题:(1) 它需要我事先知道我要请求的值的类型,这并不总是适用并且 (2) 在某个级别(大约 2-3,您的容忍度可能会有所不同)这开始看起来比直接演员或@SuppressWarnings 丑陋得多。 (1) 在技术上可以解决,因为类型信息被保留了,但是这又陷入了处理 ParameterizedType 的丑陋之中。
  • 嗯,这是java。为什么我们不能拥有有用的类型/方法/字段文字但一次性类文字?非常感谢孙。
  • 类型引用很难看。但即使是 Sun 也必须使用它们。我忘记了确切的位置,但在 JEE 6 的某个地方,它用于获取一些注释类型。这很丑。
【解决方案3】:

如果您的Map 持有相同类型的对象(例如所有小部件),那么您可以使用Map&lt;String,Widget&gt; 来消除强制转换和警告。

但是,如果您持有任意类型的对象,那么这表明您有更深层次的设计问题。如果您知道对象将基于名称的类型(例如,“bar”总是为您提供一个小部件),那么请考虑使用具有称为 Widget getBar() 而不是 Map 的方法的对象。

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

【讨论】:

  • 只有一些是小部件。 “bar”只是一个例子,任何键都可以有一个小部件
  • 我强烈不同意您的观点,即使用多态映射值必然存在“更深层次的设计问题”。有很多场景可能是必要的——像 EAV / dynabean / ResultSet 这样的东西可能是最常见的。诚然,事情并不总是必须以这种方式通过 API 公开,但这就是它们在幕后的内容 - 您的基本地图。
  • 是的,我正在做一些类似于 ResultSet 的事情
【解决方案4】:

或者我收到警告的事实意味着我应该重新考虑我在做什么。

你明白了。合乎逻辑的步骤是创建Map&lt;String, Widget&gt; 而不是Map&lt;String, Object&gt;。如果由于某种原因这不是一个选项,您可以执行以下操作:

Widget w = Widget.class.cast(foo);
w.spin();

这不再给出编译器警告,但这并不一定意味着您的 Map 混合对象是一个好习惯。

编辑:正如 ChssPly76 指出的那样,这实际上不应该产生“未经检查的演员表”警告。我在 Eclipse 中对其进行了测试,它确实没有给出特别的警告。您能否发布一个SSCCE(带有main() 的课程纯粹是为了说明问题)以便我们更好地了解发生了什么?

编辑 2:因此您使用的地图可能包含地图等通用结构。这就解释了这一点。好吧,除了重新设计结构之外,我没有看到任何其他选择,只能使用 @SuppressWarnings("unchecked") 注释。

【讨论】:

    【解决方案5】:

    我认为根本问题是对象类

    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();
    }
    

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

    如果你真的想放不同的东西,那么考虑写一个新的类。

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 现在实际上会触发未经检查的强制转换警告。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-05
      • 1970-01-01
      • 1970-01-01
      • 2012-10-13
      相关资源
      最近更新 更多