过滤泛型类型列表
Posted
技术标签:
【中文标题】过滤泛型类型列表【英文标题】:Filtering lists of generic types 【发布时间】:2011-11-04 12:00:31 【问题描述】:可以使用 guava filter(Iterable<?> unfiltered, Class<T> type)
轻松过滤列表或 Iterables。此操作执行两个任务:过滤列表和转换为给定类型 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()
删除空序列。
【问题讨论】:
如果Iterable.filter(...)
返回一个具有扩展功能的可迭代对象,这样您就可以链接过滤器,这将很有用。 /* S extends Collection */ Iterable<S<Double>> doubles = Iterable.filter(numbers, castOrNull(Double.class)).filter(Predicates.notNull()).filter(Predicates.notEmpty());
为什么要一步到位?转换和过滤是不同的操作。
【参考方案1】:
其集合框架中的 Scala 语言提供了与 Guava 类似的功能。我们有 Option[T] 类,它可以被认为是最多单个元素的集合。在简单的过滤或转换方法中,有一种方法可以同时执行这两种操作。它期望提供的转换函数返回 Option 类的值。然后它将返回的 Option 对象的内容合并到一个集合中。我认为您可以在 Java 中实现类似的功能。
前段时间我在考虑这个问题,因为首先应用转换然后过滤需要两次传递集合。然后有人启发我,我可以转换和过滤这个集合的迭代器。在这种情况下,集合被遍历一次,您可以根据需要应用任意数量的过滤器和转换。
【讨论】:
【参考方案2】:解决这个问题的单子方法是定义一个将可迭代对象转换为可迭代对象的可迭代对象的操作,方法是定义一个转换函数,该函数对于T
类型的对象返回Iterable<T>
类型的对象。然后,您可以连接每个迭代以再次形成一个。这种映射后接串联的组合在 Haskell 中称为 concatMap
,在 Scala 中称为 flatMap
,我确信它在其他地方还有其他名称。
为了实现这一点,我们首先创建一个函数,将您的S<? extends Number>
转换为Iterable<S<Double>>
。这与您现有的函数非常相似,但我们的成功案例是一个可迭代的,包含我们的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);
免责声明:我没有尝试编译这个。您可能必须使用泛型才能使其正常工作。
【讨论】:
让函数返回一个包含零个或一个元素的 Iterable 是一种非常有趣的方法。我没有想到这一点,我认为它让你的函数在需要时返回 null 很棒。由于 Guava 在 r10 ( docs.guava-libraries.googlecode.com/git/javadoc/com/google/… ) 中添加了 Optional 类型,我想知道我们是否可以使用 FunctionflatMap
将List[Option[A]]
转换为List[A]
,其中任何Option
向下钻取到None
,已删除”。很酷。
@eneveu:如果Optional
实施Iterable
,那就太好了,但据我所知,没有计划这样做。然而,Nat Pryce 在 Java (github.com/npryce/maybe-java) 中的 Maybe 实现确实如此。我为之工作的公司的一个人对它进行了分叉并对其进行了一些改进——你可以在github.com/youdevise/maybe-java 看到那个版本。
好点。这意味着我们不能使用Iterables.concat
将所有当前值的Iterable<Optional<T>>
展平为Iterable<T>
...我想我们可以编写一个实用方法来为我们执行此操作(或者 Guava 可以)。在不相关的注释中,我会在您的示例中使用 ImmutableSet.of()
来避免每次都初始化一个空的 ArrayList
。像return contained instanceof T ? ImmutableSet.of(new S<T>(contained)) : ImmutableSet.of();
这样的东西。很酷的是,空的不可变集合是一个单例,这避免了不必要的对象/数组实例化。以上是关于过滤泛型类型列表的主要内容,如果未能解决你的问题,请参考以下文章