Devin Andres Salemi
Devin Andres Salemi

Reputation: 2206

Java stream filtering - use Predicate<String> instead of Predicate<Class>, to operate directly on a field of the class

With java stream, I have a List of ClassA, applied to a stream for filtering, so list.stream().filter(predicate).etc. With that, the predicate would need Predicate of ClassA to apply, but I really want to have Predicate of String, because ClassA.Field1 is of type String. So rather than have the predicate contain f -> f.getField1().equals("foo"), I want it to be f -> f.equals("foo"), operating directly on the field. The reason is because I also have ClassB, and ClassC, and predicateA, predicateB, predicateC. I don't want to use multiple Predicates, I want to use one Predicate, since the operation is the same for all of them, a check that the String has the expected contents.

What is the missing link for this, how do I need to transform the stream to do this?

For additional context, after filtering I need to pull a second field from the filtered list, so the goal is to have ClassA.Field2.val (Imagine for example I need to pull an Item ID that is associated with that Item's Name, so I filter by Name)

Additionally: This is a simplification, the actual need is to use startsWith, not equals.

Upvotes: 0

Views: 2229

Answers (3)

Eritrean
Eritrean

Reputation: 16498

Inspired by @bedrin's post, here an approach to do it without a need for a new class

Assuming you have classes like:

class ClassA {
    String name;
    //getter, setter ..
}

class ClassB {
    String name;
    //getter, setter ..
}

and lists, for example :

List<ClassA> myAs = List.of(new ClassA("myA_1"), new ClassA("myA_1"), new ClassA("myA_42"));
List<ClassB> myBs = List.of(new ClassB("myB_1"), new ClassB("myB_9"), new ClassB("myB_33"));

then you could write a method which accepts a field extractor function and the string you want to test against:

private static <T> Predicate<T> nameEquals( Function<? super T, ?> fieldExtractor, String toTestAgainst ) {
    return t -> toTestAgainst.equals(fieldExtractor.apply(t));
}

and use the above method to filter your lists. Example usage:

myAs.stream().filter(nameEquals(ClassA::getName, "myA_1")).forEach(System.out::println);
myBs.stream().filter(nameEquals(ClassB::getName, "myB_1")).forEach(System.out::println);

Upvotes: 2

The recommendation to map the stream itself will generally result in a loss of the actual classes you want. Instead, apply a thin converting wrapper:

listOfA.stream()
  .filter(a -> sharedPredicate.test(a.getField1()))
  ...

Upvotes: 2

bedrin
bedrin

Reputation: 4586

You would need to separate logic about extracting strings from your classes and filtering these strings:

public class StringFieldPredicate<T> implements Predicate<T> {
    
    private final Function<T, String> extractStringFunction;
    private final Predicate<String> stringPredicate;

    public StringFieldPredicate(Function<T, String> extractStringFunction, Predicate<String> stringPredicate) {
        this.extractStringFunction = extractStringFunction;
        this.stringPredicate = stringPredicate;
    }

    @Override
    public boolean test(T t) {
        return stringPredicate.test(extractStringFunction.apply(t));
    }
    
}

It can be used like this:

public static void main(String[] args) {
    List<Number> numbers = Arrays.asList(1,2,3,4,5);
    numbers.stream().filter(new StringFieldPredicate<>(Number::toString, it -> it.startsWith("1")));
}

Upvotes: 0

Related Questions