2

I have a stream of my homegrown objects StudentInGroup (see below) and I'd like to place it into the map with 3 almost identical ways. For some reason 2 of them have succeed while the last has raised the exception:

Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractCollection.add(AbstractCollection.java:262)
at java.util.AbstractCollection.addAll(AbstractCollection.java:344)
at containers.streams.StreamsTest3.lambda$main$13(StreamsTest3.java:93)
at java.util.HashMap.merge(HashMap.java:1245)
at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at containers.streams.StreamsTest3.main(StreamsTest3.java:91)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

so the object is:

import java.util.Optional;

public class StudentInGroup {

    private Optional<String> groupNumber;
    private String name;
    private boolean isGirl;
    private int age;

    @Override
    public String toString() {
        return "StudentInGroup{" +
                "groupNumber=" + groupNumber +
                ", name='" + name + '\'' +
                ", isGirl=" + isGirl +
                ", age=" + age +
                '}';
    }

    public StudentInGroup(String name, String groupNumber, int age, boolean isGirl)
    {
        this.name=name;
        this.groupNumber=Optional.ofNullable(groupNumber);
        this.age=age;
        this.isGirl=isGirl;
    }

    public Optional<String> getGroupNumber() {
        return groupNumber;
    }

    public void setGroupNumber(Optional<String> groupNumber) {
        this.groupNumber = groupNumber;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isGirl() {
        return isGirl;
    }

    public void setGirl(boolean girl) {
        isGirl = girl;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

the creation of stream if like that:

    Optional<StudentInGroup> petrov = Optional.of(new StudentInGroup("Petrov", null, 18, false));
    Optional<StudentInGroup> petrova = Optional.of(new StudentInGroup("Petrova", "11-305", 18, true));

    List<Optional<StudentInGroup>> studentInGroupList = new ArrayList<>();
    studentInGroupList.add(Optional.of(new StudentInGroup("Ivanov", "11-307", 18, false)));
    studentInGroupList.add(Optional.of(new StudentInGroup("Johns", "11-307", 19, false)));
    studentInGroupList.add(Optional.of(new StudentInGroup("Walitov", "11-307", 17, false)));
    studentInGroupList.add(Optional.of(new StudentInGroup("Kowalski", "11-302", 19, false)));
    studentInGroupList.add(Optional.of(new StudentInGroup("Glauberová", "11-302", 19, true)));
    studentInGroupList.add(Optional.of(new StudentInGroup("Dybenko", "11-307", 17, true)));
    studentInGroupList.add(Optional.of(new StudentInGroup("Ishikawa", "11-305", 18, false)));
    studentInGroupList.add(Optional.of(new StudentInGroup("Nguyen", "11-305", 18, false)));
    studentInGroupList.add(Optional.of(new StudentInGroup("Chui", "11-305", 18, true)));
    studentInGroupList.add(petrov);
    studentInGroupList.add(Optional.empty());
    studentInGroupList.add(petrova);
Supplier<Stream<StudentInGroup>> studentInGroupStreamSupplier = () -> studentInGroupList.stream().map((o) -> o.orElse(new StudentInGroup(null, null, -1, false)));

and two working methods are

  Map<Integer, Set<StudentInGroup>> stringStudentInGroupMap3 = studentInGroupStreamSupplier.<StudentInGroup>get().collect(Collectors.toMap(StudentInGroup::getAge, Collections::<StudentInGroup>singleton,
            (Set<StudentInGroup> a, Set<StudentInGroup> b) -> {
                Set<StudentInGroup> studentInGroupSet = new HashSet<>();
                studentInGroupSet.addAll(a);
                studentInGroupSet.addAll(b);
                return studentInGroupSet;
            }));
    System.out.println(stringStudentInGroupMap3);


    Map<Integer, Set<StudentInGroup>> stringStudentInGroupMap4 = studentInGroupStreamSupplier.<StudentInGroup>get().collect(Collectors.toMap(StudentInGroup::getAge, x -> new HashSet<>(Arrays.asList(x)),
            (x, y) -> {
                x.addAll(y);
                return x;
            }));
    System.out.println(stringStudentInGroupMap4);

with output value being

{-1=[StudentInGroup{groupNumber=Optional.empty, name='null', isGirl=false, age=-1}], 17=[StudentInGroup{groupNumber=Optional[11-307], name='Dybenko', isGirl=true, age=17}, StudentInGroup{groupNumber=Optional[11-307], name='Walitov', isGirl=false, age=17}], 18=[StudentInGroup{groupNumber=Optional[11-307], name='Ivanov', isGirl=false, age=18}, StudentInGroup{groupNumber=Optional.empty, name='Petrov', isGirl=false, age=18}, StudentInGroup{groupNumber=Optional[11-305], name='Petrova', isGirl=true, age=18}, StudentInGroup{groupNumber=Optional[11-305], name='Nguyen', isGirl=false, age=18}, StudentInGroup{groupNumber=Optional[11-305], name='Ishikawa', isGirl=false, age=18}, StudentInGroup{groupNumber=Optional[11-305], name='Chui', isGirl=true, age=18}], 19=[StudentInGroup{groupNumber=Optional[11-302], name='Kowalski', isGirl=false, age=19}, StudentInGroup{groupNumber=Optional[11-302], name='Glauberová', isGirl=true, age=19}, StudentInGroup{groupNumber=Optional[11-307], name='Johns', isGirl=false, age=19}]}

{-1=[StudentInGroup{groupNumber=Optional.empty, name='null', isGirl=false, age=-1}], 17=[StudentInGroup{groupNumber=Optional[11-307], name='Dybenko', isGirl=true, age=17}, StudentInGroup{groupNumber=Optional[11-307], name='Walitov', isGirl=false, age=17}], 18=[StudentInGroup{groupNumber=Optional[11-307], name='Ivanov', isGirl=false, age=18}, StudentInGroup{groupNumber=Optional.empty, name='Petrov', isGirl=false, age=18}, StudentInGroup{groupNumber=Optional[11-305], name='Petrova', isGirl=true, age=18}, StudentInGroup{groupNumber=Optional[11-305], name='Nguyen', isGirl=false, age=18}, StudentInGroup{groupNumber=Optional[11-305], name='Ishikawa', isGirl=false, age=18}, StudentInGroup{groupNumber=Optional[11-305], name='Chui', isGirl=true, age=18}], 19=[StudentInGroup{groupNumber=Optional[11-302], name='Kowalski', isGirl=false, age=19}, StudentInGroup{groupNumber=Optional[11-302], name='Glauberová', isGirl=true, age=19}, StudentInGroup{groupNumber=Optional[11-307], name='Johns', isGirl=false, age=19}]}

while this call cause the error above:

Map<Integer, Set<StudentInGroup>> stringStudentInGroupMap6 = studentInGroupStreamSupplier.<StudentInGroup>get().collect(Collectors.toMap(StudentInGroup::getAge, Collections::<StudentInGroup>singleton,
            (x, y) -> {
                x.addAll(y);
                return x;
            }));
    System.out.println(stringStudentInGroupMap6);

so, what is the reason on the UnsupportedOperationException thrown in this particular case?

PS. I know about the groupingBy is the best way to do it, the question is why the exception is thrown.

GBlodgett
  • 12,704
  • 4
  • 31
  • 45
Eljah
  • 4,188
  • 4
  • 41
  • 85
  • 1
    `Collections::singleton` will create a singleton which doesn't allow you to add new elements. – Thomas Mar 12 '19 at 13:45
  • 1
    Not a super perfect match, but it boils down to: the Collections methods starting with `singleton` are meant to create **unmodifiable** collections. All of them do NOT support adding or removing objects later on. – GhostCat Mar 12 '19 at 13:49
  • @GhostCat it wasn't so obvious to separate the undestanding on singletone set and what actually the merge function does with it – Eljah Mar 12 '19 at 13:53

1 Answers1

5

The Collections.singleton*() methods return immutable Collection implementations.

So you have to replace:

Collections::<StudentInGroup>singleton

With:

HashSet::new

Or the appropriate Set implementation that fits your needs.

Lino
  • 19,604
  • 6
  • 47
  • 65
  • 1
    so no.1 worked only because i have recreated the collection inside the merge part? – Eljah Mar 12 '19 at 13:48
  • 1
    @IlyaYevlampiev Yes that's correct – Lino Mar 12 '19 at 13:49
  • 2
    In particular, the `Collections.singleton()` method returns a `SingletonSet`, which does not override the default implementation of the `add` method provided by `AbstractCollection` (through `AbstractSet`), which throws `UnsupportedOperationException`. – Justin Albano Mar 12 '19 at 13:50