import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; public class MainTest { public static void main(String[] args) { List<TestObject> testList = new ArrayList<>(); testList.add(new TestObject("1")); testList.add(new TestObject("2")); testList.add(new TestObject("3")); testList.add(new TestObject("3")); testList.add(new TestObject("4")); List<String> list = testList.stream() .filter(distinctFilter(TestObject::getVal)) .map(TestObject::getVal) .collect(Collectors.toList()); System.out.println(list); } private static <T> Predicate<T> distinctFilter(Function<? super T, Object> function) { Map<Object,Boolean> valuesHaveBeenSeen = new ConcurrentHashMap<>(); System.out.println("called once"); return t -> { System.out.println(valuesHaveBeenSeen); return valuesHaveBeenSeen.putIfAbsent(function.apply(t), Boolean.TRUE) == null; }; } } class TestObject { String val; public TestObject() { } public TestObject(String val) { this.val = val; } String getVal() { return this.val; } }
Output:
called once {} {1=true} {1=true, 2=true} {1=true, 2=true, 3=true} {1=true, 2=true, 3=true} [1, 2, 3, 4]
It seems the local map valuesHaveBeenSeen is just created once and used throughout the iteration.