While I was doing a little programming exercise i stumbled upon a ClassCastException
. As background i'm giving a simplified version of the exercise to demonstrate the problem:
Given a string which contains only the characters
A
orB
compute a map with the characters as keys and the number of occurrences as values. Additionally the map should always contain both characters as key (with value zero if a character is missing 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 it was possible to supply a map with default values to 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 thrown 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 anyone explain why this is happening?
There is type inference magic involved, but if you want solution here it is:
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 of using this solution in practice. Implementation details can be changed in any update and this hack stop works.
Here more explanation about "why" from groupingBy
javadoc
mapFactory - a function which, when called, produces a new empty Map of the desired type
mapFactory
is second parameter. Word "empty" is very important. Implementation use created map to store arrays of long
while iterating and then convert them to long
. It works because of massive amount of casting inside.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With