Java 8 Wyróżnia się według właściwości


455

Jak w Javie 8 mogę filtrować kolekcję za pomocą Streaminterfejsu API, sprawdzając odrębność właściwości każdego obiektu?

Na przykład mam listę Personobiektów i chcę usunąć osoby o tej samej nazwie,

persons.stream().distinct();

Użyje domyślnego sprawdzenia równości dla Personobiektu, więc potrzebuję czegoś takiego,

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

Niestety distinct()metoda nie ma takiego przeciążenia. Czy bez modyfikacji kontroli równości w Personklasie można to zrobić zwięźle?

Odpowiedzi:


555

Zastanów distinctsię filtr stanowe . Oto funkcja, która zwraca predykat, który utrzymuje stan dotyczący tego, co widział wcześniej i zwraca, czy dany element był widziany po raz pierwszy:

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

Następnie możesz napisać:

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

Zauważ, że jeśli strumień jest uporządkowany i jest uruchamiany równolegle, zachowa to dowolny element spośród duplikatów zamiast pierwszego, jak to distinct()ma miejsce.

(Jest to w zasadzie taka sama jak moja odpowiedź na to pytanie: Java Lambda Stream Distinct () na dowolnym kluczu? )


27
Chyba dla lepszej kompatybilności argumentem powinien być Function<? super T, ?>, nie Function<? super T, Object>. Należy również zauważyć, że dla uporządkowanego strumienia równoległego to rozwiązanie nie gwarantuje, który obiekt zostanie wyodrębniony (w przeciwieństwie do normalnego distinct()). Również w przypadku strumieni sekwencyjnych istnieje dodatkowy narzut związany z użyciem CHM (nieobecny w rozwiązaniu @nosid). Wreszcie, to rozwiązanie narusza umowę filtermetody, której predykat musi być bezstanowy, jak podano w JavaDoc. Niemniej jednak głosowano.
Tagir Valeev

3
@java_newbie Zwrócona przez instancja Predicate distinctByKeynie ma pojęcia, czy jest używana w strumieniu równoległym. Używa CHM w przypadku, gdy jest używany równolegle, ale dodaje to narzut w przypadku sekwencyjnym, jak zauważył powyżej Tagir Valeev.
Stuart Marks

5
@holandaGo Nie powiedzie się, jeśli zapiszesz i użyjesz instancji Predicate zwróconej przez distinctByKey. Ale działa, jeśli wywołujesz za distinctByKeykażdym razem, dzięki czemu za każdym razem tworzy nową instancję Predicate.
Stuart Marks

3
@Chinmay nie, nie powinno. Jeśli użyjesz .filter(distinctByKey(...)). Wykona metodę raz i zwróci predykat. Zasadniczo mapa jest już ponownie używana, jeśli używasz jej prawidłowo w strumieniu. Jeśli uczynisz mapę statyczną, mapa zostanie udostępniona dla wszystkich zastosowań. Więc jeśli masz dwa strumienie korzystające z tego distinctByKey(), oba użyłyby tej samej mapy, co nie jest tym, czego chcesz.
g00glen00b

3
To jest takie mądre i całkowicie nieoczywiste. Zasadniczo jest to stanowa lambda, a podstawa CallSitebędzie powiązana z get$Lambdametodą - która zwróci cały czas nową instancję Predicate, ale te instancje będą dzielić to samo mapi functiono ile rozumiem. Bardzo dobrze!
Eugene

152

Alternatywą byłoby umieszczenie osób na mapie przy użyciu nazwiska jako klucza:

persons.collect(Collectors.toMap(Person::getName, p -> p, (p, q) -> p)).values();

Zauważ, że Osoba, która jest przechowywana, w przypadku duplikatu imienia, zostanie pierwsza wcielona w postać.


23
@skiwi: czy uważasz, że istnieje sposób na wdrożenie distinct()bez tego narzutu? Skąd jakaś implementacja wiedziałaby, gdyby widział obiekt wcześniej, nie pamiętając wszystkich wyraźnych wartości, które widział? Narzut toMapi distinctjest bardzo prawdopodobne, że jest taki sam.
Holger

1
@Holger Mogłem się mylić, ponieważ nie myślałem, że to nadwyżka distinct()sama w sobie tworzy.
skiwi

2
I oczywiście zakłóca pierwotną kolejność listy
Philipp

10
@Filipp: można to naprawić, zmieniając napersons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
Holger

1
@DanielEarwicker to pytanie dotyczy „odrębnych według właściwości”. Wymagałoby to sortowania strumienia według tej samej właściwości , aby móc z niego skorzystać. Po pierwsze, OP nigdy nie stwierdził, że strumień jest w ogóle sortowany. Po drugie, strumienie nie są w stanie wykryć, czy są sortowane według określonej właściwości . Po trzecie, nie ma prawdziwej operacji strumienia „odrębnego według właściwości”, aby zrobić to, co sugerujesz. Po drugie, w praktyce istnieją tylko dwa sposoby uzyskania tak posortowanego strumienia. Posortowane źródło ( TreeSet), które i tak jest już odrębne lub sortedw strumieniu, który buforuje również wszystkie elementy.
Holger

101

Możesz zawinąć obiekty osoby w inną klasę, która porównuje tylko nazwiska osób. Następnie rozpakowujesz zawinięte obiekty, aby ponownie uzyskać strumień osoby. Operacje strumieniowe mogą wyglądać następująco:

persons.stream()
    .map(Wrapper::new)
    .distinct()
    .map(Wrapper::unwrap)
    ...;

Klasa Wrappermoże wyglądać następująco:

class Wrapper {
    private final Person person;
    public Wrapper(Person person) {
        this.person = person;
    }
    public Person unwrap() {
        return person;
    }
    public boolean equals(Object other) {
        if (other instanceof Wrapper) {
            return ((Wrapper) other).person.getName().equals(person.getName());
        } else {
            return false;
        }
    }
    public int hashCode() {
        return person.getName().hashCode();
    }
}


5
@StuartCaie Nie bardzo ... nie ma zapamiętywania, a nie chodzi o wydajność, ale dostosowanie do istniejącego API.
Marko Topolnik

6
com.google.common.base.Equivalence.wrap (S) i com.google.common.base.Equivalence.Wrapper.get () również mogą pomóc.
bjmi

Możesz uczynić klasę opakowania ogólną i sparametryzowaną przez kluczową funkcję wyodrębniania.
Lii

equalsSposób można uprościćreturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
Holger

55

Inne rozwiązanie, przy użyciu Set. Może nie być idealnym rozwiązaniem, ale działa

Set<String> set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());

Lub jeśli możesz zmodyfikować oryginalną listę, możesz użyć metody removeIf

persons.removeIf(p -> !set.add(p.getName()));

2
To najlepsza odpowiedź, jeśli nie korzystasz z bibliotek stron trzecich!
Manoj Shrestha

5
używając genialnego pomysłu, że Set.add zwraca true, jeśli ten zestaw nie zawierał już określonego elementu. +1
Luvie

Uważam, że ta metoda nie działa w przypadku przetwarzania strumienia równoległego, ponieważ nie jest bezpieczna dla wątków.
LoBo

@LoBo Prawdopodobnie nie. To tylko pomysł, który zadziała w prostych przypadkach. Użytkownicy mogą go rozszerzyć w celu zapewnienia bezpieczeństwa / równoległości wątków.
Santhosh

Ciekawe podejście, ale wygląda trochę jak anty-wzorzec do modyfikowania kolekcji zewnętrznej (zestawu) podczas filtrowania strumienia do innej kolekcji (osób) ...
Justin Rowe

31

Jest prostsze podejście przy użyciu TreeSet z niestandardowym komparatorem.

persons.stream()
    .collect(Collectors.toCollection(
      () -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName())) 
));

4
Myślę, że twoja odpowiedź pomaga uporządkować, a nie wyjątkowość. Pomogło mi to jednak zastanowić się, jak to zrobić. Sprawdź tutaj: stackoverflow.com/questions/1019854/…
janagn

Pamiętaj, że zapłacisz cenę za sortowanie elementów tutaj i nie musimy sortować, aby znaleźć duplikaty, a nawet usunąć duplikaty.
pisaruk

12
Comparator.comparing (Person :: getName)
Jean-François Savard

24

Możemy również użyć RxJava (bardzo rozbudowana biblioteka rozszerzeń reaktywnych )

Observable.from(persons).distinct(Person::getName)

lub

Observable.from(persons).distinct(p -> p.getName())

Rx jest niesamowity, ale to kiepska odpowiedź. Observablejest oparty na push, podczas gdy Streamjest oparty na pull. stackoverflow.com/questions/30216979/…
sdgfsdh

4
pytanie zadaje rozwiązanie java8 niekoniecznie wykorzystujące strumień. Moja odpowiedź pokazuje, że interfejs Java stream java8 jest mniej skuteczny niż interfejs rx
frhack

1
Przy użyciu reaktora będzie toFlux.fromIterable(persons).distinct(p -> p.getName())
Ritesh

Pytanie dosłownie brzmi „za pomocą Streaminterfejsu API”, a nie „niekoniecznie za pomocą strumienia”. To powiedziawszy, jest to świetne rozwiązanie problemu XY filtrowania strumienia do różnych wartości.
M. Justin

12

Możesz użyć groupingBykolektora:

persons.collect(Collectors.groupingBy(p -> p.getName())).values().forEach(t -> System.out.println(t.get(0).getId()));

Jeśli chcesz mieć inny strumień, możesz użyć tego:

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));

11

Możesz użyć tej distinct(HashingStrategy)metody w kolekcji Eclipse .

List<Person> persons = ...;
MutableList<Person> distinct =
    ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));

Jeśli możesz refaktoryzować w personscelu wdrożenia interfejsu kolekcji Eclipse, możesz wywołać tę metodę bezpośrednio na liście.

MutableList<Person> persons = ...;
MutableList<Person> distinct =
    persons.distinct(HashingStrategies.fromFunction(Person::getName));

HashingStrategy to po prostu interfejs strategii, który pozwala definiować niestandardowe implementacje równości i kodu mieszającego.

public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}

Uwaga: jestem osobą odpowiedzialną za kolekcje Eclipse.


Metodę odrębną dodano w Eclipse Collections 9.0, co może jeszcze bardziej uprościć to rozwiązanie. medium.com/@donraab/…
Donald Raab

10

Polecam korzystanie z Vavr , jeśli możesz. Za pomocą tej biblioteki możesz wykonać następujące czynności:

io.vavr.collection.List.ofAll(persons)
                       .distinctBy(Person::getName)
                       .toJavaSet() // or any another Java 8 Collection

Dawniej znana jako biblioteka „javaslang”.
user11153

9

Możesz użyć biblioteki StreamEx :

StreamEx.of(persons)
        .distinct(Person::getName)
        .toList()

Niestety, ta metoda skądinąd niesamowitej biblioteki StreamEx jest źle zaprojektowana - porównuje równość obiektów zamiast używać równości. Może to działać na przykład Stringdzięki internowaniu łańcuchów, ale może też nie.
Torque

7

Rozszerzając odpowiedź Stuarta Marksa, można to zrobić w krótszy sposób i bez jednoczesnej mapy (jeśli nie potrzebujesz równoległych strumieni):

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

Wtedy zadzwoń:

persons.stream().filter(distinctByKey(p -> p.getName());

2
Ten nie bierze pod uwagę, że strumień może być równoległy.
brunnsbe

Dzięki za komentarz, zaktualizowałem swoją odpowiedź. Jeśli nie potrzebujesz strumienia równoległego, nieużywanie map równoległych zapewnia znacznie lepszą wydajność.
Wojciech Górski,

Twój kod prawdopodobnie działałby dla równoległych kolekcji, gdybyś utworzył Collections.synchronizedSet(new HashSet<>())zamiast niego. Ale prawdopodobnie byłoby wolniej niż z ConcurrentHashMap.
Lii

7

Podobne podejście zastosował Saeed Zarinfam, ale bardziej styl Java 8 :)

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(toList());

1
Zastąpiłbym ją mapą, flatMap(plans -> plans.stream().findFirst().stream())aby uniknąć korzystania z opcji Opcjonalne
Andrew Sneck,

Być może jest to również w porządku: flatMap (plan -> plan.stream (). Limit (1))
Rrr

6

Zrobiłem ogólną wersję:

private <T, R> Collector<T, ?, Stream<T>> distinctByKey(Function<T, R> keyExtractor) {
    return Collectors.collectingAndThen(
            toMap(
                    keyExtractor,
                    t -> t,
                    (t1, t2) -> t1
            ),
            (Map<R, T> map) -> map.values().stream()
    );
}

Przykład:

Stream.of(new Person("Jean"), 
          new Person("Jean"),
          new Person("Paul")
)
    .filter(...)
    .collect(distinctByKey(Person::getName)) // return a stream of Person with 2 elements, jean and Paul
    .map(...)
    .collect(toList())



5

Moje podejście do tego polega na zgrupowaniu wszystkich obiektów o tej samej właściwości, a następnie skróceniu grup do rozmiaru 1, a następnie zebraniu ich jako List.

  List<YourPersonClass> listWithDistinctPersons =   persons.stream()
            //operators to remove duplicates based on person name
            .collect(Collectors.groupingBy(p -> p.getName()))
            .values()
            .stream()
            //cut short the groups to size of 1
            .flatMap(group -> group.stream().limit(1))
            //collect distinct users as list
            .collect(Collectors.toList());

3

Odrębną listę obiektów można znaleźć za pomocą:

 List distinctPersons = persons.stream()
                    .collect(Collectors.collectingAndThen(
                            Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person:: getName))),
                            ArrayList::new));

2

Najłatwiejszym sposobem na wdrożenie tego jest przeskoczenie na funkcję sortowania, ponieważ już zapewnia ona opcję opcjonalną, Comparatorktórą można utworzyć za pomocą właściwości elementu. Następnie musisz odfiltrować duplikaty, co można zrobić za pomocą statefull, Predicatektóry wykorzystuje fakt, że dla posortowanego strumienia wszystkie równe elementy sąsiadują:

Comparator<Person> c=Comparator.comparing(Person::getName);
stream.sorted(c).filter(new Predicate<Person>() {
    Person previous;
    public boolean test(Person p) {
      if(previous!=null && c.compare(previous, p)==0)
        return false;
      previous=p;
      return true;
    }
})./* more stream operations here */;

Oczywiście stanowe Predicatenie jest bezpieczne dla wątków, jednak jeśli tego potrzebujesz, możesz przenieść tę logikę do Collectori pozwolić, aby strumień zadbał o bezpieczeństwo wątków podczas używania twojego Collector. Zależy to od tego, co chcesz zrobić ze strumieniem różnych elementów, których nie powiedziałeś nam w swoim pytaniu.


1

Opierając się na odpowiedzi @ josketres, stworzyłem ogólną metodę użyteczności:

Możesz uczynić to bardziej przyjaznym dla Java 8, tworząc 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;
    }
}

1

Może przyda się komuś. Miałem trochę inny wymóg. Posiadanie listy obiektów Ainnych firm usuwa wszystkie, które mają to samo A.bpole dla tego samego A.id(wiele Aobiektów z tym samym A.idna liście). Tagir Valeev, autor odpowiedzi na partycję strumieniową, zainspirował mnie do użycia niestandardowego, który zwraca . Simple zrobi resztę.CollectorMap<A.id, List<A>>flatMap

 public static <T, K, K2> Collector<T, ?, Map<K, List<T>>> groupingDistinctBy(Function<T, K> keyFunction, Function<T, K2> distinctFunction) {
    return groupingBy(keyFunction, Collector.of((Supplier<Map<K2, T>>) 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)); }

1

Miałem sytuację, w której miałem uzyskać różne elementy z listy opartej na 2 kluczach. Jeśli chcesz odróżniać na podstawie dwóch kluczy lub może klucza złożonego, spróbuj tego

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

W moim przypadku musiałem kontrolować, co było poprzednim elementem. Potem stworzył pełnostanowego predykatu gdzie kontrolowane jeśli poprzedni elementem różnił się od bieżącego elementu, w tym przypadku trzymałem go.

public List<Log> fetchLogById(Long id) {
    return this.findLogById(id).stream()
        .filter(new LogPredicate())
        .collect(Collectors.toList());
}

public class LogPredicate implements Predicate<Log> {

    private Log previous;

    public boolean test(Log atual) {
        boolean isDifferent = previouws == null || verifyIfDifferentLog(current, previous);

        if (isDifferent) {
            previous = current;
        }
        return isDifferent;
    }

    private boolean verifyIfDifferentLog(Log current, Log previous) {
        return !current.getId().equals(previous.getId());
    }

}

0

Moje rozwiązanie na tej liście:

List<HolderEntry> result ....

List<HolderEntry> dto3s = new ArrayList<>(result.stream().collect(toMap(
            HolderEntry::getId,
            holder -> holder,  //or Function.identity() if you want
            (holder1, holder2) -> holder1 
    )).values());

W mojej sytuacji chcę znaleźć odrębne wartości i umieścić je na liście.


0

Chociaż najwyższa pozytywna odpowiedź jest absolutnie najlepszą odpowiedzią w Javie 8, jest jednocześnie absolutnie najgorsza pod względem wydajności. Jeśli naprawdę chcesz mieć kiepską aplikację o niskiej wydajności, skorzystaj z niej. Prosty wymóg wyodrębnienia unikalnego zestawu Nazwisk Osób zostanie osiągnięty przez zwykłe „For-Each” i „Set”. Gorzej, jeśli lista jest powyżej 10.

Rozważ, że masz kolekcję 20 obiektów, takich jak to:

public static final List<SimpleEvent> testList = Arrays.asList(
            new SimpleEvent("Tom"), new SimpleEvent("Dick"),new SimpleEvent("Harry"),new SimpleEvent("Tom"),
            new SimpleEvent("Dick"),new SimpleEvent("Huckle"),new SimpleEvent("Berry"),new SimpleEvent("Tom"),
            new SimpleEvent("Dick"),new SimpleEvent("Moses"),new SimpleEvent("Chiku"),new SimpleEvent("Cherry"),
            new SimpleEvent("Roses"),new SimpleEvent("Moses"),new SimpleEvent("Chiku"),new SimpleEvent("gotya"),
            new SimpleEvent("Gotye"),new SimpleEvent("Nibble"),new SimpleEvent("Berry"),new SimpleEvent("Jibble"));

Twój obiekt SimpleEventwygląda następująco:

public class SimpleEvent {

private String name;
private String type;

public SimpleEvent(String name) {
    this.name = name;
    this.type = "type_"+name;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getType() {
    return type;
}

public void setType(String type) {
    this.type = type;
}
}

Aby przetestować, masz taki kod JMH (Uwaga: używam tego samego odrębnego predykatuByKey wymienionego w zaakceptowanej odpowiedzi):

@Benchmark
@OutputTimeUnit(TimeUnit.SECONDS)
public void aStreamBasedUniqueSet(Blackhole blackhole) throws Exception{

    Set<String> uniqueNames = testList
            .stream()
            .filter(distinctByKey(SimpleEvent::getName))
            .map(SimpleEvent::getName)
            .collect(Collectors.toSet());
    blackhole.consume(uniqueNames);
}

@Benchmark
@OutputTimeUnit(TimeUnit.SECONDS)
public void aForEachBasedUniqueSet(Blackhole blackhole) throws Exception{
    Set<String> uniqueNames = new HashSet<>();

    for (SimpleEvent event : testList) {
        uniqueNames.add(event.getName());
    }
    blackhole.consume(uniqueNames);
}

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(MyBenchmark.class.getSimpleName())
            .forks(1)
            .mode(Mode.Throughput)
            .warmupBatchSize(3)
            .warmupIterations(3)
            .measurementIterations(3)
            .build();

    new Runner(opt).run();
}

Następnie uzyskasz wyniki testu porównawczego :

Benchmark                                  Mode  Samples        Score  Score error  Units
c.s.MyBenchmark.aForEachBasedUniqueSet    thrpt        3  2635199.952  1663320.718  ops/s
c.s.MyBenchmark.aStreamBasedUniqueSet     thrpt        3   729134.695   895825.697  ops/s

I jak widać, prosty For-Each ma 3 razy lepszą przepustowość i mniejszy wynik błędu w porównaniu do Java 8 Stream.

Wyższa przepustowość, lepsza wydajność


1
Dzięki, ale pytanie było bardzo konkretnie w kontekście Stream API
RichK

Tak, zgadzam się, już wspomniałem „Chociaż najwyższa pozytywna odpowiedź jest absolutnie najlepsza odpowiedź wrt Java 8”. Problem można rozwiązać na różne sposoby i próbuję tutaj podkreślić, że dany problem można rozwiązać w prosty sposób, a nie niebezpiecznie, przy użyciu strumieni Java 8, gdzie niebezpieczeństwem jest obniżenie wydajności. :)
Abhinav Ganguly

0
Here is the example
public class PayRoll {

    private int payRollId;
    private int id;
    private String name;
    private String dept;
    private int salary;


    public PayRoll(int payRollId, int id, String name, String dept, int salary) {
        super();
        this.payRollId = payRollId;
        this.id = id;
        this.name = name;
        this.dept = dept;
        this.salary = salary;
    }
} 

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class Prac {
    public static void main(String[] args) {

        int salary=70000;
        PayRoll payRoll=new PayRoll(1311, 1, "A", "HR", salary);
        PayRoll payRoll2=new PayRoll(1411, 2    , "B", "Technical", salary);
        PayRoll payRoll3=new PayRoll(1511, 1, "C", "HR", salary);
        PayRoll payRoll4=new PayRoll(1611, 1, "D", "Technical", salary);
        PayRoll payRoll5=new PayRoll(711, 3,"E", "Technical", salary);
        PayRoll payRoll6=new PayRoll(1811, 3, "F", "Technical", salary);
        List<PayRoll>list=new ArrayList<PayRoll>();
        list.add(payRoll);
        list.add(payRoll2);
        list.add(payRoll3);
        list.add(payRoll4);
        list.add(payRoll5);
        list.add(payRoll6);


        Map<Object, Optional<PayRoll>> k = list.stream().collect(Collectors.groupingBy(p->p.getId()+"|"+p.getDept(),Collectors.maxBy(Comparator.comparingInt(PayRoll::getPayRollId))));


        k.entrySet().forEach(p->
        {
            if(p.getValue().isPresent())
            {
                System.out.println(p.getValue().get());
            }
        });



    }
}

Output:

PayRoll [payRollId=1611, id=1, name=D, dept=Technical, salary=70000]
PayRoll [payRollId=1811, id=3, name=F, dept=Technical, salary=70000]
PayRoll [payRollId=1411, id=2, name=B, dept=Technical, salary=70000]
PayRoll [payRollId=1511, id=1, name=C, dept=HR, salary=70000]

-2

Jeśli chcesz wyświetlić listę osób, prosty sposób będzie następujący

Set<String> set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());

Dodatkowo, jeśli chcesz znaleźć odrębną lub unikalną listę nazwisk , nie Osoba , możesz to zrobić, stosując dwie następujące metody.

Metoda 1: użycie distinct

persons.stream().map(x->x.getName()).distinct.collect(Collectors.toList());

Metoda 2: użycie HashSet

Set<E> set = new HashSet<>();
set.addAll(person.stream().map(x->x.getName()).collect(Collectors.toList()));

2
To tworzy listę nazw, Persona nie s.
Hulk

1
Właśnie tego szukałem. Potrzebowałem metody jednowierszowej, aby wyeliminować duplikaty podczas przekształcania kolekcji w siebie. Dzięki.
Raj

-3

Najprostszy kod, jaki możesz napisać:

    persons.stream().map(x-> x.getName()).distinct().collect(Collectors.toList());

12
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.