701 Stimmen

Java 8 Distinct nach Eigenschaft

In Java 8, wie kann ich eine Sammlung mithilfe der Stream-API filtern, indem ich die Eindeutigkeit einer Eigenschaft jedes Objekts überprüfe?

Zum Beispiel habe ich eine Liste von Person-Objekten und möchte Personen mit dem gleichen Namen entfernen,

persons.stream().distinct();

Verwendet den Standard-Gleichheitscheck für ein Person-Objekt, daher benötige ich etwas wie,

persons.stream().distinct(p -> p.getName());

Leider hat die distinct()-Methode keine solche Überladung. Ist es möglich, dies ohne Änderung des Gleichheitschecks innerhalb der Person-Klasse knapp zu tun?

1voto

Rafael Winterhalter Punkte 40326

Zu spät zur Party, aber manchmal benutze ich diese Einzeiler als Äquivalent:

((Function) Value::getKey).andThen(new HashSet<>()::add)::apply

Der Ausdruck ist ein Predicate, aber da die Map inline ist, funktioniert sie als Filter. Das ist natürlich weniger lesbar, aber manchmal kann es hilfreich sein, die Methode zu umgehen.

1voto

Aliaksei Yatsau Punkte 749

Vielleicht ist es für jemanden nützlich. Ich hatte eine etwas andere Anforderung. Wenn Sie eine Liste von Objekten A von einem Drittanbieter haben, entfernen Sie alle Objekte, die dasselbe Feld A.b für dasselbe A.id haben (mehrere A-Objekte mit demselben A.id in der Liste). Die Antwort auf das Stream-Partition-Problem von Tagir Valeev hat mich dazu inspiriert, einen benutzerdefinierten Collector zu verwenden, der Map> zurückgibt. Einfaches flatMap erledigt den Rest.

 public static  Collector>> groupingDistinctBy(Function keyFunction, Function distinctFunction) {
    return groupingBy(keyFunction, Collector.of((Supplier>) HashMap::new,
            (map, error) -> map.putIfAbsent(distinctFunction.apply(error), error),
            (left, right) -> {
                left.putAll(right);
                return left;
            }, map -> new ArrayList<>(map.values()),
            Collector.Characteristics.UNORDERED)); }

1voto

Garrett Smith Punkte 688

Basierend auf der Antwort von @josketres habe ich eine generische Hilfsmethode erstellt:

Sie könnten dies Java 8-freundlicher machen, indem Sie einen Collector erstellen.

public static  Set removeDuplicates(Collection input, Comparator comparer) {
    return input.stream()
            .collect(toCollection(() -> new TreeSet<>(comparer)));
}

@Test
public void removeDuplicatesWithDuplicates() {
    ArrayList input = new ArrayList<>();
    Collections.addAll(input, new C(7), new C(42), new C(42));
    Collection 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 input = new ArrayList<>();
    Collections.addAll(input, new C(1), new C(2), new C(3));
    Collection 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;
    }
}

0voto

Meine Lösung in dieser Liste:

List Ergebnis ....

List dto3s = new ArrayList<>(result.stream().collect(toMap(
            HolderEntry::getId,
            holder -> holder,  //oder Function.identity() wenn du möchtest
            (holder1, holder2) -> holder1 
    )).values());

In meiner Situation möchte ich verschiedene Werte finden und sie in einer Liste speichern.

0voto

Kache Punkte 12983

Eine Variation der Spitzenantwort, die null behandelt:

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

In meinen 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()));

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X