Java 8 Distinto por propriedade

455

No Java 8, como filtrar uma coleção usando o método Stream API, verificando a distinção de uma propriedade de cada objeto?

Por exemplo, tenho uma lista de Personobjetos e desejo remover pessoas com o mesmo nome,

persons.stream().distinct();

Usarei a verificação de igualdade padrão para um Personobjeto, então preciso de algo como,

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

Infelizmente, o distinct()método não possui essa sobrecarga. Sem modificar a verificação de igualdade dentro da Personclasse, é possível fazer isso de forma sucinta?

RichK
fonte

Respostas:

556

Considere distinctser um filtro com estado . Aqui está uma função que retorna um predicado que mantém o estado sobre o que foi visto anteriormente e que retorna se o elemento fornecido foi visto pela primeira vez:

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

Então você pode escrever:

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

Observe que, se o fluxo for ordenado e executado em paralelo, isso preservará um elemento arbitrário dentre as duplicatas, em vez da primeira, como o distinct()faz.

(Isso é essencialmente o mesmo que a minha resposta a esta pergunta: Java Lambda Stream Distinct () na chave arbitrária? )

Stuart Marks
fonte
27
Eu acho que para uma melhor compatibilidade o argumento deveria ser Function<? super T, ?>, não Function<? super T, Object>. Também deve-se notar que, para o fluxo paralelo ordenado, esta solução não garante qual objeto será extraído (diferente do normal distinct()). Também para fluxos sequenciais, há uma sobrecarga adicional no uso do CHM (que está ausente na solução @nosid). Finalmente, esta solução viola o contrato do filtermétodo cujo predicado deve ser sem estado, conforme declarado no JavaDoc. Mesmo assim votado.
Tagir Valeev
3
@java_newbie A instância retornada por Predicate distinctByKeynão tem idéia de se está sendo usada em um fluxo paralelo. Ele usa o CHM no caso de estar sendo usado em paralelo, embora isso adicione sobrecarga no caso seqüencial, como Tagir Valeev observou acima.
Stuart Marks
5
@holandaGo Falhará se você salvar e reutilizar a instância do Predicate retornada por distinctByKey. Mas funciona se você ligar distinctByKeysempre, para criar uma instância nova do Predicado toda vez.
Stuart marcas
3
@Chinmay não, não deveria. Se você usa .filter(distinctByKey(...)). Ele executará o método uma vez e retornará o predicado. Então, basicamente, o mapa já está sendo reutilizado se você o usar corretamente dentro de um fluxo. Se você tornar o mapa estático, ele será compartilhado para todos os usos. Portanto, se você tiver dois fluxos usando isso distinctByKey(), ambos usariam o mesmo mapa, que não é o que você deseja.
g00glen00b
3
Isso é tão inteligente e completamente não óbvio. Geralmente, este é um lambda com estado e o subjacente CallSiteserá vinculado ao get$Lambdamétodo - que retornará uma nova instância Predicateo tempo todo, mas essas instâncias compartilharão o mesmo mape functionaté onde eu entendo. Muito agradável!
Eugene
152

Uma alternativa seria colocar as pessoas em um mapa usando o nome como chave:

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

Observe que a Pessoa mantida, no caso de um nome duplicado, será o primeiro a ser convocado.

o que
fonte
23
@skiwi: você acha que existe uma maneira de implementar distinct()sem essa sobrecarga? Como alguma implementação saberia se já viu um objeto antes sem lembrar de todos os valores distintos que viu? Portanto, a sobrecarga toMape distinctprovavelmente é a mesma.
Holger
1
@ Holger Eu posso estar errado lá, porque eu não tinha pensado sobre a sobrecarga em distinct()si cria.
Skiwi
2
E, obviamente, ele bagunça a ordem original da lista #
077/16 Philipp
10
@ Philipp: pode ser corrigido mudando parapersons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
Holger
1
@DanielEarwicker esta pergunta é sobre "distinta por propriedade". Exigiria que o fluxo fosse classificado pela mesma propriedade , para poder tirar vantagem disso. Primeiro, o OP nunca declarou que o fluxo está classificado. Segundo, os fluxos não conseguem detectar se estão classificados por uma determinada propriedade . Terceiro, não há uma operação de fluxo "distinta por propriedade" genuína para fazer o que você sugere. Quarto, na prática, existem apenas duas maneiras de obter um fluxo classificado. Uma fonte classificada ( TreeSet) que já é distinta de qualquer maneira ou sortedno fluxo que também armazena em buffer todos os elementos.
Holger
101

Você pode agrupar os objetos da pessoa em outra classe, que compara apenas os nomes das pessoas. Depois, você desembrulha os objetos quebrados para que uma pessoa seja transmitida novamente. As operações de fluxo podem ter a seguinte aparência:

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

A classe Wrapperpode ter a seguinte aparência:

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
fonte
13
Isso é chamado de transformação Schwartziana
Stuart Caie
5
@StuartCaie Na verdade não ... não há memorização, e o ponto não é o desempenho, mas a adaptação à API existente.
Marko Topolnik
6
com.google.common.base.Equivalence.wrap (S) e com.google.common.base.Equivalence.Wrapper.get () também podem ajudar.
bjmi
Você pode tornar a classe do wrapper genérica e parametrizada por uma função de extração de chave.
Lii 28/07
O equalsmétodo pode ser simplificado parareturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
Holger
55

Outra solução, usando Set. Pode não ser a solução ideal, mas funciona

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

Ou, se você pode modificar a lista original, pode usar o método removeIf

persons.removeIf(p -> !set.add(p.getName()));
Santhosh
fonte
2
Esta é a melhor resposta se você não estiver usando nenhuma biblioteca de terceiros!
Manoj Shrestha
5
usando a ideia genial de que Set.add retornará true se esse conjunto ainda não contiver o elemento especificado. +1
Luvie 31/07/19
Acredito que esse método não funcione para processamento de fluxo paralelo, pois não é seguro para threads.
LoBo 25/03
@LoBo Provavelmente não. Esta é apenas uma ideia, que funcionará em casos simples. Os usuários podem estendê-lo para segurança / paralelismo de threads.
Santhosh 27/03
Abordagem interessante, mas parece um anti-padrão para modificar uma coleção externa (conjunto) enquanto filtra um fluxo em outra coleção (pessoas) ...
Justin Rowe
31

Há uma abordagem mais simples usando um TreeSet com um comparador personalizado.

persons.stream()
    .collect(Collectors.toCollection(
      () -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName())) 
));
josketres
fonte
4
Penso que a sua resposta ajuda na ordenação e não na singularidade. No entanto, isso me ajudou a definir meus pensamentos sobre como fazê-lo. Verifique aqui: stackoverflow.com/questions/1019854/…
janagn
Lembre-se de que você pagará o preço pela classificação dos elementos aqui e não precisamos classificá-los para encontrar duplicatas ou mesmo remover duplicatas.
pisaruk 04/07
12
Comparator.comparing (Person :: getName)
Jean-François Savard
24

Também podemos usar o RxJava ( biblioteca de extensão reativa muito poderosa )

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

ou

Observable.from(persons).distinct(p -> p.getName())
frhack
fonte
Rx é incrível, mas esta é uma resposta ruim. Observableé baseado em push, ao passo que Streamé baseado em pull. stackoverflow.com/questions/30216979/…
sdgfsdh
4
a pergunta pede uma solução java8 que não use necessariamente o stream. Minha resposta mostram que o fluxo java8 api é menos powefull de api rx
frhack
1
Usando o reator , seráFlux.fromIterable(persons).distinct(p -> p.getName())
Ritesh
A pergunta diz literalmente "usando a StreamAPI", não "não necessariamente usando fluxo". Dito isto, esta é uma ótima solução para o problema XY de filtrar o fluxo para valores distintos.
Justin M.
12

Você pode usar o groupingBycoletor:

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

Se você quiser ter outro fluxo, use:

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

Você pode usar o distinct(HashingStrategy)método nas Coleções Eclipse .

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

Se você puder refatorar personspara implementar uma interface de Coleções do Eclipse, poderá chamar o método diretamente na lista.

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

HashingStrategy é simplesmente uma interface de estratégia que permite definir implementações personalizadas de iguais e hashcode.

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

Nota: Sou um colaborador das Coleções Eclipse.

Craig P. Motlin
fonte
O método distinctBy foi adicionado no Eclipse Collections 9.0, o que pode simplificar ainda mais esta solução. medium.com/@donraab/...
Donald Raab
10

Eu recomendo usar o Vavr , se você puder. Com esta biblioteca, você pode fazer o seguinte:

io.vavr.collection.List.ofAll(persons)
                       .distinctBy(Person::getName)
                       .toJavaSet() // or any another Java 8 Collection
Mateusz Rasiński
fonte
Anteriormente conhecida como biblioteca "javaslang".
user11153 17/03
9

Você pode usar a biblioteca StreamEx :

StreamEx.of(persons)
        .distinct(Person::getName)
        .toList()
Sllouyssgort
fonte
Infelizmente, esse método da incrível biblioteca StreamEx é mal projetado - ele compara a igualdade de objetos em vez de usar iguais. Isso pode funcionar para Strings graças à internação de strings, mas também não.
Torque
7

Estendendo a resposta de Stuart Marks, isso pode ser feito de maneira mais curta e sem um mapa simultâneo (se você não precisar de fluxos paralelos):

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

Então ligue:

persons.stream().filter(distinctByKey(p -> p.getName());
Wojciech Górski
fonte
2
Este não leva em consideração que o fluxo pode ser paralelo.
Brunnsbe
Obrigado pelo comentário, atualizei minha resposta. Se você não precisar de um fluxo paralelo, a não utilização de mapas simultâneos oferece um desempenho muito melhor.
Wojciech Górski
Seu código provavelmente funcionaria para coleções paralelas se você criasse uma Collections.synchronizedSet(new HashSet<>()). Mas provavelmente seria mais lento do que com a ConcurrentHashMap.
Lii
7

Abordagem semelhante que Saeed Zarinfam usou, mas com mais estilo Java 8 :)

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(toList());
Alex
fonte
1
Eu substituiria a linha do mapa por flatMap(plans -> plans.stream().findFirst().stream())isso evita o uso de get em opcional Opcional #
Andrew Sneck
Talvez isso também esteja ok: flatMap (planos -> planos.stream (). Limite (1))
Rrr
6

Eu fiz uma versão genérica:

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

Um exemplo:

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
fonte
6
Set<YourPropertyType> set = new HashSet<>();
list
        .stream()
        .filter(it -> set.add(it.getYourProperty()))
        .forEach(it -> ...);
Andrew Novitskyi
fonte
2
Uma boa resposta tem uma explicação melhor Como faço para escrever uma boa resposta?
Narendra Jadhav
5

Minha abordagem é agrupar todos os objetos com a mesma propriedade, reduzir os grupos para o tamanho de 1 e, finalmente, coletá-los como a 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
fonte
3

A lista de objetos distintos pode ser encontrada usando:

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

A maneira mais fácil de implementar isso é pular o recurso de classificação, pois ele já fornece um opcional Comparatorque pode ser criado usando a propriedade de um elemento. Então você precisa filtrar as duplicatas, o que pode ser feito usando um statefull Predicateque usa o fato de que, para um fluxo classificado, todos os elementos iguais são adjacentes:

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

Obviamente, um statefull Predicatenão é seguro para threads, no entanto, se essa for sua necessidade, você pode mover essa lógica para ae Collectordeixar o fluxo cuidar da segurança de threads ao usar o seu Collector. Isso depende do que você deseja fazer com o fluxo de elementos distintos que você não nos contou na sua pergunta.

Holger
fonte
1

Com base na resposta de @ josketres, criei um método de utilitário genérico:

Você pode tornar isso mais amigável ao Java 8 criando um coletor .

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
fonte
1

Talvez seja útil para alguém. Eu tinha um pouco mais um requisito. Ter uma lista de objetos Ade terceiros remove todos os que têm o mesmo A.bcampo para o mesmo A.id(vários Aobjetos com o mesmo A.idna lista). A resposta da partição de fluxo por Tagir Valeev me inspirou a usar os Collectorretornos personalizados Map<A.id, List<A>>. Simples flatMapfará o resto.

 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
fonte
1

Eu tive uma situação, na qual eu deveria obter elementos distintos da lista com base em 2 chaves. Se você deseja distintas com base em duas chaves ou pode compor uma chave, tente este

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
fonte
0

No meu caso, eu precisava controlar qual era o elemento anterior. Em seguida, criei um Predicado com estado, onde eu controlava se o elemento anterior era diferente do elemento atual; nesse caso, eu o mantinha.

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
fonte
0

Minha solução nesta listagem:

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

Na minha situação, quero encontrar valores distintos e colocá-los na lista.

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

Enquanto a resposta mais alta votada é absolutamente a melhor resposta em Java 8, é ao mesmo tempo absolutamente pior em termos de desempenho. Se você realmente deseja um aplicativo de baixo desempenho e ruim, vá em frente e use-o. O simples requisito de extrair um conjunto exclusivo de Nomes de Pessoas deve ser alcançado com mero "For-Each" e um "Set". As coisas pioram ainda mais se a lista estiver acima do tamanho 10.

Considere que você tem uma coleção de 20 objetos, assim:

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

Onde seu objeto se SimpleEventparece com isso:

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

E para testar, você tem um código JMH como este (observe que estou usando o mesmo Predicado distintoByKey mencionado na resposta aceita):

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

Então você terá resultados de benchmark como este:

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

E, como você pode ver, um For-Each simples é 3 vezes melhor em taxa de transferência e menos em pontuação de erro em comparação com o Java 8 Stream.

Maior rendimento, melhor desempenho

Abhinav Ganguly
fonte
1
Obrigado, mas a pergunta estava muito específica no contexto da API de fluxo
RichK
Sim, eu concordo, eu já mencionei "Embora a resposta mais votada seja absolutamente a melhor resposta em Java 8". Um problema pode ser resolvido de n maneiras diferentes, e estou tentando destacar aqui que o problema em questão pode ser resolvido de maneira simples, e não perigosa, com o Java 8 Streams, onde o perigo é a degradação do desempenho. :)
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
fonte
-2

Se você deseja seguir a lista de pessoas, seria a maneira mais simples

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

Além disso, se você deseja encontrar uma lista distinta ou exclusiva de nomes , não Pessoa , você também pode usar os dois métodos a seguir.

Método 1: usando distinct

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

Método 2: usando HashSet

Set<E> set = new HashSet<>();
set.addAll(person.stream().map(x->x.getName()).collect(Collectors.toList()));
Abdur Rahman
fonte
2
Isso produz uma lista de nomes, não Persons.
Hulk #
1
Era exatamente isso que eu estava procurando. Eu precisava de um método de linha única para eliminar duplicatas enquanto transformava uma coleção em uma outra. Obrigado.
Raj
-3

O código mais simples que você pode escrever:

    persons.stream().map(x-> x.getName()).distinct().collect(Collectors.toList());
2Big2BeSmall
fonte
12
Que terá uma lista distinta de nomes, porém, não pessoas pelo nome
RichK