Your scenario sounds like a good use case for the groupingBy collector. Normally, instead of supplying an equality function, you supply a function that extracts a qualifier. The elements are then mapped to these qualifiers in lists.
i.e.
Map<Qualifier, List<T>> map = list.stream()
.collect(Collectors.groupingBy(T::getQualifier));
Collection<List<T>> result = map.values();
In the case the identity of T is your qualifier, you could use Function.identity() as an argument.
But this becomes a problem when your qualifier is more than 1 field of T. You could use a tuple type, to create an alternate identity for T but this only goes so far, as there needs to be a separate tuple class for each number of fields.
If you want to use groupingBy you really need to create a temperate alternate identity for T, so you don't have to change T's equals and hashCode methods.
To create a proper identity, you need to implement equals and hashCode (or always return 0 for a hash code, with performance downsides). There is no API class for this, that I know of, but I have made a simple implementation:
interface AlternateIdentity<T> {
public static <T> Function<T, AlternateIdentity<T>> mapper(
BiPredicate<? super T, Object> equality, ToIntFunction<? super T> hasher) {
return t -> new AlternateIdentity<T>() {
@Override
public boolean equals(Object other) {
return equality.test(t, other);
}
@Override
public int hashCode() {
return hasher.applyAsInt(t);
}
};
}
}
Which you could use like:
Collection<List<T>> result
= list.stream()
.collect(Collectors.groupingBy(
AlternateIdentity.mapper(eqF, hashF)
))
.values();
Where eqF is your function, and hashF is a hash code function that hashes the same fields as eqF tests. (Again, you could also just return 0 in hashF, but having a proper implementation would speed things up.)