19

可以使用 guava 轻松过滤列表或 Iterables filter(Iterable<?> unfiltered, Class<T> type)。此操作执行两个任务:过滤列表并将其转换为给定类型 T 的序列。

然而,我经常会Iterables<Something<?>> 得到Iterables<Something<T>>一些专门的 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()它不应该抛出 aClassCastException而只是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().

4

2 回答 2

3

解决这个问题的单子方法是定义一个将可迭代对象转换为可迭代对象的可迭代对象的操作,方法是定义一个转换函数,该函数对于 typeT的对象返回 type 的对象Iterable<T>。然后,您可以连接每个迭代以再次形成一个。concatMap在 Haskell 和Scala中调用了映射后跟连接的这种组合,flatMap我相信它在其他地方还有其他名称。

为了实现这一点,我们首先创建一个将您S<? extends Number>转换为Iterable<S<Double>>. 这与您现有的函数非常相似,但我们的成功案例是 one 的迭代,包含我们的S,而失败案例(我们的 null 状态)是一个空的迭代。

<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);

免责声明:我没有尝试编译这个。您可能必须使用泛型才能使其正常工作。

于 2011-09-19T23:11:52.030 回答
2

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

前段时间我在考虑这个问题,因为首先应用转换然后过滤需要两次传递集合。然后有人启发我,我可以转换和过滤这个集合的迭代器。在这种情况下,集合会被遍历一次,您可以根据需要应用任意数量的过滤器和转换。

于 2011-09-15T06:56:56.487 回答