开发者

How can I filter a list in Java using a predicate function?

Consider the following code:

ICondition searchCondition, scopeCondition...
List<ICondition> filtered = CollectionUtil.filter(
    Arrays.asList(searchCondition, scopeCondition),
    CollectionUtil.isNonNull);

It fails to compile:

"The method filter(Collection<T>, CollectionUtil.Predicate<T>) in the type CollectionUtil is not applicable for the arguments (List<ICondition>, CollectionUtil.Predicate<Object>)"

Everything is fine if I define an ICondition-specific isNonNull() predicate, but that's dumb and I don't understand what's wrong or how to fix it.

Here are my utility functions:

public interface Predicate<T> 
{
    boolean apply(T type); 
}

public static <T> List<T> filter(Collection<T> target, Predicate<T> predicate) 
{
    target = Collections.unmodifiableCollection(target);
    List<T> result = new ArrayList<T>();
    for (T element: target) {
        if (predicate.apply(element)) {
            result.add(element);
        }
    }
    return result;
}


// This predicate works as expected.
public static CollectionUtil.Predicate<String> isStringNonBlank = new CollectionUtil.Predicate<String>() {
    public boolean apply (String item) {
        return !StringUtils.isBlank(item);
    }
};

// This predicate looks fine, but fails in usage.
public static CollectionUtil.Predicate<Object> isNonNull = new CollectionUtil.Predicate<Object>() {
    public boolean apply (Object item) {
        开发者_高级运维return null != item;
    }
};

Why can't I use the second predicate with filter()?


It looks like your filter function's predicate parameter is not properly contravariant. Try rewriting it as follows:

public static <T> List<T> filter(Collection<? extends T> source,
                                 Predicate<? super T> predicate)
{
  final List<T> result = new ArrayList<T>(source.size());
  for (T element: source)
    if (predicate.apply(element))
      result.add(element);
  return result;
}

That says that so long as the predicate function is willing to accept a type no narrower than type T, calling it with an instance of type T (or some type further derived from T) will work fine.


Try generifying isNonNull:

private static class IsNonNullPredicate<T> implements Predicate<T> {
    public boolean apply(T item) {
        return null != item;
    }
}

Now you can return it through a generic method in your util class instead of a constant.

public <T> Predicate<T> isNonNull() {
    return new IsNonNullPredicate<T>();
}

Alternatively, just do an unchecked cast on a stored instance instead of creating a new one each time:

private final Predicate isNotNullPredicate = new IsNonNullPredicate();
public <T> Predicate<T> isNonNull() {
    return (Predicate<T>) isNotNullPredicate;
}

This is what the Collections class in the Java Collections library does to provide support for generics in its utility methods. Before 1.5 there was Collections.EMPTY_LIST which after generics were added would return a List<Object>. However, that wouldn't give back a suitably generified list so Collections.emptyList() was added to return a List of any type that would fit the calling context.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜