如何参数化可比较的界面? [英] How to parametrize Comparable interface?

查看:89
本文介绍了如何参数化可比较的界面?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个主要类 - 模拟器 - 使用其他两个类 - 生产者评估者。生产者产生结果,而评估者评估这些结果。模拟器通过查询生产者来控制执行流程,然后将结果传达给评估者。



生产者和评估者的实际实现在运行时是已知的,在编译时我只知道它们的接口。下面我粘贴接口,示例实现和Simulator类的内容。



旧代码



  package com.test; 

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/ **
*生产者产生结果。我不在乎他们的类型是什么,但是地图中的值
*必须在它们之间具有可比性。
* /
接口IProducer {
public Map< Integer,Comparable> getResults();
}

/ **
*此实现使用字符串对地图中的项目进行排序。
* /
类ProducerA实现IProducer {
@Override
public Map< Integer,Comparable> getResults(){
Map< Integer,Comparable> result = new HashMap< Integer,Comparable>();
result.put(1,A);
result.put(2,B);
result.put(3,B);
返回结果;
}
}

/ **
*此实现使用整数对地图中的项目进行排序。
* /
类ProducerB实现IProducer {
@Override
public Map< Integer,Comparable> getResults(){
Map< Integer,Comparable> result = new HashMap< Integer,Comparable>();
result.put(1,10);
result.put(2,30);
result.put(3,30);

返回结果;
}
}

/ **
*评估者根据给定的groundTruth评估结果。所有它需要
*来了解结果,它们之间是可比较的。
* /
接口IEvaluator {
public double evaluate(Map< Integer,Comparable> results,
Map< Integer,Double> groundTruth);
}
$ b $ **
*这是评估者(度量)的例子 - Kendall的Tau B.
* /
类KendallTauB实现IEvaluator {
@Override
public double evaluate(Map< Integer,Comparable> results,
Map< Integer,Double> groundTruth){

int concordant = 0,不一致= 0,tiedRanks = 0,tiedCapabilities = 0; (Entry< Integer,Comparable> rank2:results.entrySet()){
(Entry< Integer,Comparable> rank1:results.entrySet()){
if(rank1.getKey()< rank2.getKey()){
final可比较的r1 = rank1.getValue();
final可比较的r2 = rank2.getValue();
final Double c1 = groundTruth.get(rank1.getKey());
final Double c2 = groundTruth.get(rank2.getKey());

final int rankDiff = r1.compareTo(r2);
final int capDiff = c1.compareTo(c2);

if(rankDiff * capDiff> 0){
concordant ++;
} else if(rankDiff * capDiff <0){
discordant ++;
} else {
if(rankDiff == 0)
tiedRanks ++;

if(capDiff == 0)
tiedCapabilities ++;
}
}
}
}

final double n = result.size()*(results.size() - 1d)/ 2d;

return(一致 - 不一致)
/ Math.sqrt((n - tiedRanks)*(n - tiedCapabilities));
}
}

/ **
*模拟器类查询生产者并将结果传递给
*评估器。
* /
public class Simulator {
public static void main(String [] args){
Map< Integer,Double> groundTruth = new HashMap< Integer,Double>();
groundTruth.put(1,1d);
groundTruth.put(2,2d);
groundTruth.put(3,3d);

列表< IProducer> producerImplementations = lookUpProducers();
列表< IEvaluator> evaluateatorImplementations = lookUpEvaluators();

IProducer producer = producerImplementations.get(1); //选择一个生产者
IEvaluator评估者= evaluateatorImplementations.get(0); //选择评估者
//注意,这个类不应该知道实际来自
//生产者(除此之外是可比较的)
Map< Integer,Comparable> results = producer.getResults();
double score = evaluateator.evaluate(results,groundTruth);

System.out.printf(Score is%.2f\\\
,score);
}

//以下方法仅用于演示目的。实际上,我使用
// ServiceLoader.load(Clazz)来动态发现和加载类
//实现这些接口的类

public static List< IProducer> lookUpProducers(){
列表< IProducer> producer = new ArrayList< IProducer>();
producer.add(new ProducerA());
producer.add(new ProducerB());

回报生产者;
}

public static List< IEvaluator> lookUpEvaluators(){
List< IEvaluator>评估者= new ArrayList< IEvaluator>();
evaluators.add(new KendallTauB());

返回评估者;
}
}

这段代码应该编译并运行。无论您选择哪种生产者实现,您都应该得到相同的结果(0.82)。

编译器警告我不要在几个地方使用泛型:




  • 在Simulator类中,在接口IEvaluator和IProducer以及实现IProducer接口的类中,只要引用Comparable接口,就会收到以下警告: Comparable是一种原始类型。在实现IEvaluator的类中,我得到以下警告(对Map的值调用compareTo()时): Type
  • 安全性:方法compareTo(Object)属于原始类型Comparable。对泛型类型的引用应该参数化


所有这些都说明了模拟器的工作原理。现在,我想摆脱编译警告。问题是,我不知道如何参数化接口IEvaluator和IProducer,以及如何更改IProducer和IEvaluator的实现。



我有一些限制:


  • 我无法知道数值的类型制片人将返回的地图。但我知道,它们都是相同的类型,并且它们将实现Comparable接口。同样,IEvaluator实例不需要知道任何正在评估的结果,除了它们是相同的类型并且它们是可比较的(IEvaluator实现需要能够调用compareTo()方法)。

  • 我必须让Simulator类脱离这个可比较的困境 - 它不需要知道任何关于这些类型的东西(除了是相同的类型,这也是可比的)。它的工作是简单地将生产者的结果传达给评估者。



有什么想法?

编辑和修改版本



使用以下答案的一些想法,我到了这个阶段,它编译并运行时没有警告,也不需要使用SuppressWarnings指令。这与Eero的建议非常相似,但主要方法有些不同。

  package com.test; 

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/ **
*生产者产生结果。我不在乎他们的类型是什么,但是地图中的值
*必须在它们之间具有可比性。
* /
interface IProducer< T extends Comparable< T>> {
public Map< Integer,T> getResults();
}

/ **
*此实现使用字符串对地图中的项目进行排序。
* /
类ProducerA实现了IProducer< String> {
@Override
public Map< Integer,String> getResults(){
Map< Integer,String> result = new HashMap< Integer,String>();
result.put(1,A);
result.put(2,B);
result.put(3,B);

返回结果;
}
}

/ **
*此实现使用整数对地图中的项目进行排序。
* /
类ProducerB实现IProducer< Integer> {
@Override
public Map< Integer,Integer> getResults(){
Map< Integer,Integer> result = new HashMap< Integer,Integer>();
result.put(1,10);
result.put(2,30);
result.put(3,30);

返回结果;
}
}

/ **
*评估者根据给定的groundTruth评估结果。所有它需要
*来了解结果,它们之间是可比较的。
* /
接口IEvaluator {
public< T extends Comparable< T>>双重评估(Map< Integer,T> results,
Map< Integer,Double> groundTruth);
}
$ b $ **
*这是评估者(度量)的例子 - Kendall的Tau B.
* /
类KendallTauB实现IEvaluator {
@Override
public< T extends Comparable< T>> (Map< Integer,T> results,
Map< Integer,Double> groundTruth){
int concordant = 0,不一致= 0,tiedRanks = 0,tiedCapabilities = 0; (Entry< Integer,T> rank2:results.entrySet()){
(Entry< Integer,T> rank1:results.entrySet()){


) if(rank1.getKey()< rank2.getKey()){
final T r1 = rank1.getValue();
final T r2 = rank2.getValue();
final Double c1 = groundTruth.get(rank1.getKey());
final Double c2 = groundTruth.get(rank2.getKey());

final int rankDiff = r1.compareTo(r2);
final int capDiff = c1.compareTo(c2);

if(rankDiff * capDiff> 0){
concordant ++;
} else if(rankDiff * capDiff <0){
discordant ++;
} else {
if(rankDiff == 0)
tiedRanks ++;

if(capDiff == 0)
tiedCapabilities ++;
}
}
}
}

final double n = result.size()*(results.size() - 1d)/ 2d;

return(一致 - 不一致)
/ Math.sqrt((n - tiedRanks)*(n - tiedCapabilities));
}
}

/ **
*模拟器类查询生产者并将结果传递给
*评估器。
* /
public class Main {
public static void main(String [] args){
Map< Integer,Double> groundTruth = new HashMap< Integer,Double>();
groundTruth.put(1,1d);
groundTruth.put(2,2d);
groundTruth.put(3,3d);

列表< IProducer<?>> producerImplementations = lookUpProducers();
列表< IEvaluator> evaluateatorImplementations = lookUpEvaluators();

IProducer<?> producer = producerImplementations.get(0);
IEvaluator评估者= evaluateatorImplementations.get(0);

//注意,这个类不应该知道实际来自
//生产者(除此之外是可比的)
double score = evaluateator.evaluate(producer.getResults() ,groundTruth);

System.out.printf(Score is%.2f\\\
,score);
}

//以下方法仅用于演示目的。实际上,我使用
// ServiceLoader.load(Clazz)来动态发现和加载类
//实现这些接口的类
public static List< IProducer<>> lookUpProducers(){
List< IProducer<>> producer = new ArrayList< IProducer<>>();
producer.add(new ProducerA());
producer.add(new ProducerB());

回报生产者;
}

public static List< IEvaluator> lookUpEvaluators(){
List< IEvaluator>评估者= new ArrayList< IEvaluator>();
evaluators.add(new KendallTauB());

返回评估者;




$ b $ p
$ b

主要区别似乎在主要方法中,目前看起来像这样。

  public static void main(String [] args){
Map< Integer,Double> ; groundTruth = new HashMap< Integer,Double>();
groundTruth.put(1,1d);
groundTruth.put(2,2d);
groundTruth.put(3,3d);

列表< IProducer<?>> producerImplementations = lookUpProducers();
列表< IEvaluator> evaluateatorImplementations = lookUpEvaluators();

IProducer<?> producer = producerImplementations.get(0);
IEvaluator评估者= evaluateatorImplementations.get(0);

//注意,这个类不应该知道实际来自
//生产者(除此之外是可比的)
double score = evaluateator.evaluate(producer.getResults() ,groundTruth);

System.out.printf(Score is%.2f\\\
,score);
}

这有效。但是,如果我将代码更改为:

  public static void main(String [] args){
Map< ;整数,双精度> groundTruth = new HashMap< Integer,Double>();
groundTruth.put(1,1d);
groundTruth.put(2,2d);
groundTruth.put(3,3d);

列表< IProducer<?>> producerImplementations = lookUpProducers();
列表< IEvaluator> evaluateatorImplementations = lookUpEvaluators();

IProducer<?> producer = producerImplementations.get(0);
IEvaluator评估者= evaluateatorImplementations.get(0);

//注意,这个类不应该知道实际来自
//生产者(除此之外是可比较的)

//下面的行改变了
Map< Integer,?扩展Comparable<>>>等级= producer.getResults();
double score = evaluator.evaluate(ranks,groundTruth);

System.out.printf(Score is%.2f\\\
,score);
}

绑定不会编译,说:绑定不匹配:类型IEvaluator的泛型方法evaluate(Map,Map)不适用于参数(Map>,Map)。推断的类型捕获#3 - ?扩展Comparable不是有界参数的有效替代>



这对我来说很奇怪。如果我调用evaluateator.evaluate(producer.getResults(),groundTruth),代码将起作用。但是,如果我首先调用producer.getResults()方法,并将其存储到一个变量中,然后使用该变量(evaluateator.evaluate(ranks,groundTruth))调用evaluate方法,则会得到编译错误(不管该变量的类型)。

解决方案

我在下面发布了我的答案。一些注意事项:


  • 生产者显然知道他们自己的类型

  • 评估者不知道什么它处理直到方法被调用

  • producerImplementations包含几种不同的类型,所以当你实际选择其中一个时,你最终会进行一次转换。



以下代码:

  package com.test; 

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/ **
*生产者产生结果。我不在乎他们的实际类型是什么,但是地图中的
*值必须在它们之间具有可比性。
* /
interface IProducer< T extends Comparable< T>> {
public Map< Integer,T> getResults();
}

/ **
*本示例实施使用字符串对地图中的项目进行排序。
* /
类ProducerA实现了IProducer< String> {
@Override
public Map< Integer,String> getResults(){
Map< Integer,String> result = new HashMap< Integer,String>();
result.put(1,A);
result.put(2,B);
result.put(3,B);

返回结果;
}
}

/ **
*本示例实施使用整数对地图中的项目进行排序。
* /
类ProducerB实现IProducer< Integer> {
@Override
public Map< Integer,Integer> getResults(){
Map< Integer,Integer> result = new HashMap< Integer,Integer>();
result.put(1,10);
result.put(2,30);
result.put(3,30);

返回结果;
}
}

/ **
*评估者根据给定的groundTruth评估结果。所有它需要
*来了解结果,它们之间是可比较的。
* /
接口IEvaluator {
public< T extends Comparable< T>>双重评估(Map< Integer,T> results,
Map< Integer,Double> groundTruth);
}

/ **
*这是一个评估者Kendall Tau-B的例子。不要打扰
*语义,重要的是我希望能够为Map< Integer,T>中出现的每个(r1,r2)调用
* r1.compareTo(r2) ;结果。
* /
class KendallTauB实现IEvaluator {
@Override
public< T extends Comparable< T>> (Map< Integer,T> results,
Map< Integer,Double> groundTruth){
int concordant = 0,不一致= 0,tiedRanks = 0,tiedCapabilities = 0; (Entry< Integer,T> rank2:results.entrySet()){
(Entry< Integer,T> rank1:results.entrySet()){


) if(rank1.getKey()< rank2.getKey()){
final T r1 = rank1.getValue();
final T r2 = rank2.getValue();
final Double c1 = groundTruth.get(rank1.getKey());
final Double c2 = groundTruth.get(rank2.getKey());

final int ranksDiff = r1.compareTo(r2);
final int actualDiff = c1.compareTo(c2);

if(ranksDiff * actualDiff> 0){
concordant ++;
} else if(ranksDiff * actualDiff <0){
disordant ++;
} else {
if(ranksDiff == 0)
tiedRanks ++;

if(actualDiff == 0)
tiedCapabilities ++;
}
}
}
}

final double n = result.size()*(results.size() - 1d)/ 2d;

return(一致 - 不一致)
/ Math.sqrt((n - tiedRanks)*(n - tiedCapabilities));
}
}

/ **
*模拟器类查询生产者并将结果传递给
*评估器。
* /
public class Simulator {
public static void main(String [] args){
//地面实例的示例
Map< Integer,Double> groundTruth = new HashMap< Integer,Double>();
groundTruth.put(1,1d);
groundTruth.put(2,2d);
groundTruth.put(3,3d);

//动态加载生产者
列出< IProducer<?>> producerImplementations = lookUpProducers();

//动态加载评估者
List< IEvaluator> evaluateatorImplementations = lookUpEvaluators();

//挑选制作人
IProducer<?> producer = producerImplementations.get(0);

//选择评估者
IEvaluator评估者= evaluateatorImplementations.get(0);

//根据实际情况评估结果
double score = evaluateator.evaluate(producer.getResults(),groundTruth);

System.out.printf(Score is%.2f\\\
,score);
}

//以下方法仅用于演示目的。实际上,我使用
// ServiceLoader.load(Clazz)来动态发现和加载类
//实现接口IProducer和IEvaluator
public static List< IProducer<>> lookUpProducers(){
List< IProducer<>> producer = new ArrayList< IProducer<>>();
producer.add(new ProducerA());
producer.add(new ProducerB());

回报生产者;
}

public static List< IEvaluator> lookUpEvaluators(){
List< IEvaluator>评估者= new ArrayList< IEvaluator>();
evaluators.add(new KendallTauB());

返回评估者;
}
}


I have a main class -- Simulator -- that uses two other classes -- Producer and Evaluator. Producer produces results, while the Evaluator evaluates those results. The Simulator controls the flow of execution by querying the Producer and then conveying the results to the Evaluator.

The actual implementation of the Producer and Evaluator are known at run-time, at compile time I only know their interfaces. Below I paste the contents of interfaces, example implementations and the Simulator class.

Old code

package com.test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Producers produce results. I do not care what is their type, but the values
 * in the map have to be comparable amongst themselves.
 */
interface IProducer {
    public Map<Integer, Comparable> getResults();
}

/**
 * This implementation ranks items in the map by using Strings.
 */
class ProducerA implements IProducer {
    @Override
    public Map<Integer, Comparable> getResults() {
        Map<Integer, Comparable> result = new HashMap<Integer, Comparable>();
        result.put(1, "A");
        result.put(2, "B");
        result.put(3, "B");
        return result;
    }
}

/**
 * This implementation ranks items in the map by using integers.
 */
class ProducerB implements IProducer {
    @Override
    public Map<Integer, Comparable> getResults() {
        Map<Integer, Comparable> result = new HashMap<Integer, Comparable>();
        result.put(1, 10);
        result.put(2, 30);
        result.put(3, 30);

        return result;
    }
}

/**
 * Evaluator evaluates the results against the given groundTruth. All it needs
 * to know about results, is that they are comparable amongst themselves.
 */
interface IEvaluator {
    public double evaluate(Map<Integer, Comparable> results,
            Map<Integer, Double> groundTruth);
}

/**
 * This is example of an evaluator (a metric) -- Kendall's Tau B.
 */
class KendallTauB implements IEvaluator {
    @Override
    public double evaluate(Map<Integer, Comparable> results,
            Map<Integer, Double> groundTruth) {

        int concordant = 0, discordant = 0, tiedRanks = 0, tiedCapabilities = 0;

        for (Entry<Integer, Comparable> rank1 : results.entrySet()) {
            for (Entry<Integer, Comparable> rank2 : results.entrySet()) {
                if (rank1.getKey() < rank2.getKey()) {
                    final Comparable r1 = rank1.getValue();
                    final Comparable r2 = rank2.getValue();
                    final Double c1 = groundTruth.get(rank1.getKey());
                    final Double c2 = groundTruth.get(rank2.getKey());

                    final int rankDiff = r1.compareTo(r2);
                    final int capDiff = c1.compareTo(c2);

                    if (rankDiff * capDiff > 0) {
                        concordant++;
                    } else if (rankDiff * capDiff < 0) {
                        discordant++;
                    } else {
                        if (rankDiff == 0)
                            tiedRanks++;

                        if (capDiff == 0)
                            tiedCapabilities++;
                    }
                }
            }
        }

        final double n = results.size() * (results.size() - 1d) / 2d;

        return (concordant - discordant)
                / Math.sqrt((n - tiedRanks) * (n - tiedCapabilities));
    }
}

/**
 * The simulator class that queries the producer and them conveys results to the
 * evaluator.
 */
public class Simulator {
    public static void main(String[] args) {
        Map<Integer, Double> groundTruth = new HashMap<Integer, Double>();
        groundTruth.put(1, 1d);
        groundTruth.put(2, 2d);
        groundTruth.put(3, 3d);

        List<IProducer> producerImplementations = lookUpProducers();
        List<IEvaluator> evaluatorImplementations = lookUpEvaluators();

        IProducer producer = producerImplementations.get(1); // pick a producer
        IEvaluator evaluator = evaluatorImplementations.get(0); // pick an evaluator
        // Notice that this class should NOT know what actually comes from
        // producers (besides that is comparable)
        Map<Integer, Comparable> results = producer.getResults();
        double score = evaluator.evaluate(results, groundTruth);

        System.out.printf("Score is %.2f\n", score);
    }

    // Methods below are for demonstration purposes only. I'm actually using
    // ServiceLoader.load(Clazz) to dynamically discover and load classes that
    // implement these interfaces

    public static List<IProducer> lookUpProducers() {
        List<IProducer> producers = new ArrayList<IProducer>();
        producers.add(new ProducerA());
        producers.add(new ProducerB());

        return producers;
    }

    public static List<IEvaluator> lookUpEvaluators() {
        List<IEvaluator> evaluators = new ArrayList<IEvaluator>();
        evaluators.add(new KendallTauB());

        return evaluators;
    }
}

This code should compile and run. You should get the same result (0.82) regardless which producer implementation you select.

Compiler warns me about not using generics in several places:

  • In Simulator class, in interfaces IEvaluator and IProducer, and in classes that implement the IProducer interface, I get the following warning, whenever I reference the Comparable interface: Comparable is a raw type. References to generic type Comparable should be parameterized
  • In classes that implement IEvaluator I get the following warning (when calling compareTo() on the values of Map): Type safety: The method compareTo(Object) belongs to the raw type Comparable. References to generic type Comparable should be parameterized

All that said, the Simulator works. Now, I'd like to get rid of compile warnings. The problem is, that I have no idea, how to parametrize the interfaces IEvaluator and IProducer, and how to change the implementations of IProducer and IEvaluator.

I have some limitations:

  • I cannot know the type of values in the Map that the producer will return. But I know, that they will all be of the same type and that they will implement the Comparable interface.
  • Similarly, the IEvaluator instance does not need to know anything about results that is evaluating, except that they are of the same type and that they are comparable (IEvaluator implemetations need to be able to call the compareTo() method.).
  • I have to to keep the Simulator class out of this "Comparable" dilemma -- it does not need to know anything about those types (besides being of the same type, which is also comparable). Its job is to simply convey results from Producer to Evaluator.

Any ideas?

Edited and revised version

Using some ideas from answers below, I got to this stage, which compiles and runs without warnings and with no need to use SuppressWarnings directive. This is very similar to what Eero suggested, but the main method is a bit different.

package com.test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Producers produce results. I do not care what is their type, but the values
 * in the map have to be comparable amongst themselves.
 */
interface IProducer<T extends Comparable<T>> {
    public Map<Integer, T> getResults();
}

/**
 * This implementation ranks items in the map by using Strings.
 */
class ProducerA implements IProducer<String> {
    @Override
    public Map<Integer, String> getResults() {
        Map<Integer, String> result = new HashMap<Integer, String>();
        result.put(1, "A");
        result.put(2, "B");
        result.put(3, "B");

        return result;
    }
}

/**
 * This implementation ranks items in the map by using integers.
 */
class ProducerB implements IProducer<Integer> {
    @Override
    public Map<Integer, Integer> getResults() {
        Map<Integer, Integer> result = new HashMap<Integer, Integer>();
        result.put(1, 10);
        result.put(2, 30);
        result.put(3, 30);

        return result;
    }
}

/**
 * Evaluator evaluates the results against the given groundTruth. All it needs
 * to know about results, is that they are comparable amongst themselves.
 */
interface IEvaluator {
    public <T extends Comparable<T>> double evaluate(Map<Integer, T> results,
            Map<Integer, Double> groundTruth);
}

/**
 * This is example of an evaluator (a metric) -- Kendall's Tau B.
 */
class KendallTauB implements IEvaluator {
    @Override
    public <T extends Comparable<T>> double evaluate(Map<Integer, T> results,
            Map<Integer, Double> groundTruth) {
        int concordant = 0, discordant = 0, tiedRanks = 0, tiedCapabilities = 0;

        for (Entry<Integer, T> rank1 : results.entrySet()) {
            for (Entry<Integer, T> rank2 : results.entrySet()) {
                if (rank1.getKey() < rank2.getKey()) {
                    final T r1 = rank1.getValue();
                    final T r2 = rank2.getValue();
                    final Double c1 = groundTruth.get(rank1.getKey());
                    final Double c2 = groundTruth.get(rank2.getKey());

                    final int rankDiff = r1.compareTo(r2);
                    final int capDiff = c1.compareTo(c2);

                    if (rankDiff * capDiff > 0) {
                        concordant++;
                    } else if (rankDiff * capDiff < 0) {
                        discordant++;
                    } else {
                        if (rankDiff == 0)
                            tiedRanks++;

                        if (capDiff == 0)
                            tiedCapabilities++;
                    }
                }
            }
        }

        final double n = results.size() * (results.size() - 1d) / 2d;

        return (concordant - discordant)
                / Math.sqrt((n - tiedRanks) * (n - tiedCapabilities));
    }
}

/**
 * The simulator class that queries the producer and them conveys results to the
 * evaluator.
 */
public class Main {
    public static void main(String[] args) {
        Map<Integer, Double> groundTruth = new HashMap<Integer, Double>();
        groundTruth.put(1, 1d);
        groundTruth.put(2, 2d);
        groundTruth.put(3, 3d);

        List<IProducer<?>> producerImplementations = lookUpProducers();
        List<IEvaluator> evaluatorImplementations = lookUpEvaluators();

        IProducer<?> producer = producerImplementations.get(0);
        IEvaluator evaluator = evaluatorImplementations.get(0);

        // Notice that this class should NOT know what actually comes from
        // producers (besides that is comparable)
        double score = evaluator.evaluate(producer.getResults(), groundTruth);

        System.out.printf("Score is %.2f\n", score);
    }

    // Methods below are for demonstration purposes only. I'm actually using
    // ServiceLoader.load(Clazz) to dynamically discover and load classes that
    // implement these interfaces
    public static List<IProducer<?>> lookUpProducers() {
        List<IProducer<?>> producers = new ArrayList<IProducer<?>>();
        producers.add(new ProducerA());
        producers.add(new ProducerB());

        return producers;
    }

    public static List<IEvaluator> lookUpEvaluators() {
        List<IEvaluator> evaluators = new ArrayList<IEvaluator>();
        evaluators.add(new KendallTauB());

        return evaluators;
    }
}

The key difference seems to be in the main method, which currently looks like this.

    public static void main(String[] args) {
        Map<Integer, Double> groundTruth = new HashMap<Integer, Double>();
        groundTruth.put(1, 1d);
        groundTruth.put(2, 2d);
        groundTruth.put(3, 3d);

        List<IProducer<?>> producerImplementations = lookUpProducers();
        List<IEvaluator> evaluatorImplementations = lookUpEvaluators();

        IProducer<?> producer = producerImplementations.get(0);
        IEvaluator evaluator = evaluatorImplementations.get(0);

        // Notice that this class should NOT know what actually comes from
        // producers (besides that is comparable)
        double score = evaluator.evaluate(producer.getResults(), groundTruth);

        System.out.printf("Score is %.2f\n", score);
    }

This works. However, If I change the code to this:

    public static void main(String[] args) {
        Map<Integer, Double> groundTruth = new HashMap<Integer, Double>();
        groundTruth.put(1, 1d);
        groundTruth.put(2, 2d);
        groundTruth.put(3, 3d);

        List<IProducer<?>> producerImplementations = lookUpProducers();
        List<IEvaluator> evaluatorImplementations = lookUpEvaluators();

        IProducer<?> producer = producerImplementations.get(0);
        IEvaluator evaluator = evaluatorImplementations.get(0);

        // Notice that this class should NOT know what actually comes from
        // producers (besides that is comparable)

        // Lines below changed
        Map<Integer, ? extends Comparable<?>> ranks = producer.getResults();            
        double score = evaluator.evaluate(ranks, groundTruth);

        System.out.printf("Score is %.2f\n", score);
}

The darn thing won't even compile, saying: Bound mismatch: The generic method evaluate(Map, Map) of type IEvaluator is not applicable for the arguments (Map>, Map). The inferred type capture#3-of ? extends Comparable is not a valid substitute for the bounded parameter >

This is totally weird to me. The code works if I invoke the evaluator.evaluate(producer.getResults(), groundTruth). However, if I first call producer.getResults() method, and store it to a variable, and then invoke the evaluate method with that variable (evaluator.evaluate(ranks, groundTruth)), I get the compile error (regardless of that variable's type).

解决方案

I've posted my answer below. Some notes:

  • The Producers obviously know their own type
  • The Evaluator doesn't know what it's dealing with until the method is called
  • The producerImplementations contains several different types, so you end up with one cast when you actually pick one of them.

Code below:

package com.test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Producers produce results. I do not care what their actual type is, but the
 * values in the map have to be comparable amongst themselves.
 */
interface IProducer<T extends Comparable<T>> {
    public Map<Integer, T> getResults();
}

/**
 * This example implementation ranks items in the map by using Strings.
 */
class ProducerA implements IProducer<String> {
    @Override
    public Map<Integer, String> getResults() {
        Map<Integer, String> result = new HashMap<Integer, String>();
        result.put(1, "A");
        result.put(2, "B");
        result.put(3, "B");

        return result;
    }
}

/**
 * This example implementation ranks items in the map by using integers.
 */
class ProducerB implements IProducer<Integer> {
    @Override
    public Map<Integer, Integer> getResults() {
        Map<Integer, Integer> result = new HashMap<Integer, Integer>();
        result.put(1, 10);
        result.put(2, 30);
        result.put(3, 30);

        return result;
    }
}

/**
 * Evaluator evaluates the results against the given groundTruth. All it needs
 * to know about results, is that they are comparable amongst themselves.
 */
interface IEvaluator {
    public <T extends Comparable<T>> double evaluate(Map<Integer, T> results,
            Map<Integer, Double> groundTruth);
}

/**
 * This is example of an evaluator, metric Kendall Tau-B. Don't bother with
 * semantics, all that matters is that I want to be able to call
 * r1.compareTo(r2) for every (r1, r2) that appear in Map<Integer, T> results.
 */
class KendallTauB implements IEvaluator {
    @Override
    public <T extends Comparable<T>> double evaluate(Map<Integer, T> results,
            Map<Integer, Double> groundTruth) {
        int concordant = 0, discordant = 0, tiedRanks = 0, tiedCapabilities = 0;

        for (Entry<Integer, T> rank1 : results.entrySet()) {
            for (Entry<Integer, T> rank2 : results.entrySet()) {
                if (rank1.getKey() < rank2.getKey()) {
                    final T r1 = rank1.getValue();
                    final T r2 = rank2.getValue();
                    final Double c1 = groundTruth.get(rank1.getKey());
                    final Double c2 = groundTruth.get(rank2.getKey());

                    final int ranksDiff = r1.compareTo(r2);
                    final int actualDiff = c1.compareTo(c2);

                    if (ranksDiff * actualDiff > 0) {
                        concordant++;
                    } else if (ranksDiff * actualDiff < 0) {
                        discordant++;
                    } else {
                        if (ranksDiff == 0)
                            tiedRanks++;

                        if (actualDiff == 0)
                            tiedCapabilities++;
                    }
                }
            }
        }

        final double n = results.size() * (results.size() - 1d) / 2d;

        return (concordant - discordant)
                / Math.sqrt((n - tiedRanks) * (n - tiedCapabilities));
    }
}

/**
 * The simulator class that queries the producer and them conveys results to the
 * evaluator.
 */
public class Simulator {
    public static void main(String[] args) {
        // example of a ground truth
        Map<Integer, Double> groundTruth = new HashMap<Integer, Double>();
        groundTruth.put(1, 1d);
        groundTruth.put(2, 2d);
        groundTruth.put(3, 3d);

        // dynamically load producers
        List<IProducer<?>> producerImplementations = lookUpProducers();

        // dynamically load evaluators
        List<IEvaluator> evaluatorImplementations = lookUpEvaluators();

        // pick a producer
        IProducer<?> producer = producerImplementations.get(0);

        // pick an evaluator
        IEvaluator evaluator = evaluatorImplementations.get(0);

        // evaluate the result against the ground truth
        double score = evaluator.evaluate(producer.getResults(), groundTruth);

        System.out.printf("Score is %.2f\n", score);
    }

    // Methods below are for demonstration purposes only. I'm actually using
    // ServiceLoader.load(Clazz) to dynamically discover and load classes that
    // implement interfaces IProducer and IEvaluator
    public static List<IProducer<?>> lookUpProducers() {
        List<IProducer<?>> producers = new ArrayList<IProducer<?>>();
        producers.add(new ProducerA());
        producers.add(new ProducerB());

        return producers;
    }

    public static List<IEvaluator> lookUpEvaluators() {
        List<IEvaluator> evaluators = new ArrayList<IEvaluator>();
        evaluators.add(new KendallTauB());

        return evaluators;
    }
}

这篇关于如何参数化可比较的界面?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆