More efficient solution on coding task using Stream API?

Here is another one.

// using SimpleEntry from java.util.AbstractMap
Set<Student> list = new HashSet<>(studentStream
    .flatMap(student -> student.getSubjects().stream()
        .map(subject -> new SimpleEntry<>(subject, student)))
    .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (l, r) -> Student.SENTINEL_VALUE)
    .values());
list.remove(Student.SENTINEL_VALUE);

(Intentionally using a sentinel value, more about that below.)

The steps:

  1. Set<Student> list = new HashSet<>(studentStream
    

    We're creating a HashSet from the Collection we're going to collect. That's because we want to get rid of the duplicate students (students with multiple unique subjects, in your case Antony).

  2. .flatMap(student -> student.subjects()
        .map(subject -> new SimpleEntry(subject, student)))
    

    We are flatmapping each student's subjects into a stream, but first we map each element to a pair with as key the subject and as value the student. This is because we need to retain the association between the subject and the student. I'm using AbstractMap.SimpleEntry, but of course, you can use any implementation of a pair.

  3. .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (l, r) -> Student.SENTINEL_VALUE)
    

    We are collecting the values into a map, setting the subject as key and the student as value for the resulting map. We pass in a third argument (a BinaryOperator) to define what should happen if a key collision takes place. We cannot pass in null, so we use a sentinel value1.
    At this point, we have inverted the relation student ↔ subject by mapping each subject to a student (or the SENTINEL_VALUE if a subject has multiple students).

  4. .values());
    

    We take the values of the map, yielding the list of all students with a unique subject, plus the sentinel value.

  5. list.remove(Student.SENTINEL_VALUE);
    

    The only thing left to do is getting rid of the sentinel value.


1 We cannot use null in this situation. Most implementations of a Map make no distinction between a key mapped to null or the absence of that particular key. Or, more accurately, the merge method of HashMap actively removes a node when the remapping function returns null. If we want to avoid a sentinel value, then we must implement or own merge method, which could be implemented like something like this: return (!containsKey(key) ? super.merge(key, value, remappingFunction) : put(key, null));.


Another solution. Looks kind of similar to Eugene.

Stream.of(stud1, stud2, stud3, stud4)
    .flatMap( s -> s.getSubjects().stream().map( subj -> new AbstractMap.SimpleEntry<>( subj, s ) ) )
    .collect( Collectors.groupingBy(Map.Entry::getKey) )

    .entrySet().stream()
    .filter( e -> e.getValue().size() == 1 )
    .map( e -> e.getValue().get(0).getValue().getName() )
    .collect( Collectors.toSet() );