如何参数化可比接口?

我有一个主类 – 模拟器 – 使用另外两个类 – ProducerEvaluator 。 生产者生成结果,而评估者评估这些结果。 模拟器通过查询生产者然后将结果传送给评估者来控制执行流程。

Producer和Evaluator的实际实现在运行时是已知的,在编译时我只知道它们的接口。 下面我粘贴接口,示例实现和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; /** * 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 getResults(); } /** * This implementation ranks items in the map by using Strings. */ class ProducerA implements IProducer { @Override public Map getResults() { Map result = new HashMap(); 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 getResults() { Map result = new HashMap(); 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 results, Map groundTruth); } /** * This is example of an evaluator (a metric) -- Kendall's Tau B. */ class KendallTauB implements IEvaluator { @Override public double evaluate(Map results, Map groundTruth) { int concordant = 0, discordant = 0, tiedRanks = 0, tiedCapabilities = 0; for (Entry rank1 : results.entrySet()) { for (Entry rank2 : results.entrySet()) { if (rank1.getKey()  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 groundTruth = new HashMap(); groundTruth.put(1, 1d); groundTruth.put(2, 2d); groundTruth.put(3, 3d); List producerImplementations = lookUpProducers(); List 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 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 lookUpProducers() { List producers = new ArrayList(); producers.add(new ProducerA()); producers.add(new ProducerB()); return producers; } public static List lookUpEvaluators() { List evaluators = new ArrayList(); evaluators.add(new KendallTauB()); return evaluators; } } 

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

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

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

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

我有一些限制:

  • 我无法知道生产者将返回的Map中的值类型。 但我知道,它们都属于同一类型,并且它们将实现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; /** * 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> { public Map getResults(); } /** * This implementation ranks items in the map by using Strings. */ class ProducerA implements IProducer { @Override public Map getResults() { Map result = new HashMap(); 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 getResults() { Map result = new HashMap(); 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> double evaluate(Map results, Map groundTruth); } /** * This is example of an evaluator (a metric) -- Kendall's Tau B. */ class KendallTauB implements IEvaluator { @Override public <T extends Comparable> double evaluate(Map results, Map groundTruth) { int concordant = 0, discordant = 0, tiedRanks = 0, tiedCapabilities = 0; for (Entry rank1 : results.entrySet()) { for (Entry rank2 : results.entrySet()) { if (rank1.getKey()  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 groundTruth = new HashMap(); groundTruth.put(1, 1d); groundTruth.put(2, 2d); groundTruth.put(3, 3d); List<IProducer> producerImplementations = lookUpProducers(); List 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 lookUpEvaluators() { List evaluators = new ArrayList(); evaluators.add(new KendallTauB()); return evaluators; } } 

主要方法似乎是主要方法,目前看起来像这样。

  public static void main(String[] args) { Map groundTruth = new HashMap(); groundTruth.put(1, 1d); groundTruth.put(2, 2d); groundTruth.put(3, 3d); List<IProducer> producerImplementations = lookUpProducers(); List 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); } 

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

  public static void main(String[] args) { Map groundTruth = new HashMap(); groundTruth.put(1, 1d); groundTruth.put(2, 2d); groundTruth.put(3, 3d); List<IProducer> producerImplementations = lookUpProducers(); List 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); } 

darn的东西甚至都不会编译,说: 绑定不匹配:IEvaluator类型的generics方法evaluate(Map,Map)不适用于参数(Map>,Map)。 推断类型捕获#3-of? extends Comparable不是有界参数的有效替代>

这对我来说非常奇怪。 如果我调用evaluateator.evaluate(producer.getResults(),groundTruth),代码就可以工作。 但是,如果我首先调用producer.getResults()方法,并将其存储到变量中,然后使用该变量调用evaluate方法(evaluateator.evaluate(ranking,groundTruth)),我会得到编译错误(无论该变量是什么)类型)。

我在下面发布了我的答案。 一些说明:

  • 制片人显然知道他们自己的类型
  • 在调用该方法之前,Evaluator不知道它正在处理什么
  • 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; /** * 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> { public Map getResults(); } /** * This example implementation ranks items in the map by using Strings. */ class ProducerA implements IProducer { @Override public Map getResults() { Map result = new HashMap(); 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 { @Override public Map getResults() { Map result = new HashMap(); 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 results, Map 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 results. */ class KendallTauB implements IEvaluator { @Override public > double evaluate(Map results, Map groundTruth) { int concordant = 0, discordant = 0, tiedRanks = 0, tiedCapabilities = 0; for (Entry rank1 : results.entrySet()) { for (Entry 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 groundTruth = new HashMap(); groundTruth.put(1, 1d); groundTruth.put(2, 2d); groundTruth.put(3, 3d); // dynamically load producers List> producerImplementations = lookUpProducers(); // dynamically load evaluators List 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> lookUpProducers() { List> producers = new ArrayList>(); producers.add(new ProducerA()); producers.add(new ProducerB()); return producers; } public static List lookUpEvaluators() { List evaluators = new ArrayList(); evaluators.add(new KendallTauB()); return evaluators; } } 

您需要指定对象愿意与之比较的事物类型。 就像是:

 import java.util.Map; import java.util.HashMap; interface IProducer> { public Map getResults(); } interface IEvaluator { public > double evaluate(Map results, Map groundTruth); } public class Main { public static void main(String[] args) { IProducer producer = null; IEvaluator evaluator = null; Map results = producer.getResults(); double score = evaluator.evaluate(results, new HashMap()); } } 

警告只是要求你做这样的事吗?

 public interface IProducer> { public Map getResults(); } 

每当我实现Comparable (或扩展Comparator )时,我总是这样做:

 public class Dog implements Comparable { private String breed; public String getBreed() { return breed; } public void setBreed(String s) { breed = s; } public int compareTo(Dog d) { return breed.compareTo(d.getBreed()); } } 

请注意,当参数化Comparable时,不需要在compareTo中使用对象。