【问题标题】:Java generic function: how to return Generic typeJava泛型函数:如何返回泛型类型
【发布时间】:2009-12-24 16:58:50
【问题描述】:

这是一个 Java 泛型模式:

public <T> T getResultData(Class<T> resultClass, other_args) { 
   ...
   return resultClass.cast(T-thing);
}

典型的调用如下所示:

   DoubleBuffer buffer;
   buffer = thing.getResultData(DoubleBuffer.class, args);

当所需的返回类型本身是通用的时,我一直无法弄清楚如何干净地使用此模式。为了“具体”,如果这样的函数想要返回 Map&lt;String,String&gt; 怎么办?因为你不能得到一个泛型的类对象,当然,唯一的选择是传递Map.class,然后你需要一个演员和一个@SuppressWarning

一个最终的电话如下:

Map<String, String> returnedMap;
returnedMap = thing.getResultData(Map.class, some_other_args);

现在又回到了需要强制转换和抑制警告的状态。

我想人们可以从java.lang.reflect.Type 家族而不是Class 中获取一些东西,但这些并不是特别容易编造出来的。看起来像:

class Dummy {
 Map<String, String> field;
}

...

Type typeObject = Dummy.class.getField("field").getGenericType();

鉴于此,被调用函数可以提取基本类型并将其用于强制转换或 newInstance-ing,但虚拟字段业务确实看起来很丑。

请注意,像这样的函数并不总是调用newInstance。显然,如果他们这样做了,他们就不需要打电话给resultClass.cast

【问题讨论】:

    标签: java generics


    【解决方案1】:

    您无法在标准 Java API 中做到这一点。但是,有一些实用程序类可以从泛型类中获取Type。例如,在 Google Gson(将 JSON 转换为完全有价值的 Javabeans,反之亦然)中有一个 TypeToken 类。您可以按如下方式使用它:

    List<Person> persons = new Gson().fromJson(json, new TypeToken<List<Person>>() {}.getType());
    

    这样的构造正是您所需要的。您可以找到here 源代码,您可能会发现它也很有用。它只需要一个额外的getResultData() 方法,它可以接受Type

    【讨论】:

      【解决方案2】:

      嗯,。这是一个丑陋的(部分)解决方案。你不能泛型参数化一个泛型参数,所以你不能写

      public <T<U,V>> T getResultData ...
      

      但是你可以返回一个参数化的集合,例如一组

      public < T extends Set< U >, U > T getResultData( Class< T > resultClass, Class< U > paramClass ) throws IllegalAccessException, InstantiationException {
              return resultClass.newInstance();
          }
      

      如果 U-param 存在于类签名中,您可以在哪里删除它。

      【讨论】:

      • 很有趣,它也适用于两个参数(通过扩展Map)。但是,老实说,在这个和@SuppressWarnings 之间进行选择,我每次都会选择后者:-)
      • 我同意 - 我确实说过“丑陋”;)
      【解决方案3】:

      是的,我认为这是不可能的。

      想象一下这个场景。您有一个包含要读取的序列化对象的文件。如果您将函数设为通用,则无需强制转换即可使用它。但是假设你有一个 Map 序列化到一个文件中。

      当您反序列化它时,无法知道原始的通用映射类型是什么。就java而言,它只是一个Map

      我在列表中遇到了这个问题,这很烦人。但我实际上最终做的是创建一个通用的“转换集合”类,它接受一个集合和一个“转换器”(可以是匿名内部类型)。

      然后,当您遍历转换集合时,每个项目都会被转换。这解决了警告问题。只是为了消除警告需要做很多工作,但我认为这不是我提出该框架的主要原因。您还可以做更强大的事情,例如获取文件名的集合,然后编写一个转换器来加载文件中的数据,或者进行查询,或其他任何事情。

      【讨论】:

        【解决方案4】:

        所以如果我理解正确,你真正想做的是(非法伪语法):

        public <T, S, V> T<S, V> getResultData(Class<T<S, V>> resultClass, other_args) { 
          ...
          return resultClass.cast(T-thing);
        }
        

        你不能,因为你不能进一步参数化泛型类型T。与java.lang.reflect.* 混在一起无济于事:

        1. 没有Class&lt;Map&lt;String, String&gt;&gt; 这样的东西,因此无法动态创建它的实例。
        2. 实际上没有办法声明有问题的方法返回T&lt;S, V&gt;

        【讨论】:

        • @ChssPly76:碰巧有一个用于 Map 的 GenericType 对象。如果你有一个 Map 类型的字段,并且你调用 Field.getGenericType,你会得到它。然而,获得其中一个的唯一方法确实是通过对字段或返回类型的反射来获取它。
        • 你的意思是ParameterizedType。是的,有,是的,您可以从字段/方法声明中获取它。不,它Class 相同,因此不会以任何方式帮助您:您不能强制转换,也不能创建实例相应的类也只使用ParameterizedType
        • 嗯,实际上,这并不像看起来那么不可能。被调用的函数可以在 ParameterizedType 上调用 getRawType() 用于 newInstance/cast 目的。它只是丑陋,但看起来就像它得到的一样好。
        【解决方案5】:

        如果你想使用类型安全的容器类,你可以使用TypeLiteral。它们在Guice 中用于提供类型安全的绑定。有关更多示例和TypeLiteral 类,请参阅 Guice 源代码。

        顺便说一句,你不需要演员表,如果你打电话给resultClass.newInstance()

        public <T> T getResultData(Class<T> resultClass, other_args) {       
           ...
           return resultClass.newInstance();
        }
        

        【讨论】:

        • 除非您无法获取并因此传递 Class 实例以获取像 Map&lt;String, String&gt; 这样的泛型类,所以这对 OP 的问题毫无帮助。
        • 是的。这正是我们到达这里的原因。
        • 语言不支持它们,但您使用由 Neal Gafter 发明的 TypeLiteral。 gafter.blogspot.com/2006/12/type-literals.html
        • 如果您将 TypeLiteral 参考编辑为您的实际答案,我会赞成它。
        【解决方案6】:

        如果您并不真正需要地图的确切泛型类型,并且您的代码不依赖该类型,但您只是对可以编写的警告感到恼火:

        Map<?, ?> returnedMap;
        returnedMap = thing.getResultData(Map.class, some_other_args);
        

        然后您可以使用任何非类型参数化的 map 方法,如 containsKey、clear、iterator 等,或遍历 entrySet 或将返回的Map 传递给任何泛型方法,一切都会是类型安全的。

        Map<?, ?> returnedMap;
        returnedMap = thing.getResultData(Map.class, some_other_args);
        
        Object myKey = ...;
        
        Object someValue = returnedMap.get(myKey);
        
        for (Iterator<? extends Map.Entry<?,?>> it = returnedMap.entrySet().iterator(); it.hasNext(); )
          if (it.next().equals(myKey))
            it.remove();
        
        for (Map.Entry<?,?> e : returnedMap.entrySet()) {
          if (e.getKey().equals(myKey))
            e.setValue(null); // if the map supports null values
        }
        
        doSomething(returnedMap);
        
        ...
        
        <K,V> void doSomething(Map<K,V> map) { ... }
        

        实际上,您可以在不知道其键或值类型的情况下以类型安全的方式对 Map 执行许多操作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-10-26
          • 2016-12-24
          • 1970-01-01
          相关资源
          最近更新 更多