【问题标题】:Getting a ClassCastException when using a custom Collector with java 8 streams使用带有 java 8 流的自定义收集器时获取 ClassCastException
【发布时间】:2018-06-13 07:49:44
【问题描述】:

我有一个自定义的Collector

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);
    }
}

我是这样使用收集器的

try (Stream<MyModel> resultStream = generateDataStream()) {
    SortedSet<Map.Entry<Integer, ClusterModel>> clusters = resultStream.collect(new ClusteringCollector()); // This line throws a ClassCastException
}

但问题是,当我尝试运行上面的collect 方法时,我不断收到ClassCastException。这是堆栈跟踪

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)

谁能告诉我为什么会这样?我没有收到任何编译错误,finisherMap 正确转换为SortedSet

【问题讨论】:

    标签: java java-stream classcastexception collectors


    【解决方案1】:

    发布的代码中有几个错误表明这不是实际代码,但是,主要问题是可以识别的。尽管您在整理器中有一个复杂的转换,但您指定了 IDENTITY_FINISH 特征。

    IDENTITY_FINISH 特征意味着完成器就像Function.identity(),但在运行时,Stream 实现无法检查泛型签名是否与该声明兼容。当它使用此特性决定跳过完成器时,它只会返回容器对象,在您的情况下是 HashMap,当然,它不能分配给 SortedSet

    最后,这是这个错误的更好结果。更糟糕的是,如果容器和结果类型是兼容的,并且跳过一个重要的完成器一开始就没有被注意到。所以在指定IDENTITY_FINISH 特性时要小心。

    请注意,当您不实现Collector,而是通过将函数传递给Collector.of(…) 来构造一个时,您永远不需要指定该特征,因为它将根据您是否指定完成器函数或不是。对于没有finisher的重载方法,泛型签名甚至会确保容器类型匹配结果类型。

    【讨论】:

    • 附带说明,您正在多次执行if(map.containsKey(key)) { map.get(key).someMethod(value); } else { map.put(key, value); } 之类的操作。你可以改用map.merge(key, value, ValueType::someMethod);。此外,您可以将比较器简化为Comparator.comparingDouble(cm -&gt; cm.getValue().getClusterCount() * cm.getValue().getClusterWeight())
    猜你喜欢
    • 1970-01-01
    • 2016-01-25
    • 2023-03-17
    • 2016-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多