1

I'm writing a method which filters a specific Collection with a "Predicate" and returns a new Collection containing only filtered elements (those for whom Predicate returns true).

Something like this :

    public <T> Collection<T> filter(Collection<T> collection, Closure<T> predicate);

I know that, in Java, I can't just create a new Collection() at runtime, because of type-erasure.

I also know the "work-around" by passing an extra-argument to the method to call T.newInstance().

This would look like :

    public <T> Collection<T> filter(Class<? extends Collection<T>> collectionToInstanciate, Collection<T> collection, Closure<T> predicate) {

        // create the new Collection
        Collection<T> container = collectionToInstanciate.newInstance();

        // and then add only filtered items
        Iterator<T> iter = collection.iterator();
        while (iter.hasNext()) {
            T obj = iter.next();

            // if Predicate.invoke() returns true, then keep element, otherwise skip it
            if (predicate.invoke(obj)) {
                container.add(obj);
            }
        }
        return container;
    }

But how should I call my method ?

For instance, if I want only odd numbers of a List of Integers, I'd like to do :

    // instanciate ArrayList<Integer> = [1, 2, 3, 4, 5]
    ArrayList<Integer> array = ...;

    // return a new LinkedList<Integer> with only odd numbers
    filter(LinkedList<Integer>.class, array, new Closure<Integer>() {
        public Boolean invoke(Integer arg_p) {
            return (arg_p % 2 == 0);
        }
    });

    // should return [2, 4] as a LinkedList<Integer>

The problem is that

    LinkedList<Integer>.class 

doesn't compile.

How should I declare that to correctly instanciate a LinkedList in the filter() method ?

Regards,

4

2 回答 2

3

Generics are a compile time feature and have little meaning at runtime. If you want to create a LinkedList, that is what you do. You can't make the compiler give you an error based on something you do at runtime.

A simpler solution is to pass an instance of the class you want to populate.

List<Integer> result = filter(new LinkedList<Integer>(), array, 
    new Predicate<Integer>() {
        public boolean invoke(Integer arg_p) {
            return (arg_p % 2 == 0);
        }
    });

It is marginally shorter and can be checked at compile time.

Note: many of these predicates are much simpler and faster as a plain loop.

List<Integer> result = new LinkedList<Integer>();
for(Integer i: array)
    if (i % 2 == 0)
        result.add(i);
于 2013-06-04T21:11:44.403 回答
0

从 Peter Lawrey 的回答和 LuiggiMendoza 的评论来看,另一种方法是以这种方式使用 filter() 方法:

    List<Integer> result = filter(
        LinkedList.class, // instead of new LinkedList<Integer>()
        array, 
        new Predicate<Integer>() {
            public boolean invoke(Integer arg_p) {
                return (arg_p % 2 == 0);
            }
        });

在 filter() 方法中:

    public <T> Collection<T> filter(Class<? extends Collection> collectionToInstanciate, Collection<T> collection, Closure<T> predicate) {

    // create the new Collection as a raw Collection, and cast it back to Collection<T>
    Collection<T> container = (Collection<T>) collectionToInstanciate.newInstance();

    // and then add only filtered items
    Iterator<T> iter = collection.iterator();
    while (iter.hasNext()) {
        T obj = iter.next();

        // if Predicate.invoke() returns true, then keep element, otherwise skip it
        if (predicate.invoke(obj)) {
            container.add(obj);
        }
    }
    return container;
}
于 2013-06-05T12:04:52.240 回答