在这一页我们将提供Java 8 Stream distinct() 的例子。distinct() 返回由该流中不同元素组成的流。distinct() 是流接口的方法。distinct() 使用 hashCode()equals() 方法获取不同的元素。因此,我们的类必须实现 hashCode()equals() 方法。如果 distinct() 在有序的流上工作,那么对于重复的元素,在相遇顺序中首先出现的元素将被保留,这样对 distinct 元素的选择是稳定的。在无序流的情况下,不同元素的选择不一定是稳定的,并且可以改变。distinct() 执行有状态的中间操作。

对于有序流的并行管道,保持 distinct() 的稳定性是昂贵的,因为它需要大量的缓冲开销。如果遇到顺序的一致性不是必须的,那么我们应该使用可以通过使用 BaseStream.unordered() 方法实现的无序流。

现在我们将提供 Stream distinct() 方法的示例。

1、Stream.distinct() 操作简单对象

我们首先来看一下 distinct() 方法在 Stream 里面的声明。

Stream<T> distinct();

它是 Stream 接口里面定义的一个方法。下面我们来对它的使用进行举例。这下面这个例子当中我们有一个包含重复元素的数组类型的字符串数据类型。

DistinctSimpleDemo.java

public class DistinctSimpleDemo {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("AA", "BB", "CC", "BB", "CC", "AA", "AA");
        Long l = list.stream().distinct().count();
        System.out.println("No. of distinct elements : " + l);
        String output = list.stream().distinct().collect(Collectors.joining(","));
        System.out.println(output);
    }

}

Output:

No. of distinct elements : 3
AA,BB,CC

2、Stream.distinct() 操作复杂对象列表

之前的例子是操作简单的数据对象字符串,下在我们来操作一下复杂一点的对象。我们先来定义一个 Book 对象,为了去除重复对象这个对象需要重写 hashCode()equals()

DistinctWithUserObjects.java

public class DistinctWithUserObjects {

    public static void main(String[] args) {
        List<Book> list = new ArrayList<>();
        {
            list.add(new Book("Core Java", 200));
            list.add(new Book("Core Java", 200));
            list.add(new Book("Learning Freemarker", 150));
            list.add(new Book("Spring MVC", 300));
            list.add(new Book("Spring MVC", 300));
        }
        Long l = list.stream().distinct().count();
        System.out.println("No. of distinct elements : " + l);
        list.stream().distinct().forEach(b -> System.out.println(b.getName() + ", " + b.getPrice()));
    }

}

Output:

No. of distinct elements : 3
Core Java, 200
Learning Freemarker, 150
Spring MVC, 300

3、自定义去重规则

distinct() 没有提供根据根据属性或键来过滤不同的元素。它以对象本身的 hashCode()equals() 来进行过滤工作。如果我们想通过属性或键来区分元素,我们可以通过自定义代码来实现。下面就是代码片段:

static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object,Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

上面的代码可以与Stream filter() 方法一起使用,如下所示。

list.stream().filter(distinctByKey(b -> b.getName()));

现在我们来看看 distinctByKey() 方法。此方法返回 Predicate 实例,该实例维护前面使用ConcurrentHashMap 所看到的状态。

下面是一个使用 distinctByKey()方法通过类属性获得流中不同元素的完整示例。

DistinctByProperty.java

public class DistinctByProperty {
    public static void main(String[] args) {
        List<Book> list = new ArrayList<>();
        {
            list.add(new Book("Core Java", 200));
            list.add(new Book("Core Java", 300));
            list.add(new Book("Learning Freemarker", 150));
            list.add(new Book("Spring MVC", 200));
            list.add(new Book("Hibernate", 300));
        }
        list.stream().filter(distinctByKey(b -> b.getName()))
                .forEach(b -> System.out.println(b.getName()+ "," + b.getPrice()));
    }
    
    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object,Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }
}

Output:

Core Java,200
Learning Freemarker,150
Spring MVC,200
Hibernate,300