import java.util.*;
import java.util.stream.*;

/* Name of the class has to be "Main" only if the class is public. */
public class Main {

	static List<Foo> joinedCategoriesMap(List<Foo> input) {
	    return input
	            .stream()
	            .collect(Collectors.groupingBy(Foo::getId))
	            .entrySet().stream()
	            .map(e -> new Foo(
	                    e.getKey(),
	                    e.getValue().get(0).name,
	                    e.getValue().stream().map(Foo::getCategory).collect(Collectors.toList()))
	            )
	            .collect(Collectors.toList());
	}
	
	static List<Foo> joinedCategoriesReduce(List<Foo> input) {
        return input
                .stream()
                .collect(Collectors.groupingBy(Foo::getId))  // Map<Integer, List<Foo>>
                .entrySet().stream()
                .map(e -> e.getValue().stream() // Stream<Foo>
                        .reduce(new Foo(e.getKey(), e.getValue().get(0).getName(), (String)null), Foo::merge)
                )
                .collect(Collectors.toList());
    }
    
	public static void main (String[] args) throws java.lang.Exception {
		final Foo f1 = new Foo(1L, "a", "c1");
		final Foo f2 = new Foo(1L, "a", "c2");
		final Foo f3 = new Foo(2L, "a", "c1");
		
		final List<Foo> li = List.of(f1, f2, f3);
		
		System.out.println("Map: ");
		joinedCategoriesMap(li).forEach(System.out::println);
		
		System.out.println("Reduce: ");
        joinedCategoriesReduce(li).forEach(System.out::println);
	}
	
    static class Foo {
        private Long id;
        private String name;
        private String category;
        private List<String> categories;

        Foo(Long id, String name, String cat) {
            this.id = id;
            this.name = name;
            this.category = cat;
        }

        Foo(Long id, String name, List<String> cats) {
            this.id = id;
            this.name = name;
            this.categories = cats;
        }

        static Foo merge(Foo accum, Foo other) {
            if (null == accum.categories) {
                accum.categories = new ArrayList<>();
                if (null != accum.category) {
                    accum.categories.add(accum.category);
                    accum.category = null;
                }
            }
            accum.categories.add(other.category);

            return accum;
        }

        public Long getId() { return id; }
        public void setId(Long id) { this.id = id; }

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

        public String getCategory() { return category; }

        @Override
        public String toString() {
            return String.format("Foo { id=%d, name=%s, category=%s, categories=%s }", id, name, category, categories);
        }
    }
}