for*_*ran 8 java generics functional-programming wildcard matching
我决定用Java编写一些常见的高阶函数(map,filter,reduce等),这些函数通过泛型是安全类型的,并且我遇到了在一个特定函数中匹配通配符的问题.
为了完整,函子接口是这样的:
/**
* The interface containing the method used to map a sequence into another.
* @param <S> The type of the elements in the source sequence.
* @param <R> The type of the elements in the destination sequence.
*/
public interface Transformation<S, R> {
/**
* 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);
}
Run Code Online (Sandbox Code Playgroud)
令人不安的功能就像一个地图,但不是转换一个集合,而是转换一个Map(起初我认为它应该被调用mapMap,但它听起来很愚蠢,我最终调用它remapEntries).
我的第一个版本是(并且坐下来,因为签名是一个非常怪物):
/**
* <p>
* Fills a map with the results of applying a mapping function to
* a source map.
* </p>
* Considerations:
* <ul>
* <li>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).</li>
* <li>If the result map already contained some elements, those won't
* be cleared first.</li>
* <li>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).</li>
* </ul>
*
* @param <SK> Type of the source keys.
* @param <SV> Type of the source values.
* @param <RK> Type of the result keys.
* @param <RV> Type of the result values.
* @param <MapRes>
* @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<RK, RV>> MapRes remapEntries(final Transformation<Map.Entry<SK, SV>, Map.Entry<RK,RV>> f, final Map<SK, SV> source, MapRes result) {
for (Map.Entry<SK, SV> entry : source.entrySet()) {
Map.Entry<RK, RV> res = f.apply(entry);
result.put(res.getKey(), res.getValue());
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
它似乎是非常正确的,但问题是所使用的转换必须完全匹配类型参数,这使得难以重用兼容类型的map函数.所以我决定在签名中添加通配符,结果如下:
public static <SK, SV, RK, RV, MapRes extends Map<RK, RV>> MapRes remapEntries(final Transformation<? super Map.Entry<? super SK, ? super SV>, ? extends Map.Entry<? extends RK, ? extends RV>> f, final Map<SK, SV> source, MapRes result) {
for (Map.Entry<SK, SV> entry : source.entrySet()) {
Map.Entry<? extends RK, ? extends RV> res = f.apply(entry);
result.put(res.getKey(), res.getValue());
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
但是当我试图测试它时,通配符匹配失败:
@Test
public void testRemapEntries() {
Map<String, Integer> things = new HashMap<String, Integer>();
things.put("1", 1);
things.put("2", 2);
things.put("3", 3);
Transformation<Map.Entry<String, Number>, Map.Entry<Integer, String>> swap = new Transformation<Entry<String, Number>, Entry<Integer, String>>() {
public Entry<Integer, String> apply(Entry<String, Number> sourceObject) {
return new Pair<Integer, String>(sourceObject.getValue().intValue(), sourceObject.getKey()); //this is just a default implementation of a Map.Entry
}
};
Map<Integer, String> expected = new HashMap<Integer, String>();
expected.put(1, "1");
expected.put(2, "2");
expected.put(3, "3");
Map<Integer, String> result = IterUtil.remapEntries(swap, things, new HashMap<Integer, String>());
assertEquals(expected, result);
}
Run Code Online (Sandbox Code Playgroud)
错误是:
method remapEntries in class IterUtil cannot be applied to given types
required: Transformation<? super java.util.Map.Entry<? super SK,? super SV>,? extends java.util.Map.Entry<? extends RK,? extends RV>>,java.util.Map<SK,SV>,MapRes
found: Transformation<java.util.Map.Entry<java.lang.String,java.lang.Number>,java.util.Map.Entry<java.lang.Integer,java.lang.String>>,java.util.Map<java.lang.String,java.lang.Integer>,java.util.HashMap<java.lang.Integer,java.lang.String>
Run Code Online (Sandbox Code Playgroud)
那么,有关如何解决这个问题的任何提示?或者我应该放弃并为此编写显式循环?^ _ ^
小智 5
我想你应该看看Google Guava API.
在那里你可以找到一个与你的转换类似的功能界面.还有一类地图与实用的方法来创建或变换图像的情况.
在实施仿制药使用方法时,您还应该考虑PECS.
这是一个困难的问题.以下知识完全无用,没有人应该关心:
首先要解决的是类型swap.输入类型不应该是Entry<String,Number>,因为它不能接受Entry<String,Integer>,这不是一个子类型E<S,N>.但是,E<S,I>是一个子类型E<? extends S,? extends N>.所以我们的变压器应该把它作为输入.对于输出,没有外卡,因为变压器无论如何都只能实例化一个具体的类型.我们只想诚实准确地了解可以消费什么以及将要生产什么:
/* */ Transformation<
Entry<? extends String, ? extends Number>,
Entry<Integer, String>
> swap
= new Transformation<
Entry<? extends String, ? extends Number>,
Entry<Integer, String>> ()
{
public Entry<Integer, String> apply(
Entry<? extends String, ? extends Number> sourceObject)
{
return new Pair<Integer, String>(
sourceObject.getValue().intValue(),
sourceObject.getKey()
);
}
};
Run Code Online (Sandbox Code Playgroud)
注意String是最终的,没有人扩展它,但我担心通用系统不是那么聪明,所以原则上,? extends String无论如何,我做了,为了以后好.
然后,让我们考虑一下remapEntries().我们怀疑大多数变形金刚通过它会有类似的类型声明swap,因为我们提出的理由.所以我们最好
remapEntry(
Transformation<
Entry<? extends SK, ? extends SV>,
Entry<RK,RV>
> f,
...
Run Code Online (Sandbox Code Playgroud)
正确匹配该论点.从那里,我们计算出源和结果的类型,我们希望它们尽可能通用:
public static <SK, SV, RK, RV, RM extends Map<? super RK, ? super RV>>
RM remapEntries(
Transformation<
Entry<? extends SK, ? extends SV>,
Entry<RK,RV>
> f,
Map<? extends SK, ? extends SV> source,
RM result
)
{
for(Entry<? extends SK, ? extends SV> entry : source.entrySet()) {
Entry<RK,RV> res = f.apply(entry);
result.put(res.getKey(), res.getValue());
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
RM没有必要,可以直接使用Map<? super RK, ? super RV>.但似乎您希望返回类型与result调用者上下文中的类型相同.我想简单地做出返回类型void- 已经有足够的麻烦了.
如果swap不使用,这件事就会失败? extends.例如,如果输入类型是String-Integer,那么做? extends它们是荒谬的.但是你可以使用不同参数类型声明的重载方法来匹配这种情况.
好吧,这完全没有运气.但是,这完全不值得.如果你忘记它,你的生活会好得多,并使用原始类型,用英语记录参数,在运行时进行类型检查.问问自己,通用版本能为您买到什么吗?很少,以极高的价格渲染您的代码完全不可理解.如果我们明天早上阅读方法签名,那么包括你自己和我在内的任何人都无法理解它.它比正则表达更糟糕.
| 归档时间: |
|
| 查看次数: |
3150 次 |
| 最近记录: |