I do have a simialar problem like descripted here. But with two differences first I do use the stream api and second I do have an equals()
and hashCode()
method already. But within the stream the equalitity of the of Blogs are in this context not the same as defined in the Blog
class.
Collection<Blog> elements = x.stream()
... // a lot of filter and map stuff
.peek(p -> sysout(p)) // a stream of Blog
.? // how to remove duplicates - .distinct() doesn't work
I do have a class with an equal Method lets call it ContextBlogEqual
with the method
public boolean equal(Blog a, Blog b);
Is there any way removing all duplicate entries with my current stream approach based on the ContextBlogEqual#equal
method?
I thought already on grouping, but this doesn't work either, because the reason why blogA
and blogB
is equal isn't just one parameter. Also I have no idea how I could use .reduce(..), because there is useally more than one element left.
You can use the Stream. distinct() method to remove duplicates from a Stream in Java 8 and beyond. The distinct() method behaves like a distinct clause of SQL, which eliminates duplicate rows from the result set.
We can remove duplicate element in an array by 2 ways: using temporary array or using separate index. To remove the duplicate element from array, the array must be in sorted order. If array is not sorted, you can sort it by calling Arrays. sort(arr) method.
If you don't want duplicates, use a Set instead of a List . To convert a List to a Set you can use the following code: // list is some List of Strings Set<String> s = new HashSet<String>(list); If really necessary you can use the same construction to convert a Set back into a List .
In essence, you either have to define hashCode
to make your data work with a hashtable, or a total order to make it work with a binary search tree.
For hashtables you'll need to declare a wrapper class which will override equals
and hashCode
.
For binary trees you can define a Comparator<Blog>
which respects your equality definition and adds an arbitrary, but consistent, ordering criterion. Then you can collect into a new TreeSet<Blog>(yourComparator)
.
First, please note that equal(Blog, Blog)
method is not enough for the most scenarios as you will need to pairwise compare all the entries which is not efficient. It's better to define the function which extracts new key from the blog entry. For example, let's 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's have some 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 that it's undesired, because we don't want to compare the time
field. The simplest way to create new key is to use Arrays.asList
:
Function<Blog, Object> keyExtractor = b -> Arrays.asList(b.name, b.id);
The resulting keys already have proper equals
and hashCode
implementations.
Now if you fine with terminal operation, you may create a custom collector like this:
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 merge function is (a, b) -> a
which means select the previously added entry when repeating key appears. We use LinkedHashMap
to preserve the order (omit this parameter if you don't care about order). Finally we dump the map values into the new ArrayList
. You can move such collector creation to the 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()));
}
This way the usage will be simpler:
List<Blog> distinctByNameId = blogs.stream()
.collect(distinctBy(b -> Arrays.asList(b.name, b.id)));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With