Jak sparametryzować porównywalny interfejs?
Mam główną klasę -Symulator - używa dwóch innych klas -Producent iEwaluator. Producent generuje wyniki, podczas gdy oceniający ocenia te wyniki. Symulator steruje przepływem wykonania, wysyłając zapytanie do producenta, a następnie przekazując wyniki do oceniającego.
Rzeczywista implementacja Producenta i Ewaluatora jest znana w czasie wykonywania, w czasie kompilacji znam tylko ich interfejsy. Poniżej wklejam zawartość interfejsów, przykładowe implementacje i klasę Simulator.
Stary kodpackage 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;
}
}
Ten kod powinien się skompilować i uruchomić. Powinieneś uzyskać taki sam wynik (0,82) niezależnie od wybranej wersji producenta.
Kompilator ostrzega mnie przed niestosowaniem generycznych w kilku miejscach:
W klasie Simulator, w interfejsach IEvaluator i IProducer oraz w klasach, które implementują interfejs IProducer, otrzymuję następujące ostrzeżenie, gdy odwołuję się do interfejsu Porównywalnego:Porównywalny jest typ surowy. Odniesienia do typu ogólnego Porównywalne należy sparametryzowaćW klasach implementujących IEvaluator otrzymuję następujące ostrzeżenie (podczas wywoływania compareTo () na wartościach Map):Bezpieczeństwo typu: Metoda compareTo (Object) należy do typu raw Porównywalny. Odniesienia do typu ogólnego Porównywalne należy sparametryzowaćWszystko, co powiedział, Symulator działa. Teraz chciałbym pozbyć się kompilujących ostrzeżeń. Problem polega na tym, że nie mam pojęcia, jak sparametryzować interfejsy IEvaluator i IProducer oraz jak zmienić implementacje IProducer i IEvaluator.
Mam pewne ograniczenia:
Nie mogę znać typu wartości na mapie, które producent zwróci. Ale wiem, że wszystkie będą tego samego typu i że zaimplementują interfejs Porównywalny.Podobnie instancja IEvaluator nie musi nic wiedzieć o wynikach, które są oceniane, z wyjątkiem tego, że są tego samego typu i są porównywalne (implementacje IEvaluator muszą być w stanie wywołać metodę compareTo ()).Muszę trzymać klasę Symulatora z dylematu „Porównywalny” - nie musi nic wiedzieć o tych typach (poza tym, że jest tego samego typu, co również jest porównywalne). Jego zadaniem jest po prostu przekazywanie wyników od producenta do ewaluatora.Jakieś pomysły?
Edytowana i poprawiona wersjaKorzystając z kilku pomysłów z poniższych odpowiedzi, dotarłem do tego etapu, który kompiluje i działa bez ostrzeżeń i bez potrzeby korzystania z dyrektywy SuppressWarnings. Jest to bardzo podobne do tego, co zasugerował Eero, ale główna metoda jest nieco inna.
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;
}
}
Kluczową różnicą wydaje się być metoda główna, która obecnie wygląda tak.
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);
}
To działa. Jeśli jednak zmienię kod do tego:
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);
}
Rzecz cholerna nawet się nie skompiluje, mówiąc:Niezgodność powiązania: ogólna metoda oceny (mapa, mapa) typu IEvaluator nie ma zastosowania do argumentów (mapa>, mapa). Wnioskowane przechwycenie typu # 3-of? extends Porównywalny nie jest prawidłowym substytutem ograniczonego parametru>
To dla mnie całkowicie dziwne. Kod działa, jeśli wywołam ewaluator.evaluate (producent.getResults (), groundTruth). Jeśli jednak najpierw wywołam metodę producer.getResults () i zapiszę ją w zmiennej, a następnie wywołam metodę oceny z tą zmienną (Evaluator.evaluate (ranks, groundTruth)), otrzymuję błąd kompilacji (niezależnie od tej zmiennej rodzaj).