Q:

Filtering collection to get items whose properties occured n times in Java 8 using streams

Assume that I have a Collection of Person(name, age) objects:
Set<Person> people = new HashSet<Person>();
people.add(new Person("Peter", 26));
people.add(new Person("Dane", 24));
people.add(new Person("Gregory", 26));
people.add(new Person("Daniel", 27));
people.add(new Person("Micheal", 27));
people.add(new Person("David", 26));
people.add(new Person("Kate", 21));

people.forEach(i -> System.out.println(i.getName()));

now I would like to check if there are three guys who have the same age or, if there are pairs of people with the same name, and get these people in another list. So I wrote following generic static method:
private static <T> Collection<T> filterWhenMultipleOccurance(Collection<T> collection,
        Function<T, ?> comparisonCriteria, int demandedOccuranceAmount) {
    return collection.stream()
            .filter(item -> collection.stream()
                    .filter(otherItem -> 
                    comparisonCriteria.apply(otherItem) == comparisonCriteria.apply(item))
            .toArray().length == demandedOccuranceAmount)
            .collect(Collectors.toList());
}

Now I can call this method to meet my demands:
Collection<Person> selectedPeople = filterWhenMultipleOccurance(people, p -> p.getAge(), 3);
System.out.println("---------------");
selectedPeople.forEach(p -> System.out.println(p.getName()));

Which gets me proper list consisting of Peter, Gregory and David.
Somehow I feel this solution is neither elegant nor efficient, since I'm nesting stream().filter call, so I assume that time complexity would be at least O(n^2). Is there a better solution to this problem?

A:

I think you'd better of using the groupingBy collector, classifying your elements by the function you give as parameter. This will give you a Map<Object, List<T>>. 
From there get the stream from the entry set again. Since you're not interested in the keys, you map each entry to its value (a List<T>) and filter the lists which have an appropriate size. Finally, you collect the elements into a list again (after flatmapping the lists), resulting in a List<T>.
private static <T> Collection<T> filterWhenMultipleOccurance(Collection<T> collection,
                                                             Function<T, ?> comparisonCriteria, 
                                                             int demandedOccuranceAmount) {
    return collection.stream()
                     .collect(groupingBy(comparisonCriteria))
                     .values()
                     .stream()
                     .filter(l -> l.size() >= demandedOccuranceAmount)
                     .flatMap(List::stream)
                     .collect(toList());
}

In your original solution for each item in the stream, you're filtering all elements of the collections again. So basically you're doing unnecessary operations, because once you filtered an item that satisfies the requirement, you know that every item with the same property given the comparison function will yield the same result.
Also you're using == in comparisonCriteria.apply(otherItem) == comparisonCriteria.apply(item) which might not work since you're comparing references (I guess this is not what you want). 
Finally you are wasting memory because you create an array just for calling .length. You can use count() directly in this case. 

