First, note that for most scenarios, the equal(Blog, Blog) method equal(Blog, Blog) not enough, because you need to match all posts that are inefficient. It is better to define a function that extracts a new key from a blog entry. For example, consider the following Blog class:
static class Blog { final String name; final int id; final long time; public Blog(String name, int id, long time) { this.name = name; this.id = id; this.time = time; } @Override public int hashCode() { return Objects.hash(name, id, time); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Blog other = (Blog) obj; return id == other.id && time == other.time && Objects.equals(name, other.name); } public String toString() { return name+":"+id+":"+time; } }
Let there be test data:
List<Blog> blogs = Arrays.asList(new Blog("foo", 1, 1234), new Blog("bar", 2, 1345), new Blog("foo", 1, 1345), new Blog("bar", 2, 1345)); List<Blog> distinctBlogs = blogs.stream().distinct().collect(Collectors.toList()); System.out.println(distinctBlogs);
Here distinctBlogs contains three entries: [foo:1:1234, bar:2:1345, foo:1:1345] . Suppose this is undesirable because we do not want to compare the time field. The easiest way to create a new key is to use Arrays.asList :
Function<Blog, Object> keyExtractor = b -> Arrays.asList(b.name, b.id);
The resulting keys already have the correct equals and hashCode .
Now, if you are fine with the terminal, you can create a custom collector as follows:
List<Blog> distinctByNameId = blogs.stream().collect( Collectors.collectingAndThen(Collectors.toMap( keyExtractor, Function.identity(), (a, b) -> a, LinkedHashMap::new), map -> new ArrayList<>(map.values()))); System.out.println(distinctByNameId);
Here we use keyExtractor to generate the keys, and the merge function is (a, b) -> a , which means choosing a previously added record when a duplicate key appears. We use LinkedHashMap to maintain order (omit this option if you don't need order). Finally, we upload the map values to the new ArrayList . You can transfer such a collector creation to a separate method and generalize it:
public static <T> Collector<T, ?, List<T>> distinctBy( Function<? super T, ?> keyExtractor) { return Collectors.collectingAndThen( Collectors.toMap(keyExtractor, Function.identity(), (a, b) -> a, LinkedHashMap::new), map -> new ArrayList<>(map.values())); }
Thus, the use will be easier:
List<Blog> distinctByNameId = blogs.stream() .collect(distinctBy(b -> Arrays.asList(b.name, b.id)));