Reputation: 2206
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
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
Reputation: 77177
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
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