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?
source to share
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
).
source to share
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>>
.
source to share
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());
}
source to share
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
source to share
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
.
source to share