2016-01-21 95 views
1

我現在搜索並嘗試了一天以上,無法找到我在Java中遇到的常見問題的解決方案。原因很明顯 - 類型刪除。但我的問題是:在Java中這個問題真的沒有好的解決方案嗎?由於這類問題每次都會出現,所以我願意調查更多時間。Java工廠使用泛型,.class與.getClass()

我得到的錯誤是:

的方法doStrategy在IStrategy <捕獲#2的類型(?捕獲#2的延伸I)?擴展I>不適用於參數(I)

所以我簡化了下面的例子。

想象的模型:

package model; 

public interface I { 

//there are actual 30 classes implementing I... 
} 

public class A implements I { 

    public void someSpecificMagicForA(){ 
     System.out.println("A"); 
    } 
} 

public class B implements I { 

    public void someSpecificMagicForB() { 
     System.out.println("B"); 
    } 

} 

和選擇邏輯

和通用戰略廠

package strategy; 

import java.util.HashMap; 
import java.util.Map; 

import model.A; 
import model.B; 

public class StrategyFactory { 

    static { 
     strategies.put(A.class, AStrategy.class); 
     strategies.put(B.class, BStrategy.class); 
    } 
    private static final Map<Class<?>, Class<? extends IStrategy<?>>> strategies = new HashMap<>(); 

    @SuppressWarnings("unchecked") // I am fine with that suppress warning 
    public <T> IStrategy<T> createStategy(Class<T> clazz){ 
     Class<? extends IStrategy<?>> strategyClass = strategies.get(clazz); 

     assert(strategyClass != null); 

     try { 
      return (IStrategy<T>) strategyClass.newInstance(); 
     } catch (InstantiationException | IllegalAccessException e) { 
      e.printStackTrace(); 
      return null; 
     } 
    } 
} 

這裏是測試

import java.util.ArrayList; 
import java.util.List; 

import junit.framework.TestCase; 
import model.A; 
import model.B; 
import model.I; 
import strategy.IStrategy; 
import strategy.StrategyFactory; 


public class TestCases extends TestCase { 

    public void testWithConcreteType(){ 

     B b = new B(); 

     StrategyFactory factory = new StrategyFactory(); 
     IStrategy<B> createStategy = factory.createStategy(B.class); 
     createStategy.doStrategy(b); //awesome 
    } 

    public void testWithGenericType(){ 

     List<I> instances = createTestData(); // image this is the business data 

     StrategyFactory factory = new StrategyFactory(); 

     for (I current : instances){ 
      IStrategy<? extends I> createStategy = factory.createStategy(current.getClass()); 
      createStategy.doStrategy(current); //meh 
      //The method doStrategy(capture#2-of ? extends I) in the type IStrategy<capture#2-of ? extends I> 
      //is not applicable for the arguments (I) 
     } 
    } 

    private List<I> createTestData(){ 
     A a = new A(); 
     B b = new B(); 

     List<I> instances = new ArrayList<>(); 
     instances.add(a); 
     instances.add(b); 

     return instances; 
    } 
} 

我嘗試了另一種方法,使用番石榴TypeTokens(https://github.com/google/guava/wiki/ReflectionExplained)。但是我沒有設法做到這一點,因爲我真的沒有< T>,因爲我得到的是實現該接口的實例集合。

我有一個工作,並沒有那麼糟糕的解決方案,雖然使用訪問者模式。因爲我有真實的類

...visitor class 
public void visit(A a){ 
    doVisit(A.class, a); //private generic method now works of course 
} 

在編譯時再次一切都很好。但在這個特殊情況下,我花了相當長的時間來實現這個訪問者的超過30個子類。所以我真的想爲未來有一個更好的解決方案。

任何意見,非常感謝。

+1

ohhhh!這很多.. – Satya

+0

@ mr-lister thx編輯 - didn't知道&lt問題 - 但是有道理^^ – Rainer

回答

2

問題無關與擦除。 Java的類型系統不足以在沒有幫助的情況下靜態推理關於currentcurrent.getClass()之間的關係。

在您的代碼:

for (I current : instances){ 
    IStrategy<? extends I> createStategy = factory.createStategy(current.getClass()); 
    createStategy.doStrategy(current); 
} 

current.getClass()的結果是Class<? extends I>類型的對象;也就是我們不知道的一些子類型I。我們程序員知道,無論它是什麼類型,這也是current的具體類型,因爲我們已經閱讀了getClass的文檔,但類型系統不知道。所以,當我們得到一個IStrategy<? extends I>,我們所知道的是,這是一個策略,一些子類型I,不一定I本身。此外,通配符類型(類型爲?)旨在丟失更多信息,因此類型系統甚至不知道我們的策略接受與getClass()結果相同的類型。

因此,爲了使節目類型檢測,我們需要(a)給予該類型系統的一些非通配符名稱爲I的具體亞型即current是,和(b)告訴IT人員的current實際上值有該類型。好消息是,我們可以做到這一點。

爲了給通配符類型一個名字,我們可以使用一種叫做「通配符捕獲」的技術,在這裏我們創建一個專用輔助函數,其唯一的作用是將一個特定的類型變量名賦予一個類型,通配符。我們將測試循環體拉出到它自己的功能極其需要一個類型參數:

private <T> void genericTestHelperDraft1(Class<T> currentClass, T current) { 
    StrategyFactory factory = new StrategyFactory(); 
    IStrategy<T> createStrategy = factory.createStategy(t); 
    createStrategy.doStrategy(current); // works 
} 

這個函數的任務是有效地引入類型參數T,它可以讓Java的知道,我們打算參考相同未知類型T無處不在我們使用它。有了這些信息,我們可以瞭解到我們從工廠獲得的策略與我們的輸入類具有相同的類型,這是類型簽名中的當前類型current

不幸的是,我們去的時候調用這個方法,我們仍然會得到一個編譯錯誤:

for (I current : instances){ 
    genericTestHelperDraft1(current.getClass(), current); 
    // Type error because current is not of type "capture of ? extends I" 
} 

這裏的問題是,該型系統不知道current有它自己的類型! Java的類型系統不理解currentcurrent.getClass()之間的關係,所以它不知道無論類型current.getClass()返回什麼類型,我們都可以將current視爲該類型的值。幸運的是,我們可以用簡單的下降來解決這個問題,因爲我們(程序員)知道current有它自己的類型。我們必須在我們的助手中這樣做,因爲在助手之外,我們沒有任何名稱,我們想要斷言current有。我們可以改變的代碼如下所示:

private <T> void genericTestHelperDraft2(Class<T> t, Object current) { 
    T currentDowncast = t.cast(current); 
    StrategyFactory factory = new StrategyFactory(); 
    IStrategy<T> createStrategy = factory.createStategy(t); 
    createStrategy.doStrategy(currentDowncast); 
} 

現在我們可以在測試中改變環路:

for (I current : instances){ 
    genericTestHelperDraft2(current.getClass(), current); 
} 

和一切正常。

+1

此外,我們在這裏失去了一點類型的保存,我將其標記爲最佳答案,因爲我認爲這是針對java中這類問題的最佳「通用」解決方案。非常感謝您的詳細解答! – Rainer

1

Generic是一個編譯時功能,您只能使用編譯器可以確定的安全功能。

注意類型在所有情況下都不會被擦除。例如,您可以獲得AStrategyBStrategy的類型,因爲這些是非動態的具體類型。

AStrategy as = new AStrategy(); 
for(AnnotatedType asc : as.getClass().getAnnotatedInterfaces()) { 
    Type type = asc.getType(); 
    System.out.println(type); 
    if (type instanceof ParameterizedType) { 
     ParameterizedType pt = (ParameterizedType) type; 
     for (Type t : pt.getActualTypeArguments()){ 
      System.out.println(t); // class A 
     } 
    } 
} 

打印

IStrategy<A> 
class A 

所有這些代碼是否讓您的解決方案簡單,我不知道,但你可以得到它正在實施IStrategy的類型提供上課的時候編譯已知。

+1

感謝您的答案 - 我會調查這種方法。不幸的是,由於在這裏缺乏聲譽,我無法對你的答案讚不絕口。 :( – Rainer

1

在Peter的回答和一些更多的研究之後,我相當確信,如果不以這種或那種方式增強模型,這在Java中是不可能的。我決定留在遊客模式,因爲編譯時間檢查值得我看來額外的代碼。所以這裏是我在最後實現的(也確定你都知道訪問者模式 - 只是要完成)。

public interface I { 

    public void acceptVisitor(IVisitor visitor); 

    //there are actual 30 classes implementing I... 
} 

public interface IVisitor { 

    public void visit(A a); 

    public void visit(B b); 
} 

public void testWithGenericType(){ 

     List<I> instances = createTestData(); // image this is the business data 

     StrategyFactory factory = new StrategyFactory(); 
     Visitor visitor = new Visitor(factory); 

     for (I current : instances){ 
      current.acceptVisitor(visitor); 
     } 
    } 

    class Visitor implements IVisitor { 

     private final StrategyFactory factory; 

     public Visitor(StrategyFactory factory) { 
      this.factory = factory; 
     } 

     private <T> void doVisit(Class<T> clazz, T t){ 
      IStrategy<T> createStategy = factory.createStategy(clazz); 
      createStategy.doStrategy(t); 
     } 

     @Override 
     public void visit(A a) { 
      doVisit(A.class, a); 
     } 

     @Override 
     public void visit(B b) { 
      doVisit(B.class, b); 
     } 
    } 

希望這可能有助於別人。

問候, 賴