Java 8 Distinct par propriété

456

Dans Java 8, comment puis-je filtrer une collection à l'aide de l' StreamAPI en vérifiant la distinction d'une propriété de chaque objet?

Par exemple, j'ai une liste de Person objets et je veux supprimer des personnes du même nom,

persons.stream().distinct();

Va utiliser la vérification d'égalité par défaut pour un Person objet, j'ai donc besoin de quelque chose comme,

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

Malheureusement, la distinct()méthode n'a pas une telle surcharge. Sans modifier le contrôle d'égalité à l'intérieur de la Personclasse, est-il possible de le faire succinctement?

RichK
la source

Réponses:

557

Considérez distinctcomme un filtre dynamique . Voici une fonction qui retourne un prédicat qui maintient l'état de ce qu'il a vu précédemment, et qui retourne si l'élément donné a été vu pour la première fois:

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

Ensuite, vous pouvez écrire:

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

Notez que si le flux est ordonné et est exécuté en parallèle, cela préservera un élément arbitraire parmi les doublons, au lieu du premier, comme le distinct()fait.

(C'est essentiellement la même que ma réponse à cette question: Java Lambda Stream Distinct () sur une clé arbitraire? )

Marques Stuart
la source
27
Je suppose que pour une meilleure compatibilité, l'argument devrait être Function<? super T, ?>non Function<? super T, Object>. Il convient également de noter que pour le flux parallèle ordonné, cette solution ne garantit pas quel objet sera extrait (contrairement à la normale distinct()). De plus, pour les flux séquentiels, il y a une surcharge supplémentaire sur l'utilisation de CHM (qui est absent dans la solution @nosid). Enfin, cette solution viole le contrat de filterméthode dont le prédicat doit être sans état comme indiqué dans JavaDoc. Néanmoins voté.
Tagir Valeev
3
@java_newbie L'instance de prédicat renvoyée par distinctByKeyn'a aucune idée de son utilisation dans un flux parallèle. Il utilise CHM dans le cas où il est utilisé en parallèle, bien que cela ajoute des frais généraux dans le cas séquentiel comme Tagir Valeev l'a noté ci-dessus.
Stuart marque
5
@holandaGo Il échouera si vous enregistrez et réutilisez l'instance de prédicat renvoyée par distinctByKey. Mais cela fonctionne si vous appelez à distinctByKeychaque fois, de sorte qu'il crée à chaque fois une nouvelle instance de prédicat.
Stuart marque
3
@Chinmay non, ça ne devrait pas. Si vous utilisez .filter(distinctByKey(...)). Il exécutera la méthode une fois et renverra le prédicat. Donc, fondamentalement, la carte est déjà réutilisée si vous l'utilisez correctement dans un flux. Si vous rendiez la carte statique, la carte serait partagée pour tous les usages. Donc, si vous avez deux flux utilisant ceci distinctByKey(), les deux utiliseraient la même carte, ce qui n'est pas ce que vous voulez.
g00glen00b
3
C'est tellement intelligent et complètement non évident. Généralement, il s'agit d'un lambda avec état et le sous-jacent CallSitesera lié à la get$Lambdaméthode - qui renverra une nouvelle instance de Predicatetout le temps, mais ces instances partageront la même chose mapet functionpour autant que je sache. Très agréable!
Eugene
152

Une alternative serait de placer les personnes sur une carte en utilisant le nom comme clé:

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

Notez que la Personne conservée, en cas de nom en double, sera la première rencontrée.

wha'eve '
la source
23
@skiwi: pensez-vous qu'il existe un moyen de mettre en œuvre distinct()sans cette surcharge? Comment une implémentation pourrait-elle savoir si elle a déjà vu un objet sans se souvenir de toutes les valeurs distinctes qu'elle a vues? Ainsi, les frais généraux toMapet distinctsont très probablement les mêmes.
Holger
1
@ Holger, je me suis peut-être trompé, car je n'avais pas pensé à ce que les frais généraux pouvaient distinct()créer.
skiwi
2
Et évidemment, cela perturbe l'ordre initial de la liste
Philipp
10
@Philipp: pourrait être corrigé en changeant pourpersons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
Holger
1
@DanielEarwicker cette question concerne "distinct par propriété". Il faudrait que le flux soit trié par la même propriété , pour pouvoir en profiter. Premièrement, l'OP n'a jamais déclaré que le flux était trié du tout. Deuxièmement, les flux ne sont pas en mesure de détecter s'ils sont triés par une certaine propriété . Troisièmement, il n'y a pas de véritable opération de flux «distinct par propriété» pour faire ce que vous suggérez. Quatrièmement, en pratique, il n'y a que deux façons d'obtenir un tel flux trié. Une source triée ( TreeSet) qui est déjà distincte de toute façon ou sortedsur le flux qui met également en mémoire tampon tous les éléments.
Holger
101

Vous pouvez encapsuler les objets personne dans une autre classe, qui compare uniquement les noms des personnes. Ensuite, vous déballez les objets enveloppés pour obtenir à nouveau un flux de personne. Les opérations de flux peuvent se présenter comme suit:

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

La classe Wrapperpourrait ressembler à ceci:

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();
    }
}
nosid
la source
13
C'est ce qu'on appelle la transformation de Schwartzian
Stuart Caie
5
@StuartCaie Pas vraiment ... il n'y a pas de mémorisation, et le point n'est pas la performance, mais l'adaptation à l'API existante.
Marko Topolnik
6
com.google.common.base.Equivalence.wrap (S) et com.google.common.base.Equivalence.Wrapper.get () pourraient également vous aider.
bjmi
Vous pouvez rendre la classe wrapper générique et paramétrée par une fonction d'extraction de clé.
Lii
La equalsméthode peut être simplifiée pourreturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
Holger
55

Une autre solution, en utilisant Set. Ce n'est peut-être pas la solution idéale, mais ça marche

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

Ou si vous pouvez modifier la liste d'origine, vous pouvez utiliser la méthode removeIf

persons.removeIf(p -> !set.add(p.getName()));
Santhosh
la source
2
C'est la meilleure réponse si vous n'utilisez aucune bibliothèque tierce!
Manoj Shrestha
5
en utilisant l'idée géniale que Set.add renvoie true si cet ensemble ne contenait pas déjà l'élément spécifié. +1
Luvie
Je crois que cette méthode ne fonctionne pas pour le traitement de flux parallèle, car elle n'est pas adaptée aux threads.
LoBo
@LoBo Probablement pas. Ce n'est qu'une idée, qui fonctionnera pour des cas simples. Les utilisateurs peuvent l'étendre pour la sécurité des threads / parallélisme.
Santhosh
Approche intéressante, mais ressemble un peu à un anti-modèle pour modifier une collection externe (ensemble) tout en filtrant un flux sur une autre collection (personnes) ...
Justin Rowe
31

Il existe une approche plus simple en utilisant un TreeSet avec un comparateur personnalisé.

persons.stream()
    .collect(Collectors.toCollection(
      () -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName())) 
));
josketres
la source
4
Je pense que votre réponse aide à la commande et non à l'unicité. Cependant, cela m'a aidé à définir mes réflexions sur la façon de le faire. Vérifiez ici: stackoverflow.com/questions/1019854/…
janagn
Gardez à l'esprit que vous paierez le prix pour trier les éléments ici et nous n'avons pas besoin de trier pour trouver des doublons ou même supprimer des doublons.
pisaruk
12
Comparator.comparing (Person :: getName)
Jean-François Savard
24

Nous pouvons également utiliser RxJava ( bibliothèque d' extensions réactives très puissante )

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

ou

Observable.from(persons).distinct(p -> p.getName())
frhack
la source
Rx est génial, mais c'est une mauvaise réponse. Observableest basé sur push alors qu'il Streamest basé sur pull. stackoverflow.com/questions/30216979/…
sdgfsdh
4
la question demande une solution java8 n'utilisant pas nécessairement stream. Ma réponse montre que l'API java8 stream est moins puissante que l'API
RX
1
En utilisant le réacteur , ce seraFlux.fromIterable(persons).distinct(p -> p.getName())
Ritesh
La question dit littéralement «en utilisant l' StreamAPI», pas «pas nécessairement en utilisant stream». Cela dit, c'est une excellente solution au problème XY de filtrage du flux en valeurs distinctes.
M. Justin
12

Vous pouvez utiliser le groupingBycollecteur:

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

Si vous souhaitez avoir un autre flux, vous pouvez utiliser ceci:

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));
Saeed Zarinfam
la source
11

Vous pouvez utiliser la distinct(HashingStrategy)méthode dans les collections Eclipse .

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

Si vous pouvez refactoriser personspour implémenter une interface Eclipse Collections, vous pouvez appeler la méthode directement dans la liste.

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

HashingStrategy est simplement une interface de stratégie qui vous permet de définir des implémentations personnalisées d'equals et de hashcode.

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

Remarque: je suis un committer pour les collections Eclipse.

Craig P. Motlin
la source
La méthode distinctBy a été ajoutée dans Eclipse Collections 9.0, ce qui peut simplifier davantage cette solution. medium.com/@donraab/…
Donald Raab
10

Je vous recommande d'utiliser Vavr , si vous le pouvez. Avec cette bibliothèque, vous pouvez effectuer les opérations suivantes:

io.vavr.collection.List.ofAll(persons)
                       .distinctBy(Person::getName)
                       .toJavaSet() // or any another Java 8 Collection
Mateusz Rasiński
la source
Anciennement connue sous le nom de bibliothèque "javaslang".
user11153
9

Vous pouvez utiliser la bibliothèque StreamEx :

StreamEx.of(persons)
        .distinct(Person::getName)
        .toList()
Sllouyssgort
la source
Malheureusement, cette méthode de la bibliothèque StreamEx, par ailleurs géniale, est mal conçue - elle compare l'égalité des objets au lieu d'utiliser des égaux. Cela peut fonctionner pendant Strings grâce à l'internement de chaînes, mais cela peut aussi ne pas fonctionner.
Couple le
7

En étendant la réponse de Stuart Marks, cela peut être fait de manière plus courte et sans carte simultanée (si vous n'avez pas besoin de flux parallèles):

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

Appelez ensuite:

persons.stream().filter(distinctByKey(p -> p.getName());
Wojciech Górski
la source
2
Celui-ci ne prend pas en compte le fait que le flux peut être parallèle.
brunnsbe
Merci pour le commentaire, j'ai mis à jour ma réponse. Si vous n'avez pas besoin d'un flux parallèle, ne pas utiliser de cartes simultanées vous offre de bien meilleures performances.
Wojciech Górski
Votre code fonctionnerait probablement pour les collections parallèles si vous en créiez un à la Collections.synchronizedSet(new HashSet<>())place. Mais ce serait probablement plus lent qu'avec un ConcurrentHashMap.
Lii
7

Approche similaire utilisée par Saeed Zarinfam mais plus de style Java 8 :)

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(toList());
Alex
la source
1
Je remplacerais la ligne de carte avec flatMap(plans -> plans.stream().findFirst().stream())elle évite l'utilisation de get on Facultatif
Andrew Sneck
Peut-être que c'est aussi ok: flatMap (plans -> plans.stream (). Limit (1))
Rrr
6

J'ai fait une version générique:

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()
    );
}

Un exemple:

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())
Guillaume Cornet
la source
6
Set<YourPropertyType> set = new HashSet<>();
list
        .stream()
        .filter(it -> set.add(it.getYourProperty()))
        .forEach(it -> ...);
Andrew Novitskyi
la source
2
Une bonne réponse a une meilleure explication Comment écrire une bonne réponse?
Narendra Jadhav
5

Mon approche consiste à regrouper tous les objets ayant la même propriété, puis à raccourcir les groupes à la taille 1, puis à les collecter finalement sous la forme d'un 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());
uneq95
la source
3

La liste des objets distincts peut être trouvée en utilisant:

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

La façon la plus simple de l'implémenter est de sauter sur la fonction de tri car elle fournit déjà une option Comparatorqui peut être créée en utilisant la propriété d'un élément. Ensuite, vous devez filtrer les doublons, ce qui peut être fait en utilisant un état Predicatequi utilise le fait que pour un flux trié, tous les éléments égaux sont adjacents:

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 */;

Bien sûr, un état Predicaten'est pas sûr pour les threads, mais si tel est votre besoin, vous pouvez déplacer cette logique dans un Collectoret laisser le flux s'occuper de la sécurité des threads lorsque vous utilisez votre Collector. Cela dépend de ce que vous voulez faire avec le flux d'éléments distincts que vous ne nous avez pas dit dans votre question.

Holger
la source
1

Sur la base de la réponse de @ josketres, j'ai créé une méthode utilitaire générique:

Vous pouvez rendre cela plus compatible avec Java 8 en créant un collecteur .

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;
    }
}
Garrett Smith
la source
1

Peut-être sera utile à quelqu'un. J'avais un peu une autre exigence. Avoir la liste des objets Ade tiers supprimer tous ceux qui ont le même A.bchamp pour le même A.id(plusieurs Aobjets avec le même A.iddans la liste). Partition flux réponse par Tagir Valeev m'a inspiré d'utiliser la coutume Collectorqui les retours Map<A.id, List<A>>. Simple flatMapfera le reste.

 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)); }
Aliaksei Yatsau
la source
1

J'ai eu une situation, où j'étais supposé obtenir des éléments distincts de la liste sur la base de 2 clés. Si vous voulez une distinction sur la base de deux clés ou une clé composite, essayez ceci

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());
Akanksha gore
la source
0

Dans mon cas, j'avais besoin de contrôler quel était l'élément précédent. J'ai ensuite créé un prédicat avec état où j'ai contrôlé si l'élément précédent était différent de l'élément actuel, dans ce cas, je l'ai conservé.

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());
    }

}
Flavio Oliva
la source
0

Ma solution dans cette liste:

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

Dans ma situation, je veux trouver des valeurs distinctes et les mettre dans la liste.

Евгений Трахимович
la source
0

Alors que la réponse la plus élevée est la meilleure réponse à Java 8, elle est en même temps absolument pire en termes de performances. Si vous voulez vraiment une mauvaise application peu performante, allez-y et utilisez-la. La simple exigence d'extraction d'un ensemble unique de noms de personne doit être obtenue par de simples «pour chacun» et un «ensemble». Les choses empirent encore si la liste dépasse 10.

Considérez que vous avez une collection de 20 objets, comme ceci:

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"));

Où vous vous opposez SimpleEventressemble à ceci:

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;
}
}

Et pour tester, vous avez un code JMH comme celui-ci, (veuillez noter que j'utilise le même prédicat distinctByKey mentionné dans la réponse acceptée):

@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();
}

Ensuite, vous aurez des résultats de référence comme celui-ci:

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

Et comme vous pouvez le voir, un simple For-Each a un débit 3 fois supérieur et un score d'erreur inférieur à Java 8 Stream.

Plus le débit est élevé, meilleures sont les performances

Abhinav Ganguly
la source
1
Merci, mais la question était très spécifique dans le contexte de l'API Stream
RichK
Oui, je suis d'accord, j'ai déjà mentionné "Alors que la réponse la plus élevée est absolument la meilleure réponse par rapport à Java 8". Un problème peut être résolu de différentes manières, et j'essaie de souligner ici que le problème en question peut être résolu simplement, plutôt que dangereusement avec Java 8 Streams, où le danger est la dégradation des performances. :)
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]
Sourav Sharma
la source
-2

Si vous voulez lister les personnes suivantes, ce serait le moyen le plus simple

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

En outre, si vous souhaitez rechercher une liste de noms distincte ou unique , et non Personne , vous pouvez également utiliser la méthode suivante.

Méthode 1: utilisation distinct

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

Méthode 2: utilisation HashSet

Set<E> set = new HashSet<>();
set.addAll(person.stream().map(x->x.getName()).collect(Collectors.toList()));
Abdur Rahman
la source
2
Cela produit une liste de noms, pas l' Personart.
Hulk
1
Ceci est exactement ce que je cherchais. J'avais besoin d'une méthode sur une seule ligne pour éliminer les doublons tout en transformant une collection en une autre. Merci.
Raj
-3

Le code le plus simple que vous puissiez écrire:

    persons.stream().map(x-> x.getName()).distinct().collect(Collectors.toList());
2Big2BeSmall
la source
12
Cela obtiendra une liste distincte de noms, pas de personnes par nom
RichK