ClassCastException when using a custom map provider when grouping

While I was doing some programming, I came across a ClassCastException . As a background, I give a simplified version of the exercise to demonstrate the problem:

For a string containing only characters A or B , output a map with characters in the form of keys and the number of occurrences as values. In addition, the card must always contain both characters as a key (with a value of 0 if the character is not in the input string).

Examples:

  • "A" => {A=1, B=0}
  • "AAB" => {A=2, B=1}

My first solution was the following:

 import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; public Map<Character, Long> createResult(String input) { Map<Character, Long> map = input.chars() .mapToObj(c -> (char) c) .collect(groupingBy(c -> c, counting())); map.putIfAbsent('A', 0L); map.putIfAbsent('B', 0L); return map; } 

This solution worked, but I wanted to try if I could provide a map with the default values โ€‹โ€‹of the groupingBy function:

 public HashMap<Character, Long> createResult2(String input) { return input.chars() .mapToObj(c -> (char) c) .collect(groupingBy(c -> c, this::mapFactory, counting())); } private HashMap<Character, Long> mapFactory() { HashMap<Character, Long> map = new HashMap<>(); map.put('A', 0L); map.put('B', 0L); return map; } 

When calling createResult2 with input A a ClassCastException is ClassCastException at runtime:

  java.lang.ClassCastException: java.lang.Long cannot be cast to [Ljava.lang.Object;
     at java.util.stream.Collectors.lambda $ groupingBy $ 45 (Collectors.java:909)
     at java.util.stream.ReduceOps $ 3ReducingSink.accept (ReduceOps.java:169)
     at java.util.stream.IntPipeline $ 4 $ 1.accept (IntPipeline.java:250)
     at java.lang.CharSequence $ 1CharIterator.forEachRemaining (CharSequence.java:149)
     at java.util.Spliterators $ IntIteratorSpliterator.forEachRemaining (Spliterators.java:1908)
     at java.util.Spliterator $ OfInt.forEachRemaining (Spliterator.java:693)
     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)

Can someone explain why this is happening?

+7
java java-8 java-stream
source share
2 answers

There is type input magic, but if you want to find a solution here:

Replace

 map.put('A', 0L); map.put('B', 0L); 

by

 map.put('A', new Object[]{0L}); map.put('B', new Object[]{0L}); 

But I strongly discourage you from using this solution in practice . Implementation information can be changed in any update, and this blocking operation works.

Here are more explanations about the โ€œwhyโ€ of groupingBy javadoc

mapFactory - a function that, when called, creates a new empty map of the desired type

mapFactory is the second parameter. The word "empty" is very important. The implementation uses the generated map to store long arrays during iteration, and then converts them to long . It works due to the large amount of casting inside.

+2
source share

Why not a simple loop?

 private static Map<Character, Integer> count(String input) { Map<Character, Integer> result = new HashMap<>(); result.put('A', 0); result.put('B', 0); for (Character c : input.toCharArray()) { result.put(c, result.get(c) + 1); } return result; } 
0
source share

All Articles