A simple example of using Collectors with java 8

Imagine I have a file like:

1, Apple
1, Pear
1, Orange
2, Apple
3, Melon
3, Orange

      

I want to parse this in a list with each entry being a map, or I think it might be my own object, but I thought the map would be the best as that is the key. value.

I have tried:

private List<Map<String, String>> readRecords(String path) {
    return Files.lines(Paths.get(path))
            .map(line -> Arrays.asList(line.split(SEPARATOR)))
            .map(snippets -> new HashMap<Integer, String>().put(Integer.parseInt(snippets.get(0)), snippets.get(1)))
            .collect(Collectors.toList());
}  

      

But it gives me an error about not being able to convert between List<String>

andList<Map<String, String>>

Or maybe there is a better way to do this?

+3


source to share


5 answers


Except that the return type is not correct ( List<Map<Integer, String>>

), the method put

will give you the previous value displayed, if any.

So,

.map(snippets -> new HashMap<Integer, String>().put(Integer.parseInt(snippets.get(0)), snippets.get(1)))

      

is actually a mapping List<String> -> String

, not List<String> -> Map<Integer, String>

.



If you want to stick with the official API, I would return a List<SimpleEntry<Integer, String>>

, since each line represents a key-value pair and changes the call map

to:

.map(snippets -> new SimpleEntry<>(Integer.parseInt(snippets.get(0)), snippets.get(1)))

      

... or collect all content in Map<Integer, List<String>>

(look at the collector groupingBy

).

+5


source


The main problem is this line:

snippets -> new HashMap<Integer, String>().put(Integer.parseInt(snippets.get(0)), snippets.get(1))

      

The return type of this lambda String

, because it Map.put(key,value)

does not return the map itself, but value

.



Also, I'm not entirely sure that using a full fledged volatile hashmap to store one key-value pair is warranted. I would probably collect pairs of values ​​into one card, not a list of one-shot cards.


Another issue Eran noticed is that your method should return List<Map<Integer,String>>

, not List<Map<String,String>>

.

+2


source


You should return the generated map from the second map

lambda:

private List<Map<Integer, String>> readRecords(String path) {
    return Files.lines(Paths.get(path))
            .map(line -> Arrays.asList(line.split(SEPARATOR)))
            .map(snippets -> {
                Map<Integer, String> map = new HashMap<Integer, String>();
                map.put(Integer.parseInt(snippets.get(0)), snippets.get(1));
                return map; 
            })
            .collect(Collectors.toList());
}  

      

Since your maps only contain one mapping, it is better to use singletonMap

though:

private List<Map<Integer, String>> readRecords(String path) throws IOException {
    return Files.lines(Paths.get(path))
        .map(line -> Arrays.asList(line.split(SEPARATOR)))
        .map(snippets -> Collections.singletonMap(Integer.parseInt(snippets.get(0)), snippets.get(1)))
        .collect(Collectors.toList());
}  

      

+2


source


I am guessing that using the map is overkill here. You can take a look at https://docs.oracle.com/javase/8/javafx/api/javafx/util/Pair.html

+1


source


Adding to the comment I made on the original post, this is the code I came up with. Obviously, this assumes that you want to have a one-to-many relationship, with an integer mapped to List<String>

.

public class MappingDemo {

    public static void main(String[] args) {
        MappingDemo demo = new MappingDemo();
        System.out.println("... Using custom collector ...");
        demo.dumpMap(demo.getFruitMappingsWithCustomCollector());
        System.out.println("... Using 'External' map ...");
        demo.dumpMap(demo.getFruitMappingsWithExternalMap());
    }

    public Map<Integer, List<String>> getFruitMappingsWithCustomCollector(){
        // Resulting map is created from within the lambda expression.
        return getContent().stream().map(s -> s.split(",\\s"))
                .collect(
                      HashMap::new,
                      (map, ary) -> map.computeIfAbsent(Integer.parseInt(ary[0]),
                            k -> new ArrayList<>()).add(ary[1]),
                      (map1, map2) -> map1.entrySet().addAll(map2.entrySet())
                );
    }

    public Map<Integer,List<String>> getFruitMappingsWithExternalMap(){
        // Create the map external from the lambda and add to it.
        final Map<Integer,List<String>> fruitMappings = new HashMap<>();
        getContent().stream().map(s -> s.split(",\\s"))
              .forEach(ary ->
                    fruitMappings.computeIfAbsent(Integer.parseInt(ary[0]),
                          k -> new ArrayList<>()).add(ary[1]));
        return fruitMappings;
    }

    public void dumpMap(Map<Integer,List<String>> map){
        map.entrySet().forEach(e -> System.out.println(e.getKey() + " -> " + e.getValue()));
    }

    public List<String> getContent(){
        return Arrays.asList("1, Apple",
              "1, Pear",
              "1, Orange",
              "2, Apple",
              "3, Melon",
              "3, Orange",
              "1, Mango",
              "3, Star Fruit",
              "4, Pineapple",
              "2, Pomegranate");
    }
}

      

And the conclusion

... Using custom collector ...
1 -> [Apple, Pear, Orange, Mango]
2 -> [Apple, Pomegranate]
3 -> [Melon, Orange, Star Fruit]
4 -> [Pineapple]
... Using 'External' map ...
1 -> [Apple, Pear, Orange, Mango]
2 -> [Apple, Pomegranate]
3 -> [Melon, Orange, Star Fruit]
4 -> [Pineapple]

I'm sure someone can do better.

getContent

was just a simple means to get the values ​​using the text you gave. It would be easy to use Files.readAllLines

instead getContent()

if you are really reading File

.

0


source







All Articles