【问题标题】:Java Generics - Mixed Object MapsJava 泛型 - 混合对象映射
【发布时间】:2017-10-19 16:18:30
【问题描述】:

我仍在习惯 Java 泛型,但我目前正在更新在泛型之前编写的应用程序以使用最新版本的 java。

问题是代码没有考虑到类型安全!

我们有一大堆地图,基本上包含各种对象类型,包括字符串。例如:

Map map = new HashMap();
map.put("key1", "String1");
map.put("key2", new Date());
map.put("key3", new CutsomClass());

现在我仍在努力寻找处理这些问题的最佳方法,而无需重构大量代码。目前无法进行重构。

目前我看不到 Map<String, Object> 之后的任何内容,尽管 Map<String, ? super Object> 有效,但我认为它本质上是一样的?

【问题讨论】:

  • Map<String, Object> map = new HashMap<>();
  • Map<Object, Object> map = new HashMap<>() 如果键和值都包含各种对象类型。
  • 谢谢小伙子...我原来的帖子遗漏了一些代码。这就是我基本上一直在做的事情......只是感觉不“正确”
  • 在我看来设计很糟糕。这应该是一个新类,而不是地图,其中包含各种项目的字段。
  • @RameshKC,与 OP 的香草 Map 相比,Map 如何提供任何类型的安全性?我认为 Michael 是正确的,因为它已经完成了 OP 用例的一半。

标签: java dictionary generics hashmap


【解决方案1】:

我仍在努力寻找处理这些问题的最佳方法 重构大量代码

所以根本不要改变它们。原始类型——即——泛型类型——在技术上仍然有效。这并不理想,它会生成一个编译器警告,但代码可以正常工作(嗯,工作和以往一样好)。

所有类都扩展Object,因此您可以将任何您想要的放入以下映射:

Map<String, Object> map = new HashMap<>();

您可以获得额外的保证,即密钥是一个字符串,因此它比使用原始类型要好一些。

但基本上,如果您无法定义键或值的类型,您应该尽量避免使用映射。

【讨论】:

  • @Michael 是的。具有所有(缺点)优点。
  • 感谢@Michael,这几乎是我的理解以及我目前的实施方式我只是不确定是否有更好的方法......重构地图以使用更好的解决方案将是理想的,但鉴于此代码已接近 15 年,目前有数百个这样的代码!
  • 事实上,它是专门的迁移,因为他们可能有办法实现在技术上向后兼容的具体类型。请参阅section 4.7 中的斜体注释以获得更广泛的讨论。 OP 可以更改 Map 的声明并编译程序这一事实几乎正是他们选择类型擦除的原因。
  • @Radiodef 好一个。 Project Valhalla 绝对是一个有趣的提议。
  • @Radiodef, "... MapMap&lt;String, Object&gt; 之间存在隐式转换" 不,类型擦除(不是转换)转到 Map,相当于 Map&lt;Object, Object&gt;String 类型参数也被删除。
【解决方案2】:

截至目前,您只能将原始类型Map 替换为Map&lt;String, Object&gt;;但该类型信息接近“无用”。除非您重构整个组件以处理 不同 地图对象,否则您无能为力。当然,您可以摆脱类型警告,但每次访问 Map 值时您仍然需要进行instanceof 检查。

解决这个问题:假设“值”类型的数量是已知的并且相当小,您可以创建一堆帮助方法,如下所示:

public Map<String, Date> extractDates(Map<String, Object> allValues) {
...

此方法可以在 allValues 映射周围实现一个“包装器”,提供那些实际上是 Date 对象的映射条目。

含义:您保留当前的地图对象,但在该地图上提供“更专业的”视图。这允许您编写 new 代码来利用额外的类型信息。

当然,这不是免费的。它增加了一定的复杂性,并且定义此类“视图地图”的确切细节可能会变得相当复杂。

【讨论】:

    【解决方案3】:

    在正确使用泛型的情况下,您几乎无法实现完整的静态类型检查。但是,我不认为您必须完全放弃类型检查并在这种情况下依赖运行时强制转换。我认为你可以朝着那个方向迈出一步。

    我假设这是代码用于一般存储的通用映射,可能用于持久性或属性等。如果是这种情况,那么您至少可以执行以下操作:

    class AnyMap<K>  {
        final Map<K,Object> map;
    
        public AnyMap(Map<K, Object> map) {
            this.map = map;
        }
    
        public <V> Map<K,V> as(Class<V> theClass) {
            return (Map<K,V>) map;
        }
    }
    
    public void test() {
        AnyMap<String> commonMap = new AnyMap<>(Collections.EMPTY_MAP);
    
        // Use this one as a Date map.
        Map<String,Date> dateMap = commonMap.as(Date.class);
        // This one as a String map.
        Map<String,String> stringMap = commonMap.as(String.class);
    
    }
    

    这是一种Map 持有者,然后可以将地图作为具有正确边界的适当通用对象提供。希望您会发现某些模块将专门为Dates 使用公共映射,而其他模块将使用Strings。在诸如此类的区域中,您可以使用 Map&lt;X,Y&gt; as(...) 为该模块/部分提供经过适当静态检查的映射,并专门在该代码部分中使用它。

    【讨论】:

    • 此方法有效,除非 Map 中的某些值本身是通用的。
    【解决方案4】:

    正如迈克尔建议的那样Map&lt;String, Object&gt; map = new HashMap&lt;&gt;(); 是第一步。
    但是,它假定您只有 String 作为键,您可能需要转换从地图中获取的对象。

    我认为第二步是将该地图拆分为多个地图,声明更具体的类型:

    Map<String, Date> mapDates = new HashMap<>(); 
    Map<String, String> mapStrings = new HashMap<>(); 
    Map<String, CustomClass> mapCutsomClasses = new HashMap<>(); 
    

    【讨论】:

    • 这三张地图都属于同一类型。
    • @Lew Bloch 快速的答案,快速的错误。谢谢你的评论。
    【解决方案5】:

    不要使用原始类型...请参阅 this 了解原因...

    现在,你可以把它分解成如下,这样你就可以得到它了:

    您的 map&lt;K,V&gt; 仅作为键字符串,因此 K = string 将是正确的,另一方面,“String1”、new Date() 和 new Custom Class 似乎没有任何共同点,但是等等,所有的类在 java 中实际上是继承 Object 类...这意味着你可以做 V=Object

    现在您的地图可以声明为Map&lt;String, Object&gt; 和所有这些

    map.put("key1", "String1");
    map.put("key2", new Date());
    map.put("key3", new CutsomClass());
    

    会好的

    【讨论】:

    • “不要使用原始类型”他试图不这样做!这就是他试图现代化的代码
    【解决方案6】:

    从非泛型(例如旧 Java)到泛型的转换可以是真正的 PITA。最简单的方法是将每个 Map、Set、List 替换为相关的泛型,例如

    Map<String, Object> map = new HashMap<>();
    

    但前提是映射对象也不是泛型(或者泛型仅用于读取)。例如。如果你的代码中有类似的东西

    Map hasmap = new HashMap();
    hashmap.put("blah",123)
    map.put("keyX",hashmap);
    

    在这种情况下,当你找到map getter,而你在原始代码中有一个put,你会遇到很多麻烦:

    Map hashmap = (HashMap)(map.get("keyX"));
    Integer value = hashmap.get("blah");
    hashmap.put("otherkey","mooo");
    

    在这种情况下,你不能有清晰的代码:如果你使用问号,你会遇到错误,就像在

    Map<?,?> hashmap = (HashMap<?,?>)(map.get("keyX"));
    Integer value = (Integer)hashmap.get("blah"); // this works 
    hashmap.put("otherkey","mooo"); // this crashes
    

    因此您有两种选择:重写代码(以避免警告),或强制 Objects 上的所有内容,并收到 Unchecked Casts 警告。

    @SuppressWarnings("unchecked")
    Map<Object,Object> hashmap = (HashMap<Object,Object>)(map.get("keyX"));
    Integer value = (Integer)hashmap.get("blah"); // this works 
    hashmap.put("otherkey","mooo"); // this works too
    

    关于问号和泛型的更多详细信息可以在这里找到:What is the difference between ? and Object in Java generics?

    【讨论】:

      【解决方案7】:

      看起来您想快速移植旧代码,并且您希望将旧代码移向严格的类型安全而不重构大型代码库。使用以下方法移植旧代码,以保留旧代码:

      Map<String, Object> oldMapPorted = new HashMap<>();
      

      在这个应用程序中编写的新代码可以使用这样的技术来实现严格的类型安全:

      Map<String, Date> newMapDates = new HashMap<>(); 
      Map<String, String> newMapStrings = new HashMap<>(); 
      Map<String, CustomClass> newCutsomClasses = new HashMap<>();
      

      可以为将来的编辑和增强创建一个新类,而旧代码仍然像往常一样具有潜在的不稳定性。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-07-09
        • 2017-10-13
        • 2019-01-22
        • 1970-01-01
        • 2015-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多