thisisshantzz
thisisshantzz

Reputation: 1097

Getting a ClassCastException when using a custom Collector with java 8 streams

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

Answers (1)

Holger
Holger

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

Related Questions