¿Cómo parametrizar la interfaz comparable?

Tengo una clase principal -Simulador - que usa otras dos clases -Productor yEvaluador. El productor produce resultados, mientras que el Evaluador evalúa esos resultados. El Simulador controla el flujo de ejecución consultando al Productor y luego transmitiendo los resultados al Evaluador.

La implementación real del Productor y el Evaluador se conocen en tiempo de ejecución, en el momento de la compilación, solo conozco sus interfaces. A continuación pego el contenido de las interfaces, las implementaciones de ejemplo y la clase Simulator.

Código antiguo
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;
    }
}

Este código debe compilar y ejecutar. Debería obtener el mismo resultado (0.82) independientemente de la implementación del productor que seleccione.

El compilador me advierte sobre no usar genéricos en varios lugares:

En la clase Simulator, en las interfaces IEvaluator y IProducer, y en las clases que implementan la interfaz IProducer, recibo la siguiente advertencia, siempre que hago referencia a la interfaz comparable:Comparable es un tipo crudo. Las referencias al tipo genérico Comparable deben ser parametrizadasEn las clases que implementan IEvaluator, obtengo la siguiente advertencia (al llamar a compareTo () en los valores del Mapa):Tipo de seguridad: el método compareTo (objeto) pertenece al tipo sin formato Comparable. Las referencias al tipo genérico Comparable deben ser parametrizadas

Todo lo dicho, el simulador funciona. Ahora, me gustaría deshacerme de las advertencias de compilación. El problema es que no tengo idea de cómo parametrizar las interfaces IEvaluator y IProducer, y cómo cambiar las implementaciones de IProducer y IEvaluator.

Tengo algunas limitaciones:

No puedo saber el tipo de valores en el Mapa que devolverá el productor. Pero sé que todos serán del mismo tipo y que implementarán la interfaz comparable.De forma similar, la instancia de IEvaluator no necesita saber nada sobre los resultados que se están evaluando, excepto que son del mismo tipo y que son comparables (las implementaciones de IEvaluator deben poder llamar al método compareTo ()).Tengo que mantener a la clase Simulator fuera de este dilema "comparable": no necesita saber nada sobre esos tipos (además de ser del mismo tipo, que también es comparable). Su trabajo es simplemente transmitir los resultados del Productor al Evaluador.

¿Algunas ideas?

Versión editada y revisada

Usando algunas ideas de las respuestas a continuación, llegué a esta etapa, que compila y ejecuta sin advertencias y sin necesidad de usar la directiva SuppressWarnings. Esto es muy similar a lo que Eero sugirió, pero el método principal es un poco diferente.

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

La diferencia clave parece estar en el método principal, que actualmente se ve así.

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

Esto funciona. Sin embargo, si cambio el código a esto:

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

La maldita cosa ni siquiera compilará, diciendo:Desajuste de enlace: el método genérico de evaluación (Mapa, Mapa) de tipo IEvaluator no es aplicable para los argumentos (Mapa>, Mapa). El tipo inferido captura # 3-de? ampliables Comparable no es un sustituto válido para el parámetro delimitado>

Esto es totalmente extraño para mí. El código funciona si invoco evaluator.evaluate (producer.getResults (), groundTruth). Sin embargo, si primero invoco al método producer.getResults (), lo almaceno en una variable y luego invoco el método de evaluación con esa variable (evaluator.evaluate (ranks, groundTruth)), obtengo el error de compilación (independientemente de la variable de esa variable). tipo).