过滤泛型类型的列表

19
使用Guava的filter(Iterable<?> unfiltered, Class<T> type)方法可以轻松地过滤列表或可迭代对象。此操作执行两个任务:过滤列表并将其转换为给定类型T的序列。
然而,通常情况下我得到的是Iterables<Something<?>>,我想要获取一些专门的T的子序列Iterables<Something<T>>
显然,由于类型擦除,Guava无法直接解决这个问题:Something<T>没有提供任何关于它的T的直接信息。
假设我有像S<? extends Number>这样的东西。如果我能定义一些谓词来告诉我S<?>是否可以强制转换为S<Double>,那么我就可以将其用作过滤器。
<T extends Number> Predicate<S<?>> isOfType(Class<N> type) {...}

使用:

Iterable<S<?>> numbers;
Iterable<S<?>> filtered = Iterable.filter(numbers, isOfType(Double.class));

这个执行筛选任务,但缺失转换步骤。如果我认为我的谓词很好用,我甚至可以考虑强制转换:

Iterable<S<Double>> doubles = (Iterable<S<Double>>) filtered;

但是这暴露了一些丑陋的强制类型转换操作。
作为替代,我可以提供一个Function<S<?>, S<Double>>来执行转换。与Class.cast()相比,它不应该抛出ClassCastException,而是在元素无法转换(或转换失败)时简单地返回null。这样,序列可以在没有任何显式转换的情况下进行转换:
<T extends Number> Function<S<?>, S<T>> castOrNull(Class<N> type) {...}

Iterable<S<Double>> doubles = Iterable.filter(numbers, castOrNull(Double.class));

但实际上该列表并没有被过滤,对于那些无法转换或强制转换为 S<Double> 的每个元素,它仍然包含空对象。 但可以通过一个额外的过滤步骤轻松解决这个问题,例如:
Iterable<S<Double>> doubles = Iterables.filter(doubles, Predicates.notNull());

第二种解决方案对我来说更加聪明。要定义的Function可能执行转换(隐藏未经检查的操作),也可能在必要时创建一些新的对象S<T>
剩下的问题是: 是否有更聪明的方法可以通过单个步骤执行必要的转换和过滤?我可以简单地定义一些实用函数,例如:
<I,O> Iterables<O> convert(
    Iterables<O> input, 
    Function<? super I, ? extends O> convert, 
    Predicate<? super O> filter);

<I,O> Iterables<O> convert(
    Iterables<O> input, 
    Function<? super I, ? extends O> convert);

第二个函数是第一个函数的简化版,使用 Predicates.notNull()
但是也值得拥有第一个函数,因为谓词不一定是 Predicates.notNull()
想象一下一个 Iterable<Iterable<? extends Number>>。转换函数 Function<Iterable<? extends Number>, Iterable<Double>> 可以简单地返回一个过滤后的序列,该序列可能为空,而不是返回 null。最终,通过使用 Iterables.isEmpty(),额外的过滤器可以删除空序列。

1
如果Iterable.filter(...)返回一个带有扩展功能的可迭代对象,以便您可以链接过滤器,那将非常有用。 /* S extends Collection */ Iterable<S<Double>> doubles = Iterable.filter(numbers, castOrNull(Double.class)).filter(Predicates.notNull()).filter(Predicates.notEmpty()); - aalku
4
为什么你想要在一步中完成这件事呢?转换和过滤是不同的操作。 - pawstrong
2个回答

3
这个问题的单子方法是定义一种操作,将可迭代对象转换为可迭代对象的集合,通过定义一个转换函数,对于类型为的对象,返回类型为>的对象。然后可以将每个可迭代对象连接起来再形成一个单一的可迭代对象。在Haskell中,这种由映射和连接组合而成的操作称为concatMap,在Scala中则称为flatMap,我相信其他地方也有不同的名称。
为了实现这个,首先创建一个函数,将你的>转换为>>。这与您现有的函数非常相似,但成功情况下会产生包含我们的的一个可迭代对象,而失败情况(空状态)则是一个空的可迭代对象。
<T extends Number> Function<S<?>, Iterable<S<T>>> castOrNull(Class<T> type) {
    return new Function<S<?>, Iterable<S<T>>> {
        @Override
        public Iterable<S<T>> apply(S<?> s) {
            Object contained = s.get();
            if (!(contained instanceof T)) {
                return ImmutableSet.of();
            }

            return ImmutableSet.of(new S<T>(contained));
        }
    };
}

我们将按照您上述的指定,把这个应用于原始的可迭代对象。
Iterable<Iterable<S<Double>>> doubleIterables = Iterables.map(numbers, castOrNull(Double.class));

我们可以将所有这些内容连接在一起,以便再次生成一个迭代器,其中包含所有所需的值,但不包括我们想要删除的任何值。
Iterable<S<Double>> doubles = Iterables.concat(doubleIterables);

免责声明:我没有尝试编译这个。您可能需要尝试一些泛型来使其工作。



1
让函数返回一个包含零个或一个元素的Iterable是一种非常有趣的方法。我之前没有想到过这一点,我认为如果需要,让函数返回null是很好的。由于Guava在r10中添加了Optional类型(http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html),我想知道我们是否可以使用Function<S<?>, Optional<S<T>>>,并过滤“present”值的结果Iterable。 - Etienne Neveu
1
我刚刚读了luckyjaca的回答,他们在Scala中使用“Option”类型,还添加了flatMap函数。有关更多信息,请参见此SO答案:https://dev59.com/inNA5IYBdhLWcg3wL6sc#1060400 “flatMapList [Option [A]]转换为List [A],并删除任何下钻到NoneOption。”非常酷。 - Etienne Neveu
2
@eneveu:如果Optional实现了Iterable,那就太好了,但据我所知,目前没有计划这样做。然而,Nat Pryce在Java中实现的Maybe(https://github.com/npryce/maybe-java)却实现了这一点。我工作的公司的一个人对其进行了分支并进行了改进 - 您可以在https://github.com/youdevise/maybe-java上查看该版本。 - Samir Talwar
1
很好的观点。这意味着我们不能使用Iterables.concatIterable<Optional<T>>扁平化为所有存在值的Iterable<T>...不过我猜我们可以编写一个实用程序方法来完成这个任务(或者Guava可以)。另外,我会在你的示例中使用ImmutableSet.of()来避免每次初始化空的ArrayList。像return contained instanceof T ? ImmutableSet.of(new S<T>(contained)) : ImmutableSet.of();这样。很酷的是,空的不可变集合在内部是单例的,这避免了不必要的对象/数组实例化。 - Etienne Neveu

2

Scala语言在其集合框架中提供与Guava类似的功能。我们有Option[T]类,可以被视为最多单元素集合。在简单的过滤或转换方法之间,有一种同时执行两个操作的方法。它期望提供的转换函数返回Option类的值。然后将返回的Option对象的内容合并到集合中。我认为你可以在Java中实现类似的功能。

我曾经思考过这个问题,因为首先应用转换,然后再过滤需要对集合进行两次传递。然后有人启发我可以转换和过滤此集合的迭代器。在这种情况下,只需遍历集合一次,您可以应用任意数量的过滤和转换。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接