首页 文章

Java 8属性不同

提问于
浏览 101
290

在Java 8中,如何通过检查每个对象的属性的清晰度来使用 Stream API过滤集合?

例如,我有一个 Person 对象的列表,我想删除具有相同名称的人,

persons.stream().distinct();

将使用 Person 对象的默认相等检查,所以我需要像,

persons.stream().distinct(p -> p.getName());

不幸的是 distinct() 方法没有这样的重载 . 如果不修改 Person 类中的相等性检查,是否可以简洁地执行此操作?

20 回答

  • 349

    另一种方法是使用名称作为关键字将人员放在 Map 中:

    persons.collect(toMap(Person::getName, p -> p, (p, q) -> p)).values();
    

    请注意,如果名称重复,则保留的Person将是第一个被限制的人 .

  • 90

    在@ josketres的回答基础上,我创建了一个通用的实用方法:

    你可以通过创建一个Collector来使这个Java 8更加友好 .

    public static <T> Set<T> removeDuplicates(Collection<T> input, Comparator<T> comparer) {
        return input.stream()
                .collect(toCollection(() -> new TreeSet<>(comparer)));
    }
    
    
    @Test
    public void removeDuplicatesWithDuplicates() {
        ArrayList<C> input = new ArrayList<>();
        Collections.addAll(input, new C(7), new C(42), new C(42));
        Collection<C> result = removeDuplicates(input, (c1, c2) -> Integer.compare(c1.value, c2.value));
        assertEquals(2, result.size());
        assertTrue(result.stream().anyMatch(c -> c.value == 7));
        assertTrue(result.stream().anyMatch(c -> c.value == 42));
    }
    
    @Test
    public void removeDuplicatesWithoutDuplicates() {
        ArrayList<C> input = new ArrayList<>();
        Collections.addAll(input, new C(1), new C(2), new C(3));
        Collection<C> result = removeDuplicates(input, (t1, t2) -> Integer.compare(t1.value, t2.value));
        assertEquals(3, result.size());
        assertTrue(result.stream().anyMatch(c -> c.value == 1));
        assertTrue(result.stream().anyMatch(c -> c.value == 2));
        assertTrue(result.stream().anyMatch(c -> c.value == 3));
    }
    
    private class C {
        public final int value;
    
        private C(int value) {
            this.value = value;
        }
    }
    
  • 74

    您可以将person对象包装到另一个类中,该类仅比较人员的名称 . 然后,您打开包装的对象以再次获取人流 . 流操作可能如下所示:

    persons.stream()
        .map(Wrapper::new)
        .distinct()
        .map(Wrapper::unwrap)
        ...;
    

    Wrapper 可能如下所示:

    class Wrapper {
        private final Person person;
        public Wrapper(Person person) {
            this.person = person;
        }
        public Person unwrap() {
            return person;
        }
        public boolean equals(Object other) {
            if (other instanceof Wrapper) {
                return ((Wrapper) other).person.getName().equals(person.getName());
            } else {
                return false;
            }
        }
        public int hashCode() {
            return person.getName().hashCode();
        }
    }
    
  • 22

    你可以使用 groupingBy collector:

    persons.collect(groupingBy(p -> p.getName())).values().forEach(t -> System.out.println(t.get(0).getId()));
    

    如果你想拥有另一个流,你可以使用它:

    persons.collect(groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));
    
  • 21

    你可以使用StreamEx库:

    StreamEx.of(persons)
            .distinct(Person::getName)
            .toList()
    
  • 15

    扩展Stuart Marks的答案,这可以用更短的方式完成,没有并发映射(如果你不需要并行流):

    public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        final Set<Object> seen = new HashSet<>();
        return t -> seen.add(keyExtractor.apply(t));
    }
    

    然后打电话:

    persons.stream().filter(distinctByKey(p -> p.getName());
    
  • 9

    您可以在Eclipse Collections中使用 distinct(HashingStrategy) 方法 .

    List<Person> persons = ...;
    MutableList<Person> distinct =
        ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));
    

    如果您可以重构 persons 来实现Eclipse Collections接口,则可以直接在列表上调用该方法 .

    MutableList<Person> persons = ...;
    MutableList<Person> distinct =
        persons.distinct(HashingStrategies.fromFunction(Person::getName));
    

    HashingStrategy只是一个策略接口,允许您定义equals和hashcode的自定义实现 .

    public interface HashingStrategy<E>
    {
        int computeHashCode(E object);
        boolean equals(E object1, E object2);
    }
    

    注意:我是Eclipse Collections的提交者 .

  • 6

    考虑 distinct 是一个有状态过滤器 . 这是一个函数,它返回一个谓词,该谓词维护前面所看到的状态,并返回给定元素是否第一次被看到:

    public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }
    

    然后你可以写:

    persons.stream().filter(distinctByKey(Person::getName))
    

    请注意,如果流是有序的并且并行运行,这将保留重复项中的任意元素,而不是第一个,如 distinct() .

    (这与此问题的my answer基本相同:Java Lambda Stream Distinct() on arbitrary key?

  • 6

    使用带有自定义比较器的TreeSet有一种更简单的方法 .

    persons.stream()
        .collect(Collectors.toCollection(
          () -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName())) 
    ));
    
  • 6

    另一种解决方案,使用 Set . 可能不是理想的解决方案,但它确实有效

    Set<String> set = new HashSet<>(persons.size());
    persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());
    

    或者,如果您可以修改原始列表,则可以使用removeIf方法

    persons.removeIf(p -> !set.add(p.getName()));
    
  • 6

    也许对某些人有用 . 我还有一点要求 . 拥有来自第三方的对象列表 A 将删除所有具有相同 A.id 字段的相同 A.id (列表中具有相同 A.id 的多个 A 对象) . Stream partition回答Tagir Valeev启发我使用自定义 Collector 返回 Map<A.id, List<A>> . 简单的 flatMap 将完成其余的工作 .

    public static <T, K, K2> Collector<T, ?, Map<K, List<T>>> groupingDistinctBy(Function<T, K> keyFunction, Function<T, K2> distinctFunction) {
        return groupingBy(keyFunction, Collector.of((Supplier<Map<K2, T>>) HashMap::new,
                (map, error) -> map.putIfAbsent(distinctFunction.apply(error), error),
                (left, right) -> {
                    left.putAll(right);
                    return left;
                }, map -> new ArrayList<>(map.values()),
                Collector.Characteristics.UNORDERED)); }
    
  • 5

    使用以下两种方法也可以找到不同或唯一的列表 .

    方法1:使用Distinct

    yourObjectName.stream().map(x->x.yourObjectProperty).distinct.collect(Collectors.toList());
    

    方法2:使用HashSet

    Set<E> set = new HashSet<>();
    set.addAll(yourObjectName.stream().map(x->x.yourObjectProperty).collect(Collectors.toList()));
    
  • 4

    我们也可以使用RxJava(非常强大的reactive extension库)

    Observable.from(persons).distinct(Person::getName)
    

    要么

    Observable.from(persons).distinct(p -> p.getName())
    
  • 4

    我做了一个通用版本:

    private <T, R> Collector<T, ?, Stream<T>> distinctByKey(Function<T, R> keyExtractor) {
        return Collectors.collectingAndThen(
                toMap(
                        keyExtractor,
                        t -> t,
                        (t1, t2) -> t1
                ),
                (Map<R, T> map) -> map.values().stream()
        );
    }
    

    一个例子:

    Stream.of(new Person("Jean"), 
              new Person("Jean"),
              new Person("Paul")
    )
        .filter(...)
        .collect(distinctByKey(Person::getName)) // return a stream of Person with 2 elements, jean and Paul
        .map(...)
        .collect(toList())
    
  • 2

    如果可以,我建议使用Vavr . 使用此库,您可以执行以下操作:

    io.vavr.collection.List.ofAll(persons)
                           .distinctBy(Person::getName)
                           .toJavaSet() // or any another Java 8 Collection
    
  • 2
    Set<YourPropertyType> set = new HashSet<>();
    list
            .stream()
            .filter(it -> set.add(it.getYourProperty()))
            .forEach(it -> ...);
    
  • 1

    你可以写的最简单的代码:

    persons.stream().map(x-> x.getName()).distinct().collect(Collectors.toList());
    
  • 0

    Saeed Zarinfam使用的类似方法,但更多Java 8风格:)

    persons.collect(groupingBy(p -> p.getName())).values().stream()
     .map(plans -> plans.stream().findFirst().get())
     .collect(toList());
    
  • -2

    另一个支持它的库是jOOλ,它的Seq.distinct(Function<T,U>)方法:

    Seq.seq(persons).distinct(Person::getName).toList();
    

    Under the hood,它与accepted answer几乎完全相同 .

  • -2

    实现此目的的最简单方法是跳转排序功能,因为它已经提供了可选的 Comparator ,可以使用元素的属性创建 . 然后你必须过滤掉可以使用statefull Predicate 完成的重复项,它使用的事实是,对于排序的流,所有相等的元素都是相邻的:

    Comparator<Person> c=Comparator.comparing(Person::getName);
    stream.sorted(c).filter(new Predicate<Person>() {
        Person previous;
        public boolean test(Person p) {
          if(previous!=null && c.compare(previous, p)==0)
            return false;
          previous=p;
          return true;
        }
    })./* more stream operations here */;
    

    当然,statefull Predicate 不是线程安全的,但是如果你需要的话,你可以将这个逻辑移到 Collector 中,让流在使用你的 Collector 时处理线程安全 . 这取决于你想要对你在问题中没有告诉我们的不同元素流做什么 .

相关问题