Supprimer les doublons d’une liste d’objects basée sur la propriété dans Java 8

J’essaie de supprimer les doublons d’une liste d’objects basée sur une propriété.

pouvons-nous le faire d’une manière simple en utilisant java 8

List employee 

Pouvons-nous en supprimer les doublons en fonction de la propriété id de l’employé? J’ai vu des publications supprimer des chaînes en double en tant qu’arrayliste de chaîne.

Vous pouvez obtenir un stream à partir de la List et l’installer dans le TreeSet partir duquel vous fournissez un comparateur personnalisé qui compare l’identifiant de manière unique.

Ensuite, si vous avez vraiment besoin d’une liste, vous pouvez replacer cette collection dans une ArrayList.

 import static java.util.Comparator.comparingInt; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toCollection; ... List unique = employee.stream() .collect(collectingAndThen(toCollection(() -> new TreeSet<>(comparingInt(Employee::getId))), ArrayList::new)); 

Vu l’exemple:

 List employee = Arrays.asList(new Employee(1, "John"), new Employee(1, "Bob"), new Employee(2, "Alice")); 

Il va sortir:

 [Employee{id=1, name='John'}, Employee{id=2, name='Alice'}] 

Une autre idée pourrait être d’utiliser un wrapper qui encapsule un employé et utilise la méthode d’égalité et de hashcode basée sur son identifiant:

 class WrapperEmployee { private Employee e; public WrapperEmployee(Employee e) { this.e = e; } public Employee unwrap() { return this.e; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; WrapperEmployee that = (WrapperEmployee) o; return Objects.equals(e.getId(), that.e.getId()); } @Override public int hashCode() { return Objects.hash(e.getId()); } } 

Ensuite, vous enroulez chaque instance, appelez distinct() , déballez-les et collectez le résultat dans une liste.

 List unique = employee.stream() .map(WrapperEmployee::new) .distinct() .map(WrapperEmployee::unwrap) .collect(toList()); 

En fait, je pense que vous pouvez rendre ce wrapper générique en fournissant une fonction qui fera la comparaison:

 class Wrapper { private T t; private Function equalityFunction; public Wrapper(T t, Function equalityFunction) { this.t = t; this.equalityFunction = equalityFunction; } public T unwrap() { return this.t; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @SuppressWarnings("unchecked") Wrapper that = (Wrapper) o; return Objects.equals(equalityFunction.apply(this.t), that.equalityFunction.apply(that.t)); } @Override public int hashCode() { return Objects.hash(equalityFunction.apply(this.t)); } } 

et la cartographie sera:

 .map(e -> new Wrapper<>(e, Employee::getId)) 

La façon la plus simple de le faire directement dans la liste est

 HashSet seen=new HashSet<>(); employee.removeIf(e->!seen.add(e.getID())); 
  • removeIf va supprimer un élément s’il répond aux critères spécifiés
  • Set.add retournera false s’il n’a pas modifié l’ Set , c’est-à-dire qu’il contient déjà la valeur
  • en combinant ces deux éléments, il supprimera tous les éléments (employés) dont l’identifiant a été rencontré avant

Bien sûr, cela ne fonctionne que si la liste prend en charge la suppression d’éléments.

Essayez ce code:

 Collection nonDuplicatedEmployees = employees.stream() .> collect(HashMap::new,(m,e)->m.put(e.getId(), e), Map::putAll) .values(); 

Si l’ordre n’a pas d’importance et qu’il est plus performant de courir en parallèle, rassemblez-vous sur une carte et obtenez ensuite des valeurs:

 employee.stream().collect(Collectors.toConcurrentMap(Employee::getId, Function.identity(), (p, q) -> p)).values() 

Si vous pouvez utiliser des equals , filtrez la liste en utilisant distinct dans un stream (voir les réponses ci-dessus). Si vous ne pouvez pas ou ne voulez pas remplacer la méthode equals , vous pouvez filter le stream de la manière suivante pour toute propriété, par exemple pour la propriété Name (identique pour la propriété Id, etc.):

 Set nameSet = new HashSet<>(); List employeesDistinctByName = employees.stream() .filter(e -> nameSet.add(e.getName())) .collect(Collectors.toList()); 

Cela a fonctionné pour moi:

 list.stream().distinct().collect(Collectors.toList()); 

Une autre solution consiste à utiliser un prédicat, vous pouvez alors l’utiliser dans n’importe quel filtre:

 public static  Predicate distinctBy(Function f) { Set objects = new ConcurrentHashSet<>(); return t -> objects.add(f.apply(t)); } 

Ensuite, réutilisez simplement le prédicat n’importe où:

 employees.stream().filter(distinctBy(e -> e.getId)); 

Note: dans le JavaDoc du filtre, qui dit qu’il prend un Predicte sans état. En fait, cela fonctionne très bien même si le stream est parallèle.

A propos des autres solutions:

1) Utiliser .collect(Collectors.toConcurrentMap(..)).values() est une bonne solution, mais c’est gênant si vous souhaitez sortinger et conserver la commande.

2) stream.removeIf(e->!seen.add(e.getID())); est aussi une autre très bonne solution. Mais nous devons nous assurer que la collection implémente removeIf, par exemple, elle lancera une exception si nous construisons la collection en utilisant Arrays.asList(..) .

Une autre version simple

 BiFunction,List ,TreeSet> appendTree = (y,x) -> (y.addAll(x))? y:y; TreeSet outputList = appendTree.apply(new TreeSet(Comparator.comparing(p->p.getId())),personList); 

Il y a beaucoup de bonnes réponses ici mais je n’ai pas trouvé celle sur l’utilisation de reduce méthode de reduce . Donc, pour votre cas, vous pouvez l’appliquer de la manière suivante:

  List employeeList = employees.stream() .reduce(new ArrayList<>(), (List accumulator, Employee employee) -> { if (accumulator.stream().noneMatch(emp -> emp.getId().equals(employee.getId()))) { accumulator.add(employee); } return accumulator; }, (acc1, acc2) -> { acc1.addAll(acc2); return acc1; });