Reputation: 1097
I have a custom Collector
of the sort
public class ClusteringCollector extends java.util.stream.Collector<MyModel, Map<String, ClusterModel>, SortedSet<Map.Entry<Integer, ClusterModel>>> {
@Override
public Supplier<Map<String, MyOtherModel>> supplier() {
return HashMap::new;
}
@Override
public BiConsumer<Map<String, ClusterModel>, MyModel> accumulator() {
return (l, r) -> {
String mapKey = r.getURI();
if(l.containsKey(mapKey)) {
l.get(mapKey).addCluster(r.getCluster());
} else {
l.put(mapKey, r.getCluster());
}
}
}
@Override
public BinaryOperator<Map<String, ClusterModel>> combiner() {
return (left, right) -> {
for(Map.Entry<String, ClusterModel> e : right.entrySet()) {
e.getValue().setClusterCount(1);
if(left.containsKey(e.getKey())) {
left.get(e.getKey()).merge(e.getValue());
} else {
left.put(e.getKey(), e.getValue());
}
}
return left;
};
}
@Override
public Function<Map<String, ClusterModel>, SortedSet<Map.Entry<Integer, ClusterModel>>> finisher() {
return (accumulated) -> {
SortedSet<Map.Entry<Integer, ClusterModel>> finished = new TreeSet<>((mine, theirs) -> {
Double t1 = mine.getValue().getClusterCount() * mine.getValue().getClusterWeight();
Double t2 = theirs.getValue().getClusterCount() * theirs.getValue().getClusterWeight();
return t2.compareTo(t1);
});
Map<Integer, ClusterModel> tempMap = new LinkedHashMap<>();
for(Map.Entry<String, ClusterModel> e : accumulated.entrySet()) {
if(tempMap.containsKey(e.getValue().hashCode())) {
tempMap.get(e.getValue().hashCode()).merge(e.getValue());
} else {
tempMap.put(e.getValue().hashCode(), e.getValue());
}
}
finished.addAll(tempMap.entrySet());
return finished;
};
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.UNORDERED, Characteristics.IDENTITY_FINISH);
}
}
I use the collector in the following way
try (Stream<MyModel> resultStream = generateDataStream()) {
SortedSet<Map.Entry<Integer, ClusterModel>> clusters = resultStream.collect(new ClusteringCollector()); // This line throws a ClassCastException
}
The problem though is that I keep getting a ClassCastException
when I try to run the collect
method above. Here is the stacktrace
java.lang.ClassCastException: java.util.HashMap cannot be cast to java.util.SortedSet
com.mycomp.abc.core.services.DefaultClusteringServiceImpl.findClusters(DefaultClusteringServiceImpl.java:78)
com.mycomp.abc.core.webservices.ClusteringWebService.getClustersFromQuery(ClusteringWebService.java:67)
com.mycomp.abc.core.webservices.ClusteringWebService$Proxy$_$$_WeldClientProxy.getClustersFromQuery(Unknown Source)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:137)
org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:296)
org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:250)
org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:237)
org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:356)
org.jboss.resteasy.core.SynchronousDispatcher.invokePropagateNotFound(SynchronousDispatcher.java:217)
org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:224)
org.jboss.resteasy.plugins.server.servlet.FilterDispatcher.doFilter(FilterDispatcher.java:62)
Can someone tell me why this is happening? I am not getting any compilation errors though and the finisher
transforms the Map
into a SortedSet
correctly.
Upvotes: 1
Views: 1400
Reputation: 298153
There are several errors in the posted code which suggest that this is not the actual code, however, the main problem is recognizable. You specified the IDENTITY_FINISH
characteristic, despite you have a complex conversion in the finisher.
The IDENTITY_FINISH
characteristic implies that the finisher was just like Function.identity()
, but at runtime, the Stream implementation can’t check whether the generic signatures are compatible with that declaration. When it uses this characteristic to decide to skip the finisher, it will just return the container object, which is a HashMap
in your case, which, of course, is not assignable to SortedSet
.
In the end, that’s the better outcome of this mistake. The worse would be if the container and result type are compatible and the skipping of a nontrivial finisher stays unnoticed at first. So be careful about specifying the IDENTITY_FINISH
characteristic.
Note that when you don’t implement Collector
, but rather construct one by passing the functions to Collector.of(…)
, you never need to specify that characteristic, as it will be injected based on whether you specified a finisher function or not. For the overloaded method without a finisher, the generic signature will even ensure that the container type matches the result type.
Upvotes: 2