2012-09-03 46 views
4

想象具有主類 - 模擬器 - 使用其它兩個類 - 生產者評價者,它實現接口IProducer和IEvaluator,分別。Java類型系統中的錯誤?

IProducer實現產生結果,而IEvaluator實現評估這些結果。模擬器通過查詢IProducer實現來控制執行流程,然後將結果傳遞給IEvaluator實例。

生產者和評估者的實際實現在運行時是已知的,在編譯時我只知道他們的接口。檢查下面的例子。

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

該代碼編譯時沒有警告,也運行它應該的方式。這是solution to the question that I asked before,所以這是一個後續問題。

使用上面的代碼,假設您想將producer.getResults()調用的結果存儲到變量中(稍後將用於調用evaluateator.evaluate(results,groundTruth)call )。 該變量的類型是什麼?

地圖<整數,? >,地圖<整數,?擴展可比較的<? > >?使主方法通用並使用泛型類型?目前爲止我沒有嘗試過。編譯器抱怨我提出的每種類型。

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 
    Map<Integer, ?> data = producer.getResults(); // this type works 
    double score = evaluator.evaluate(data, groundTruth); // but now this call does not 


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

它看起來像producer.getResults()返回的東西,不能用Java靜態表達。這是一個錯誤,或者我錯過了什麼?

+0

爲什麼你不能只使用'evaluateator.evaluate(producer.getResults(),groundTruth)'? – Jeffrey

+0

@Jeffrey,我可以使用它,但是,想象你想用幾個不同的評估者實例**對同一個數據**(從某個生產者實例返回)調用evaluate(data,goundTruth)。因爲getResults通常包含* heavy computing *,所以對每個這樣的調用調用getResult並不明智。 – David

+0

您是否嘗試過使用'Map',但沒有泛型類型參數?我知道在這裏迴避這個問題,但至少應該編譯和運行。那麼確保'Map'中有正確的東西就是你的責任。 – aroth

回答

1

這不是一個錯誤,而是一個限制。在類型系統社區中衆所周知,帶有通配符的Java具有不能用Java語法表達的類型。你的例子展示了一個這樣的案例,並且證明通配符與F-bound多態性(即T extends Something<T>形式的類型參數)基本上不兼容。說穿了,通配符是一個可怕的類型系統黑客攻擊。他們不應該被放入Java中。真正想要什麼,以及什麼能讓你的例子表達出來,是合適的存在類型(其中通配符是有限的臨時變體)。不幸的是,Java沒有它們(儘管Scala有)。

2

一個說明我的答案之前:您所有的T extends Comparable<T>報表的可能應該是T extends Comparable<? super T>,它讓更多的靈活性(你爲什麼要關心,如果它比較T S或Object S'),並且它是我的解決方案的工作要求。

這實際上不是Java類型系統的「bug」,它只是一個不便之處。 Java並不特別喜歡將交集類型作爲類型聲明的一部分。我發現來解決這個問題

一種方法是建立在正常情況下不應該被用在「不安全」的方法:

@SuppressWarnings("unchecked") 
private static <T extends Comparable<? super T>> Map<Integer, T> cast(Map<Integer, ?> map) { 
    return (Map<Integer, T>) map; 
} 

只要確保調用此方法具有Map實際是Map<Integer, T extends Comparable<? super T>>(如IProducer的返回)。

使用這種方法,你可以做到以下幾點:

IProducer<?> producer = ... 
IEvaluator evaluator = ... 
Map<Integer, ?> product = producer.getResults(); 
evaluator.evaluate(cast(product), truth); 

然後Java將自動地推斷出正確的類型參數爲您服務。

此外,在Java社區中,I前綴爲generally frowned upon

+0

謝謝。一些評論: - 而不是@SuppressWarnings(「未使用」)它應該是@SuppressWarnings(「unchecked」)。 - 我有類似的東西,但SuppressWarnings指令困擾着我 - 因爲沒有明顯的原因,只是覺得我沒有做正確的事。所以我試圖找到更好的東西。我很驚訝,我偶然發現了語言限制/不便。 - PS:也謝謝你指出他IInterface公約。我會放棄它。 – David

+0

@大衛啊,哎呀。我正在徒手輸入這個代碼。只要您可以驗證演員確實是否正確,就可以取消未經檢查的警告。編譯器不夠聰明,因爲它會刪除所有類型的信息。作爲程序員,您*很聰明,可以驗證演員陣容,因爲您不必通過類型擦除來保持二進制兼容性。 – Jeffrey