Search code examples
javajava-stream

Java Collection Utils using Java Stream API


I am trying to write my own CollectionUtils helper class that other application will use. Here is my first method

    
    public static <T, K, V> 
    Map<K, List<V>> listToMap(List<T> inputList, 
            Function<? super T, ? extends K> keyMapper, 
            Function<? super T, ? extends V> valueMapper)
    {
        Collector c = Collectors.toMap(keyMapper, valueMapper);
        return inputList.stream()
                .collect(c);
        
        
    }
    
    public void test()
    {
        // trying to get a Map<String, List<String>> showing student's name and their activities.
        listToMap(StudentDatabase.getAllStudents(), Student::getName, Student::getActivites);
    }

However, I am getting lots of compilation error that I do not understand how to solve. Can I get some help here? Is there any third party library that already does that (but it has to be using java stream api) I can use so that I do not need to write my own?

I tried above and having compilation issue.


Solution

  • There's a couple of problems with the current code:

    1. The Collector interface is generic. You should parameterize it like you're doing for all the other generic types in the code. See What is a raw type and why shouldn't we use it? for more information.

    2. You've defined the return type as Map<K, List<V>>. That would seem to indicate you're trying to implement a grouping operation. However, there are three other parts of your code indicating otherwise:

      • You use Collectors#toMap(Function,Function)

      • Your valueMapper maps to V, not List<V>

      • You call listToMap with Student::getActivities as an argument for the valueMapper, and I can only assume that method returns a list of activities (or some other collection).

      So, given all that, you should change the return type to Map<K, V>. That gives the caller full control over the value type of the map, rather than forcing them to use a list. But if you are trying to implement a grouping operation, and you always want the value type to be a List<V>, then consider using Collectors#groupingBy(Function,Collector) instead.

    Fixing those two things will give you something like:

    public static <T, K, V> Map<K, V> listToMap(
            List<T> list,
            Function<? super T, ? extends K> keyMapper,
            Function<? super T, ? extends V> valueMapper) {
        Collector<T, ?, Map<K, V>> collector = Collectors.toMap(keyMapper, valueMapper);
        return list.stream().collect(collector);
    }
    

    And here's a minimal example using the above:

    import java.util.List;
    import java.util.Map;
    import java.util.function.Function;
    import java.util.stream.Collector;
    import java.util.stream.Collectors;
    
    public class Main {
    
        public record Student(String name, List<String> activities) {}
    
        public static <T, K, V> Map<K, V> listToMap(
                List<T> list, 
                Function<? super T, ? extends K> keyMapper, 
                Function<? super T, ? extends V> valueMapper) {
            Collector<T, ?, Map<K, V>> collector = Collectors.toMap(keyMapper, valueMapper);
            return list.stream().collect(collector);
        }
    
        public static void main(String[] args) {
            List<Student> students = List.of(
                new Student("John", List.of("Piano", "Running")),
                new Student("Jane", List.of("Soccer", "Video Games")),
                new Student("Bob", List.of("Snowboarding"))
            );
            Map<String, List<String>> map = listToMap(students, Student::name, Student::activities);
            System.out.println(map);
        }
    }
    

    Output:

    {Bob=[Snowboarding], John=[Piano, Running], Jane=[Soccer, Video Games]}