web-dev-qa-db-fra.com

Supprimer les doublons d'une liste d'objets basés sur une propriété dans Java 8

J'essaie de supprimer les doublons d'une liste d'objets en fonction de certaines propriétés.

pouvons-nous le faire d'une manière simple en utilisant Java 8

List<Employee> employee

Peut-on en supprimer les doublons en fonction de la propriété id de employee. J'ai vu des publications supprimer des chaînes en double en tant qu'arrayliste de chaîne.

46
Patan

Vous pouvez obtenir un flux de la List et l'insérer dans la TreeSet à partir de laquelle vous fournissez un comparateur personnalisé qui compare l'identifiant de manière unique. 

Ensuite, si vous avez vraiment besoin d’une liste, vous pouvez ensuite la placer dans une ArrayList.

import static Java.util.Comparator.comparingInt;
import static Java.util.stream.Collectors.collectingAndThen;
import static Java.util.stream.Collectors.toCollection;

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

Étant donné l'exemple:

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

Il produira:

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

Une autre idée pourrait être d’utiliser un wrapper qui enveloppe un employé et utilise la méthode equals et hashcode avec 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 emballez chaque instance, appelez distinct(), décompressez-les et collectez le résultat dans une liste.

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

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

class Wrapper<T, U> {
    private T t;
    private Function<T, U> equalityFunction;

    public Wrapper(T t, Function<T, U> 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<T, U> that = (Wrapper<T, U>) 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))
79
Alexis C.

Le moyen le plus simple de le faire directement dans la liste est

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

Bien entendu, cela ne fonctionne que si la liste prend en charge la suppression d'éléments.

40
Holger

Essayez ce code:

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

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

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

Cela a fonctionné pour moi:

list.stream().distinct().collect(Collectors.toList());
2
Seba D'Agostino

Une autre solution consiste à utiliser un prédicat, que vous pouvez utiliser dans n'importe quel filtre:

public static <T> Predicate<T> distinctBy(Function<? super T, ?> f) {
  Set<Object> 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));

Remarque: dans le filtre JavaDoc, qui dit qu'il faut un prédicte sans état. En fait, cela fonctionne bien même si le flux est parallèle.


À propos d'autres solutions:

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

2) stream.removeIf(e->!seen.add(e.getID())); est également une autre très bonne solution. Mais nous devons nous assurer que la collection implémentée removeIf, par exemple, déclenchera une exception si nous construisons la collection, utilisez Arrays.asList(..).

2
navins

Si l'ordre n'a pas d'importance et qu'il est plus performant de s'exécuter en parallèle, Collectez vers une carte et obtenez les valeurs suivantes:

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

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

 List<Employee> employeeList = employees.stream()
      .reduce(new ArrayList<>(), (List<Employee> 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;
      });
0
asdasdsdf

Une autre version qui est simple

BiFunction<TreeSet<Employee>,List<Employee> ,TreeSet<Employee>> appendTree = (y,x) -> (y.addAll(x))? y:y;

TreeSet<Employee> outputList = appendTree.apply(new TreeSet<Employee>(Comparator.comparing(p->p.getId())),personList);
0
zawhtut