我正在尝试为Java创建一个小函数式编程库(只是为了抓住我自己的痒)。在为List
,Set
和Map
定义higher-order functions时,我遇到了这个问题:收集集合并返回相同类型集合的函数具有几乎相同的实现,但必须为每个数据结构重新定义 - List
s,Set
和Map
s。
例如,以下是map
和List
s的Set
函数的实现:
public static <A, B> List<B> map(
List<? extends A> xs,
Func1<? super A, ? extends B> transformer
) {
List<B> ys = new ArrayList<B>();
for(A a : xs) {
ys.add(transformer.apply(a));
}
return ys;
}
public static <A, B> Set<B> map(
Set<? extends A> xs,
Func1<? super A, ? extends B> transformer
) {
Set<B> ys = new HashSet<B>();
for(A a : xs) {
ys.add(transformer.apply(a));
}
return ys;
}
filter
函数:
public static <A> List<A> filter(
List<? extends A> xs,
Func1<? super A, Boolean> predicate
) {
List<A> ys = new ArrayList<A>();
for(A a : xs) {
if(predicate.apply(a)) {
ys.add(a);
}
}
return ys;
}
public static <A> Set<A> filter(
Set<? extends A> xs,
Func1<? super A, Boolean> predicate
) {
Set<A> ys = new HashSet<A>();
for(A a : xs) {
if(predicate.apply(a)) {
ys.add(a);
}
}
return ys;
}
从这个例子中可以看出,Set
和List
的实现主体几乎相同。
我的库中有很多函数,如map
和filter
,每个函数都为我感兴趣的每种类型的集合定义了三次(即List
,{{ 1}}和Set
)。这会导致很多代码重复和代码异味。我想知道Java中是否有某种方法可以帮助我避免所有的代码重复。
任何帮助将不胜感激。感谢。
修改
Map
是一个定义为:
Func1
答案 0 :(得分:6)
public static <A, B> List<B> map(
List<? extends A> xs,
Func1<? super A, ? extends B> transformer
) {
List<B> ys = new ArrayList<B>();
map(xy, transformer, ys);
return ys;
}
public static <A, B> Set<B> map(
Set<? extends A> xs,
Func1<? super A, ? extends B> transformer
) {
Set<B> ys = new HashSet<B>();
map(xy, transformer, ys);
return ys;
}
private static <A, B> map(
Collection<? extends A> xs,
Func1<? super A, ? extends B> transformer,
Iterable<B> ys
) {
for(A a : xs) {
ys.add(transformer.apply(a));
}
}
完成工作。
注意,典型的Java API是将可变集合传入,而不是在方法中创建新的集合。就个人而言,我不是收集级别的可变性的粉丝,但这是我们必须使用的(在Java中)。
(我不喜欢A
和B
作为这类东西的通用参数。)
或者你可以使用工厂:
public static <A, B> List<B> map(
List<? extends A> xs,
Func1<? super A, ? extends B> transformer
) {
return map(xs, transformer, new CollectionFactory<B, List<B>>() {
public List<B> create() { return new ArrayList<B>(); }
});
}
public static <A, B> Set<B> map(
Set<? extends A> xs,
Func1<? super A, ? extends B> transformer
) {
return map(xs, transformer, new CollectionFactory<B, Set<B>>() {
public Set<B> create() { return new HashSet<B>(); }
});
}
private interface CollectionFactory<E, C extends Collection<E>> {
C create();
}
private static <A, B, C extends Collection<B>> C map(
Iterable<? extends A> xs,
Func1<? super A, ? extends B> transformer,
CollectionFactory<B, C> factory
) {
C ys = factory.create();
for(A a : xs) {
ys.add(transformer.apply(a));
}
return ys;
}
(如果你能忍受匿名内部类的毫无意义的冗长。)
如果不是Collection
,那么你需要放一些(丑陋的)适配器。
为了完整性(虽然未经过测试,可以进行一些调整),使用继承的令人不快的解决方案:
Set<String> strs = hashSets().map(things, formatter);
...
public static <E> Functions<E, Set<E>> hashSets() {
return new Functions<E, Set<E>>() {
protected Set<E> createCollections() {
return new HashSet<E>();
}
};
}
public abstract class Functions<E, C extends Collection<E>> {
protected abstract C createCollection();
public <S> C map(
Set<? extends S> xs,
Func1<? super S, ? extends E> transformer
) {
C ys = createCollection();
for(S a : xs) {
ys.add(transformer.apply(a));
}
return ys;
}
public <S> C filter(
List<? extends S> xs,
Func1<? super S, Boolean> predicate // Predicate<? super S> might be nicer!!
) {
C ys = createCollection();
for(A a : xs) {
if(predicate.apply(a)) {
ys.add(a);
}
}
return ys;
}
}
答案 1 :(得分:4)
我认为你不能比汤姆在his answer中建议的做得更好。 Java不支持更高级的类型 - 一种可以帮助您抽象集合类型的功能,从而避免为每个集合类型复制相同的代码。
Scala支持此功能,并且在其标准库中广泛使用。 Adriaan Moors的This paper讨论了Scala如何在更高级别的类型的帮助下避免这种代码重复。
上述论文的两个截图:
答案 2 :(得分:4)
Java没有高阶多态(也就是更高类型),因此在类型系统中这是不可能的。许多Java程序员采用XML和/或反射(即逃避类型系统)来解决这个缺陷。
Scala可以解决这个问题,你所描述的内容称为协变函子。这个相当基础的数据类型(以及更多)已在Scalaz库中实现,并包含java.util的实现。*。
此外,还有更多的协变仿函数不是集合,还有更多仿函数不协变。
如果您希望进一步探索这一特定概念,您可能希望谷歌参加“20中级Scala练习”。
答案 3 :(得分:2)
我不相信Java的类型系统足够复杂以解决这个问题,但Scala是。使用2.8版本的集合库,他们构建了一个系统,可根据您正在使用的集合自动创建适当类型的集合。因此,如果您在filter
上致电List
,则会返回新的List
。在filter
上致电Set
,您将获得Set
。这样做虽然仍然只有filter
的单个实现。
要了解详情,请查看Traversable
以及使用它的内容。我相信CanBuildFrom
是很多魔法发生的地方。
答案 4 :(得分:1)
有效地,列表只是类型T
的Monad,使其能够存储该类型的多个实例。这就是为什么monad的所有常用法则都适用于此的原因,因此您可以使用bind
和return
成员实现所有操作。
对不起,我现在没有时间进一步解释,但在.NET空间中我们有SelectMany和Enumerable.Repeat(1,element)用于相同的目的。有很多关于此的信息。
任何运算符(例如示例中的filter
)都可以使用SelectMay
分别实现绑定。