Programing

Java 8의 속성을 기반으로 객체 목록에서 중복 제거

lottogame 2020. 12. 14. 07:43
반응형

Java 8의 속성을 기반으로 객체 목록에서 중복 제거


이 질문에 이미 답변이 있습니다.

일부 속성을 기반으로 개체 목록에서 중복을 제거하려고합니다.

Java 8을 사용하여 간단한 방법으로 할 수 있습니까?

List<Employee> employee

id직원의 재산에 따라 중복을 제거 할 수 있습니까 ? 중복 문자열을 문자열의 arraylist에서 제거하는 게시물을 보았습니다.


에서 스트림을 가져 와서 ID를 고유하게 비교하는 사용자 지정 비교기를 제공하는 List에 넣을 수 있습니다 TreeSet.

그런 다음 목록이 정말로 필요하면이 컬렉션을 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));

예를 들어 :

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

다음과 같이 출력됩니다.

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

또 다른 아이디어는 직원을 래핑하고 ID를 기반으로하는 equals 및 hashcode 메서드를 갖는 래퍼를 사용하는 것입니다.

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

그런 다음 각 인스턴스를 래핑 distinct()하고을 호출 하고 래핑을 풀고 결과를 목록으로 수집합니다.

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

실제로 비교를 수행하는 함수를 제공하여이 래퍼를 제네릭으로 만들 수 있다고 생각합니다.

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

매핑은 다음과 같습니다.

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

목록에서 직접 수행하는 가장 쉬운 방법은

HashSet<Object> seen=new HashSet<>();
employee.removeIf(e->!seen.add(e.getID()));
  • removeIf 지정된 기준을 충족하면 요소를 제거합니다.
  • Set.addfalse수정하지 않은 경우 반환 됩니다 Set. 즉 이미 값이 포함되어 있습니다.
  • 이 두 가지를 결합하면 이전에 ID가 발생한 모든 요소 (직원)가 제거됩니다.

물론 목록이 요소 제거를 지원하는 경우에만 작동합니다.


이 코드를 시도하십시오.

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

If you can make use of equals, then filter the list by using distinct within a stream (see answers above). If you can not or don't want to override the equals method, you can filter the stream in the following way for any property, e.g. for the property Name (the same for the property Id etc.):

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

If order does not matter and when it's more performant to run in parallel, Collect to a Map and then get values:

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

This worked for me:

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

You need to implement equals, of course


Another solution is to use a Predicate, then you can use this in any filter:

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

Then simply reuse the predicate anywhere:

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

Note: in the JavaDoc of filter, which says it takes a stateless Predicte. Actually, this works fine even if the stream is parallel.


About other solutions:

1) Using .collect(Collectors.toConcurrentMap(..)).values() is a good solution, but it's annoying if you want to sort and keep the order.

2) stream.removeIf(e->!seen.add(e.getID())); is also another very good solution. But we need to make sure the collection implemented removeIf, for example it will throw exception if we construct the collection use Arrays.asList(..).


Another version which is 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);

There are a lot of good answers here but I didn't find the one about using reduce method. So for your case, you can apply it in following way:

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

참고URL : https://stackoverflow.com/questions/29670116/remove-duplicates-from-a-list-of-objects-based-on-property-in-java-8

반응형