私は、他の 2 つのクラス ( ProducerとEvaluator ) を使用するメイン クラスSimulatorを持っています。プロデューサーは結果を生成し、評価者はそれらの結果を評価します。Simulator は、Producer にクエリを実行し、結果を Evaluator に伝えることで、実行の流れを制御します。
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<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;
}
}
このコードはコンパイルして実行する必要があります。選択したプロデューサーの実装に関係なく、同じ結果 (0.82) が得られるはずです。
コンパイラは、いくつかの場所でジェネリックを使用しないことについて警告します。
- Simulator クラス、インターフェイス IEvaluator と IProducer、および IProducer インターフェイスを実装するクラスで、Comparable インターフェイスを参照するたびに、次の警告が表示されます。Comparable は生の型です。ジェネリック型 Comparable への参照はパラメーター化する必要があります
- IEvaluator を実装するクラスで、次の警告が表示されます (Map の値に対して compareTo() を呼び出した場合) 。ジェネリック型 Comparable への参照はパラメーター化する必要があります
とはいえ、シミュレーターは機能します。ここで、コンパイル警告を取り除きたいと思います。問題は、インターフェイス IEvaluator と IProducer をパラメータ化する方法と、IProducer と IEvaluator の実装を変更する方法がわからないことです。
私にはいくつかの制限があります:
- プロデューサーが返す Map の値の型がわかりません。しかし、それらはすべて同じ型であり、 Comparable インターフェースを実装することはわかっています。
- 同様に、IEvaluator インスタンスは、評価中の結果について何も知る必要はありませんが、それらが同じ型であり、比較可能であることを除きます (IEvaluator 実装は、compareTo() メソッドを呼び出すことができる必要があります)。
- この「比較可能」なジレンマから Simulator クラスを除外する必要があります。これらのタイプについて何も知る必要はありません (同じタイプであることに加えて、これも比較可能です)。その仕事は、結果を Producer から Evaluator に単純に伝えることです。
何か案は?
編集・改訂版
以下の回答からいくつかのアイデアを使用して、この段階に到達しました。これは、警告なしでコンパイルおよび実行され、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<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;
}
}
主な違いは、現在このようになっている 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);
}
これは機能します。ただし、コードを次のように変更すると:
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);
}
くそったれなことは、コンパイルさえしません。推測されたタイプのキャプチャ #3-of ? extends Comparable は境界付きパラメーターの有効な代替ではありません >
これは私にはまったく奇妙です。evaluator.evaluate(producer.getResults(), groundTruth) を呼び出すと、コードが機能します。ただし、最初に Producer.getResults() メソッドを呼び出して変数に格納し、次にその変数 (evaluator.evaluate(ranks, groundTruth)) を使用して evaluate メソッドを呼び出すと、コンパイル エラーが発生します (その変数の値に関係なく)。タイプ)。