0

So I am currenty trying to implement a method which does some filtering on lists regardless of their actual type. Here is the actual method:

public static <T extends List<String>> T filterList(T list, Predicate <String> predicate) {

    T newList = ???
    list.forEach(s -> {
        if (predicate.test(s)) newList.add(s);
    });
    return newList;

}

So the generic type T is basically the some implementation of List such as ArrayList or LinkedList and regardless of their actual implementation I want to do some filtering through a Predicate passed as parameter. The return type of the method is the same as the list which is passed as a parameter. But how is it possible to instanciate an empty List based on T (see line 2)? To show you how the method is intended to be used i provided an example. The following example would filter an ArrayList based on the length of the containing Strings:

ArrayList<String> listOfNames = new ArrayList<>();
listOfNames.add("stackoverflowuser");
listOfNames.add("sitaguptana");
listOfNames.add("nyan cat");
listOfNames.add("pedro");

Predicate<String> lengthUnderTen = (string) -> string.length() < 10;

ArrayList <String> result = filterList(listOfNames,lengthUnderTen);
solomid
  • 13
  • 2

5 Answers5

4

If I am understanding your question correctly, then I don't see why you need to use generics at all here.

The following function will accept any class that extends List as a parameter e.g. an ArrayList, LinkedList etc.:

public static List<String> filterList(List<String> list, Predicate<String> predicate) {
    return list.stream().filter(predicate).collect(Collectors.toList());
}

Full example:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class Example {

    public static void main(String[] args) {
        ArrayList<String> example1 = new ArrayList<>();
        example1.add("abc");
        example1.add("ghe");

        LinkedList<String> example2 = new LinkedList<>();
        example2.add("foo");
        example2.add("bar");

        List<String> result1 = filterList(example1, s -> s.contains("a"));
        List<String> result2 = filterList(example2, s -> s.contains("f"));
    }

    public static List<String> filterList(List<String> list, Predicate<String> predicate) {
        return list.stream().filter(predicate).collect(Collectors.toList());
    }
}
explv
  • 2,709
  • 10
  • 17
2

If you could modify your method as

public static <T> List<T> filterList(List<T> list, Predicate<T> predicate) {

    return list.stream().filter(predicate).collect(Collectors.toList());
}

It looks clean because it works for any type of List, not only for List<String>. This method will be more generic.

Hemant Patel
  • 3,160
  • 1
  • 20
  • 29
1

Have the caller pass in a Supplier<T> as well.

public static <T extends List<String>> T filterList(T list, Predicate <String> predicate, Supplier<T> listCreator) {

    T newList = listCreator.get();
    list.forEach(s -> {
       // ...
daniu
  • 14,137
  • 4
  • 32
  • 53
0

You could create the class by reflection by passing a List<String> as parameter.
In fact you don't need to specify any wildcard for your list.

public static List<String> filterList(List<String> list,  Predicate<String> predicate) throws InstantiationException, IllegalAccessException {

    List<String> newList = list.getClass()
                               .newInstance();
    list.forEach(s -> {
        if (predicate.test(s)) newList.add(s);
    });
    return  newList;
}

But note that a cleaner way would be to use a stream to collect the new List if the implementation doesn't matter :

public static List<String> filterList(List<String> list,  Predicate<String> predicate) throws InstantiationException, IllegalAccessException {

  return list.stream()
             .filter(predicate)
             .collect(Collectors.toList());
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
0

You have a base class that already allows you to cleanly program to interface. You could design this API method to return a List object, keeping the generic type at element level.

Please note that this pattern is already provided by the standard API (List/Collection + Stream), so you shouldn't need to recreate it. - see note at bottom.

If you do not have any constraint regarding the type of list returned by the method, then it's this implementation's choice which list type it returns (using array list below):

public static <T> List<T> filterList(List<T> list, Predicate<T> predicate) {

    List<T> newList = new ArrayList<>(); //You can choose a different type here
    list.forEach(s -> {
        if (predicate.test(s)) newList.add(s);
    });

    return newList;
}

If you leave it to your caller to choose what type of list is created, then perhaps you should take a factory:

public static <U, T extends List<U>> T filterList(T list, 
      Predicate<U> predicate, Supplier<T> newListFactory) {

    T newList = newListFactory.get(); //You can choose a different type here
    list.forEach(s -> {
        if (predicate.test(s))
            newList.add(s);
    });

    return newList;
}

Note: this pattern is already provided by the collections API:

java.util.stream.Stream.filter(Predicate<? super T>)

This allows you to do exactly the same thing, except that the creation of the returned list (say, you ran collect(Collectors.toList()))

ernest_k
  • 44,416
  • 5
  • 53
  • 99