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.
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))
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ésSet.add
renverra false
s'il n'a pas modifié la Set
, c'est-à-dire qu'il contient déjà la valeurBien entendu, cela ne fonctionne que si la liste prend en charge la suppression d'éléments.
Essayez ce code:
Collection<Employee> nonDuplicatedEmployees = employees.stream()
.<Map<Integer, Employee>> collect(HashMap::new,(m,e)->m.put(e.getId(), e), Map::putAll)
.values();
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());
Cela a fonctionné pour moi:
list.stream().distinct().collect(Collectors.toList());
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(..)
.
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()
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;
});
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);