Java 8 Distinct by property

后端 未结 29 1796
傲寒
傲寒 2020-11-21 22:35

In Java 8 how can I filter a collection using the Stream API by checking the distinctness of a property of each object?

For example I have a list of

相关标签:
29条回答
  • Building on @josketres's answer, I created a generic utility method:

    You could make this more Java 8-friendly by creating a Collector.

    public static <T> Set<T> removeDuplicates(Collection<T> input, Comparator<T> comparer) {
        return input.stream()
                .collect(toCollection(() -> new TreeSet<>(comparer)));
    }
    
    
    @Test
    public void removeDuplicatesWithDuplicates() {
        ArrayList<C> input = new ArrayList<>();
        Collections.addAll(input, new C(7), new C(42), new C(42));
        Collection<C> result = removeDuplicates(input, (c1, c2) -> Integer.compare(c1.value, c2.value));
        assertEquals(2, result.size());
        assertTrue(result.stream().anyMatch(c -> c.value == 7));
        assertTrue(result.stream().anyMatch(c -> c.value == 42));
    }
    
    @Test
    public void removeDuplicatesWithoutDuplicates() {
        ArrayList<C> input = new ArrayList<>();
        Collections.addAll(input, new C(1), new C(2), new C(3));
        Collection<C> result = removeDuplicates(input, (t1, t2) -> Integer.compare(t1.value, t2.value));
        assertEquals(3, result.size());
        assertTrue(result.stream().anyMatch(c -> c.value == 1));
        assertTrue(result.stream().anyMatch(c -> c.value == 2));
        assertTrue(result.stream().anyMatch(c -> c.value == 3));
    }
    
    private class C {
        public final int value;
    
        private C(int value) {
            this.value = value;
        }
    }
    
    0 讨论(0)
  • 2020-11-21 23:11

    A variation of the top answer that handles null:

        public static <T, K> Predicate<T> distinctBy(final Function<? super T, K> getKey) {
            val seen = ConcurrentHashMap.<Optional<K>>newKeySet();
            return obj -> seen.add(Optional.ofNullable(getKey.apply(obj)));
        }
    

    In my tests:

            assertEquals(
                    asList("a", "bb"),
                    Stream.of("a", "b", "bb", "aa").filter(distinctBy(String::length)).collect(toList()));
    
            assertEquals(
                    asList(5, null, 2, 3),
                    Stream.of(5, null, 2, null, 3, 3, 2).filter(distinctBy(x -> x)).collect(toList()));
    
            val maps = asList(
                    hashMapWith(0, 2),
                    hashMapWith(1, 2),
                    hashMapWith(2, null),
                    hashMapWith(3, 1),
                    hashMapWith(4, null),
                    hashMapWith(5, 2));
    
            assertEquals(
                    asList(0, 2, 3),
                    maps.stream()
                            .filter(distinctBy(m -> m.get("val")))
                            .map(m -> m.get("i"))
                            .collect(toList()));
    
    0 讨论(0)
  • 2020-11-21 23:13

    There's a simpler approach using a TreeSet with a custom comparator.

    persons.stream()
        .collect(Collectors.toCollection(
          () -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName())) 
    ));
    
    0 讨论(0)
  • 2020-11-21 23:14

    Similar approach which Saeed Zarinfam used but more Java 8 style:)

    persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
     .map(plans -> plans.stream().findFirst().get())
     .collect(toList());
    
    0 讨论(0)
  • 2020-11-21 23:15

    I had a situation, where I was suppose to get distinct elements from list based on 2 keys. If you want distinct based on two keys or may composite key, try this

    class Person{
        int rollno;
        String name;
    }
    List<Person> personList;
    
    
    Function<Person, List<Object>> compositeKey = personList->
            Arrays.<Object>asList(personList.getName(), personList.getRollno());
    
    Map<Object, List<Person>> map = personList.stream().collect(Collectors.groupingBy(compositeKey, Collectors.toList()));
    
    List<Object> duplicateEntrys = map.entrySet().stream()`enter code here`
            .filter(settingMap ->
                    settingMap.getValue().size() > 1)
            .collect(Collectors.toList());
    
    0 讨论(0)
  • 2020-11-21 23:17

    Consider distinct to be a stateful filter. Here is a function that returns a predicate that maintains state about what it's seen previously, and that returns whether the given element was seen for the first time:

    public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }
    

    Then you can write:

    persons.stream().filter(distinctByKey(Person::getName))
    

    Note that if the stream is ordered and is run in parallel, this will preserve an arbitrary element from among the duplicates, instead of the first one, as distinct() does.

    (This is essentially the same as my answer to this question: Java Lambda Stream Distinct() on arbitrary key?)

    0 讨论(0)
提交回复
热议问题