Javagenerics – 实现像map这样的高阶函数

我决定在Java中编写一些常见的高阶函数(map,filter,reduce等),这些函数通过generics是类型安全的,而且我遇到了在一个特定函数中匹配通配符的问题。

为了完整,函子接口是这样的:

/** * The interface containing the method used to map a sequence into another. * @param  The type of the elements in the source sequence. * @param  The type of the elements in the destination sequence. */ public interface Transformation { /** * The method that will be used in map. * @param sourceObject An element from the source sequence. * @return The element in the destination sequence. */ public R apply(S sourceObject); } 

令人不安的function就像一个地图 ,但不是转换一个集合,而是转换一个Map (起初我认为它应该被称为mapMap ,但它听起来很愚蠢,我最终称之为remapEntries )。

我的第一个版本是(并且坐下来,因为签名是一个非常怪物):

  /** * 

* Fills a map with the results of applying a mapping function to * a source map. *

* Considerations: *
    *
  • The result map must be non-null, and it's the same object what is returned * (to allow passing an unnamed new Map as argument).
  • *
  • If the result map already contained some elements, those won't * be cleared first.
  • *
  • If various elements have the same key, only the last entry given the * source iteration order will be present in the resulting map (it will * overwrite the previous ones).
  • *
* * @param Type of the source keys. * @param Type of the source values. * @param Type of the result keys. * @param Type of the result values. * @param * @param f The object that will be used to remapEntries. * @param source The map with the source entries. * @param result The map where the resulting entries will be put. * @return the result map, containing the transformed entries. */ public static <SK, SV, RK, RV, MapRes extends Map> MapRes remapEntries(final Transformation<Map.Entry, Map.Entry> f, final Map source, MapRes result) { for (Map.Entry entry : source.entrySet()) { Map.Entry res = f.apply(entry); result.put(res.getKey(), res.getValue()); } return result; }

它似乎是非常正确的,但问题是所使用的转换必须完全匹配类型参数,这使得难以重用兼容类型的map函数。 所以我决定在签名中添加通配符,结果如下:

 public static <SK, SV, RK, RV, MapRes extends Map> MapRes remapEntries(final Transformation<? super Map.Entry, ? extends Map.Entry> f, final Map source, MapRes result) { for (Map.Entry entry : source.entrySet()) { Map.Entry res = f.apply(entry); result.put(res.getKey(), res.getValue()); } return result; } 

但是当我试图测试它时,通配符匹配失败:

 @Test public void testRemapEntries() { Map things = new HashMap(); things.put("1", 1); things.put("2", 2); things.put("3", 3); Transformation<Map.Entry, Map.Entry> swap = new Transformation<Entry, Entry>() { public Entry apply(Entry sourceObject) { return new Pair(sourceObject.getValue().intValue(), sourceObject.getKey()); //this is just a default implementation of a Map.Entry } }; Map expected = new HashMap(); expected.put(1, "1"); expected.put(2, "2"); expected.put(3, "3"); Map result = IterUtil.remapEntries(swap, things, new HashMap()); assertEquals(expected, result); } 

错误是:

 method remapEntries in class IterUtil cannot be applied to given types required: Transformation<? super java.util.Map.Entry,? extends java.util.Map.Entry>,java.util.Map,MapRes found: Transformation<java.util.Map.Entry,java.util.Map.Entry>,java.util.Map,java.util.HashMap 

那么,有关如何解决此问题的任何提示? 或者我应该放弃并为此编写显式循环? ^ _ ^

我想你应该看看Google Guava API 。

在那里你可以找到一个与你的转换类似的function界面。 还有一个带有实用程序方法的类Maps来创建或转换地图实例。

在实施仿制药使用方法时,您还应该考虑PECS 。

这是一个困难的问题。 以下知识完全无用,没有人应该关心:

首先要解决的是swap类型。 输入类型不应该是Entry ,因为它不能接受Entry ,它不是E的子类型。 但是, EE的子类型E E E 。 所以我们的变压器应该把它作为输入。 对于输出,没有外卡,因为变压器无论如何都只能实例化一个具体类型。 我们只想诚实准确地了解可以消费什么以及将要生产什么:

  /* */ Transformation< Entry, Entry > swap = new Transformation< Entry, Entry> () { public Entry apply( Entry sourceObject) { return new Pair( sourceObject.getValue().intValue(), sourceObject.getKey() ); } }; 

注意String是最终的,没有人扩展它,但我担心通用系统不是那么聪明,所以原理问题我做了? extends String 无论如何, ? extends String ,以便以后使用。

然后,让我们考虑一下remapEntries() 。 我们怀疑大多数变形金刚通过它会有与swap类似的声明,因为我们提出了合理的理由。 所以我们最好

 remapEntry( Transformation< Entry, Entry > f, ... 

正确匹配该参数。 从那里,我们计算出源和结果的类型,我们希望它们尽可能通用:

 public static > RM remapEntries( Transformation< Entry, Entry > f, Map source, RM result ) { for(Entry entry : source.entrySet()) { Entry res = f.apply(entry); result.put(res.getKey(), res.getValue()); } return result; } 

RM没有必要,可以直接使用Map Map Map 。 但似乎您希望返回类型与调用者上下文中的result类型相同。 我只是简单地使返回类型void – 已经有足够的麻烦了。

如果swap不使用,这件事会失败? extends ? extends 。 例如,如果输入类型是String-Integer ,那么这很荒谬? extends ? extends他们。 但是你可以使用不同参数类型声明的重载方法来匹配这种情况。

好吧,出于好运,这很有效。 但是,这完全值得。 如果你忘了它,你的生活会好得多,并且使用原始类型,用英语记录参数,在运行时进行类型检查。 问问自己,通用版本能为您买到什么吗? 很少,以极高的价格渲染您的代码完全不可理解。 如果我们明天早上阅读方法签名,那么包括你自己和我自己在内的任何人都无法理解它。 它比正则表达更糟糕。

突然出现了一些东西:如果嵌套通用参数中的通配符不会被捕获,因为它们实际上是类型的一部分,那么我可以在映射中使用反向边界而不是在Transformation中使用它们。

 public static > MapRes remapEntries(final Transformation, Map.Entry> f, final Map source, MapRes result) { for (Map.Entry entry : source.entrySet()) { Map.Entry res = f.apply((Map.Entry)entry); result.put(res.getKey(), res.getValue()); } return result; } 

唯一的问题是我们必须在Transformation.apply进行未经检查的强制Transformation.apply 。 如果Map.Entry接口是只读的 ,那将是完全安全的,所以我们可以交叉指向并希望转换不会尝试调用Map.Entry.setValue

如果调用setValue方法以确保至少运行时类型安全,我们仍然可以传递Map.Entry接口的不可变包装器,该接口引发exception。

或者只是创建一个显式的不可变Entry接口并使用它,但这有点像作弊(因为有两个不同的转换):

 public interface ImmutableEntry { public K getKey(); public V getValue(); } public static > RM remapEntries(final Transformation, Map.Entry> f, final Map source, RM result) { for (final Map.Entry entry : source.entrySet()) { Map.Entry res = f.apply(new ImmutableEntry() { public SK getKey() {return entry.getKey();} public SV getValue() {return entry.getValue();} }); result.put(res.getKey(), res.getValue()); } return result; }