10

How can I get the distinct element from list based on multiple condition using java 8 stream ?

For example - Let's assume an object Person :

class Person {
    Integer id;
    String name;
}

I want to have a list with unique combination of id and name.
There can be multiple records with same id and name in list

Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
seasagar
  • 153
  • 2
  • 3
  • 11
  • What about using `hashCode()`? – Scott C Wilson Jan 09 '18 at 09:54
  • I have override the hash code and equals method in class Person but note sure java 8 stream will read this method or note. below is the expression I have written person.stream().distinct().collect(Collectors.toList()); – seasagar Jan 09 '18 at 09:56
  • @seasagar, I'm rarely sure of what will be leveraged by APIs myself, that's why I read their documentation. Does the documentation of distinct() tell you that it will make use of hashCode() or equals() ? – kumesana Jan 09 '18 at 10:12

6 Answers6

18

You can create your own distinct method for example :

private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Map<Object, Boolean> seen = new ConcurrentHashMap<>();
    return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

So you can use it with filter :

List<Person> persons = listPersons.stream()
         //@Holger suggest
        .filter(distinctByKey(pr -> Arrays.asList(pr.getId(), pr.getName())))
        .collect(toList());

Take a look at this :

If your list person is :

List<Person> listPersons = Arrays.asList(
        new Person(1, "person1"),
        new Person(1, "person5"),
        new Person(2, "person2"),
        new Person(1, "person1"),
        new Person(1, "person2"),
        new Person(1, "person1"),
        new Person(3, "person3")
);

Outputs

Person{id=1, name=person1}
Person{id=1, name=person5}
Person{id=2, name=person2}
Person{id=1, name=person2}
Person{id=3, name=person3}
Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
  • @YCF_L, thanks for the response. I am expecting the unique combination of id and name. So in the example above Person { id = 1 and name = person1} and Person { id = 1 and name = person5} should also come in output – seasagar Jan 09 '18 at 10:38
  • 3
    I strongly discourage from using a compound key like `pr.getId() + " " + pr.getName()`; it can be ambiguous if either string contains spaces and it will be inefficient if the strings are long. Fixing both issues is as easy as using `Arrays.asList(pr.getId(), pr.getName())` as key (or Java 9: `List.of(pr.getId(), pr.getName())`)… – Holger Jan 09 '18 at 13:31
  • 1
    @Holger if i understand your suggest, I should to change the filter to this `.filter(distinctByKey(pr -> Arrays.asList(pr.getId(), pr.getName())))` ? – Youcef LAIDANI Jan 09 '18 at 13:36
  • First time I see this way, Thank you @Holger I already edit my answer with your suggest – Youcef LAIDANI Jan 09 '18 at 13:39
  • 1
    The key point is that `List`s have a well defined (and suitable) equality and according hash code. Further, they keep the elements by reference, so its cheaper than string concatenation for larger strings. When using Java 9, `List.of(a, b)` will be preferable, as the result is immutable and its fixed sized overhead is smaller, but the equality and hashcode contract remains the same. – Holger Jan 09 '18 at 13:48
  • Thank you @Holger You really teach me something new here, I appreciate it – Youcef LAIDANI Jan 09 '18 at 13:55
2

Override hashCode() and equals() methods for your class, then you can do something like this:

Set<Person> result = persons.stream().collect(Collectors.toSet());

Set is a data structure that does not allow duplicates so this way you'll only have unique elements.

Here is a solution: https://ideone.com/e.js/G7T2rH

  • it's interesting which ones the OP wants to keep, the first one or the last one – Eugene Jan 09 '18 at 10:01
  • 1
    When the elements implement `hashCode()` and `equals()` properly, you can simply use `Set result = new HashSet<>(persons);` – Holger Jan 09 '18 at 13:34
2

Below are the steps to get the solution.

  1. You need to implement equals and hashcode in class Person for equality check. If you have overridden these methods in your class, whenever there is an equality check, the methods implemented in Person will be called and will always return the unique result.

     class Person {
    
     Integer id;
     String name;
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         Person p = (Person) o;
         return id == p.id && Objects.equals(name, p.name);
     }
     @Override
     public int hashCode() {
     return Objects.hash(id, name);
    

    }

}

  1. Use any of the below methods for getting distinct Person value:

    a). Use Set because Set contains only unique values. It's an unordered collection.

     Set<Person> set=new HashSet<Person>();
    
      for(Person c:list){
         set.add(c);
         }
    

    b). Using Java8 Stream API:

     List<Person> unique = list.stream().collect(collectingAndThen(toCollection(() -> new TreeSet<>(comparingInt(Person::getId))),
                     ArrayList::new));
    

    c). If ordered collection is required:

     LinkedHashSet<Person> lset=new LinkedHashSet<Person>();
     for(Personc:list){
         lset.add(c);
     }
    

    d) Java 8 Stream API method:

     List<Person> ll= list.stream().distinct().collect(Collectors.toList());
     ll.forEach((k) -> System.out.println(k.getId()+" & "+k.getName()));
    
garima garg
  • 308
  • 1
  • 8
1

You can filter your list first to get the elements that match the criteria and then use distinct.

List<element> getDistinctForCrit(Filter<element> pred) {
 //assuming list is already available in this class
 return list.stream().filter(pred).distinct().collect(Collectors.toList())
}

You can pass your filter criteria to this function and then the distinct will get the unique values for you.

distinct() takes produces distinct value based on the object comparison.

Therefore you should give your uniqueness logic in your equals() method of the class

Amar Dev
  • 1,360
  • 2
  • 18
  • 38
0

I am not sure, what exactly you are looking for, but i got a similar problem some time ago, may this helps a bit.

import java.util.ArrayList;
import java.util.List;

public class Person {

public int id;
public String name;
public static List<String> combinedIds;

public Person(int id, String name) {
    this.id = id;
    this.name = name;
}

public int getId() {
    return id;
}

public String getName() {
    return name;
}

public static String getCombinedUuid(Person person) {
    return person.getName() + person.getId();
}

public static void main(String[] args) {

    List<String> combinedIDs = new ArrayList<String>();

    Person person = new Person(12345, "Doe");

    String generatedUuid = getCombinedUuid(person);

    combinedIDs.add(generatedUuid);

    System.out.println(combinedIDs.get(0).toString());

}

}
Dominik Wolf
  • 23
  • 1
  • 6
-1

There is method Stream.distinct() method, that returns stream of unique values (based on Object.equals)

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#distinct--

Bartosz Bilicki
  • 12,599
  • 13
  • 71
  • 113