2017-08-01 49 views
4

假設我們有三個類 - AbstractMessageAbstractEngineAbstractAction。這三個類都以通用的方式相互引用,因此每個引擎都有相應的消息和操作,您可以直接在代碼中引用它們。如何確保這是我的類簽名中引用的泛型類型?

public class MyMessage<M extends AbstractMessage<M,E,A>, E extends AbstractEngine<M,E,A>, A extends AbstractAction<M,E,A>> { 

這工作正常,但是當我嘗試在最高級別執行行爲時遇到了一些問題。我的AbstractAction類具有正是如此定義的applyTo方法:

protected abstract M applyTo(E engine, Object guarantee); 

和我AbstractEngine類有這個

private final M apply(A action) { 
    return action.apply(this, this.guarantee); 
} 

而正是在這條線,它不太願意 - 抱怨說:

The method applyTo(E, Object) in the type AbstractAction<M,E,A> is not 
applicable for the arguments (AbstractEngine<M,E,A>, Object) 

現在其原因很明顯 - 所討論的E可能是一些OTHER AbstractEngine,並且無法知道我們調用它的子類是否實際上是一個E

我的問題是,我怎麼能說對確定性,如果你要class MyEngine extends AbstractEngine<M...,E...,A...>MyEngineMUSTE?並有這種確定性烘烤到AbstractEngine

下面是一個說明問題的小例子。

class EngineExample { 

    static abstract class AbEng<A extends AbAct<A,M,E>, M extends AbMes<A,M,E>, E extends AbEng<A,M,E>> { 

     final M func(A act) { 
      return act.apply(this); // compile error here 
     } 

    } 

    static abstract class AbMes<A extends AbAct<A,M,E>, M extends AbMes<A,M,E>, E extends AbEng<A,M,E>> { 

    } 

    static abstract class AbAct<A extends AbAct<A,M,E>, M extends AbMes<A,M,E>, E extends AbEng<A,M,E>> { 

     abstract void apply(E e); 

    } 

    static class RealEng extends AbEng<RealAct, RealMes, RealEng> { 

    } 

    static class RealMes extends AbMes<RealAct, RealMes, RealEng> { 

    } 

    static class RealAct extends AbAct<RealAct, RealMes, RealEng> { 

     void apply(RealEng eng) { 
      System.out.println("applied!"); 
     } 
    } 

} 
+0

@Tezra並非所有的東西都需要mcve。我沒有類型錯誤。對不起,你不明白這個問題,但也許你應該再讀一遍。也許還可以讀瓦倫丁的答案,這是對問題所在的嘗試。 – corsiKa

+0

Tezra,A將永遠是'AbstractAction' - 就在第一個代碼片段中。就編譯時限制它們的類型而言,我不想在每個類上都定義它們 - 我想在實例時聲明它們。我以前使用過這個範例,並且它工作得很好*除了*這個愚蠢的類型問題不知道它們是在通用描述符中指定的類型。而已。 – corsiKa

+0

@Tezra對不起,對'AbstractMessage','AbstractAction'和'AbstractEngine'中的每一個,您都有'M extends AbstractMessage ,E extends AbstractEngine ,A extends AbstractAction 'rider。我認爲這是這個問題的暗示,但它似乎沒有。 – corsiKa

回答

3

使用寬鬆有效的參數類型

最簡單的辦法,就是沒有實際執行的是this isInstanceOf E。抽象的規則已經保證這是一個安全的操作,所以如果你改變參數來允許任何引擎,它就會工作。

abstract Action<E> { 
    public void apply(Engine<?> e, Object o) { 
    e.doSomething(o); 
    } 
} 

abstract Action<E> { 
    <T extends Engine<?>> public T apply(T e, Object o) { 
    return e.doSomething(o); 
    } 
} 

使用類型安全的包裝

另一種解決方案是創建一個綁定在一起這3個其他類,和移動交互調用的包裝。

abstract System<A extends Action, M extends Message, E extends Engine> { 
    abstract void apply(A action, E engine) { 
     engine.render(action.apply()) 
    } 
} 

或者讓包裝類獲取這3個實例並使用傳入的版本。這基本上是「允許任何足夠接近」的解決方案,並增加另一個班級來管理他們如何能夠和不能相互交流。

預製檢查

您也可以在建設提供參考投拋出一個錯誤,如果投的設置是無效的。

private final E dis = (E) this; 

這真的只是從會轉移問題上的編譯時間,有時運行時間,所以一般來講,不是一個安全/穩定的解決方案。


下一個解決方案是針對您的案例(使用我們的討論中的信息)。基本上,您想要在A類和B類可以繼承的抽象類中定義一個方法,但A和B不應該使用它們的基類互換。

只要使用多態和使用泛型只是作爲一種分離器

下面是一個使用多態,而不是MVCe的修改,使用泛型只能作爲一種類型類別的排他鎖機制。基本上,Type是一個語義接口,可以說是否語義上,這些類相互交談是有意義的。 (物理引擎和光引擎可能共享一些功能,但讓它們互換是沒有意義的。)

class test { 

    public static void main(String[] rawrs) { 
     RealEng re = new RealEng(); 
     RealAct ra = new RealAct(); 
     MockAct ma = new MockAct(); 
     ra.apply(re); 
     // Remove all code related to Type interface if next line should compile 
     ma.apply(re); // compile error here 
    } 

    static interface Type { 
    } 

    static interface Real extends Type { 
    }; 

    static interface Mock extends Type { 
    }; 

    static abstract class AbEng<T extends Type> { 

     final void func(AbAct<T> act) { 
      act.apply(this); // compile error here 
     } 

    } 

    static abstract class AbMes<T extends Type> { 

    } 

    static abstract class AbAct<T extends Type> { 

     abstract void apply(AbEng<T> e); 

    } 

    static class RealEng extends AbEng<Real> { 

    } 

    static class RealMes extends AbMes<Real> { 

    } 

    static class RealAct extends AbAct<Real> { 
     @Override 
     void apply(AbEng<Real> eng) { 
      System.out.println("applied!"); 
     } 
    } 

    static class MockAct extends AbAct<Mock> { 
     @Override 
     void apply(AbEng<Mock> eng) { 
      System.out.println("applied!"); 
     } 
    } 

} 
+0

爲了記錄,正是這個最優雅地解決問題的選項6。 – corsiKa

+0

無關:你爲我的問題提出的編輯 - 不太好。 A)我傾向於在放下版本1後快速編輯/更新我的答案 - 很多人都這樣做。所以如果你想改進一個答案 - 等待幾分鐘,以確保你沒有進入作者的方式,B)是的,我在那裏的代碼並不好 - 但我自己想到了。因此,請在刪除答案中的內容時小心。 – GhostCat

+0

不過,我認爲你的行爲是最好的,我很感激! – GhostCat

2

Java泛型中的遞歸類型參數通常很麻煩。

這裏的問題是,矛盾的是,你不能保證this引用E的實例;我們唯一知道的關於this的是它也延伸了Engine<M, A, E>但實際上並不是E

明顯的解決辦法是增加一個鑄((E)this),並且可能是一個可接受的解決方案,但你必須在合同(直通的javadoc或其他單證),其Engine擴展類必須分配E自己清楚。

另一種解決方案只是簡單地將這些方法簽名更改爲更靈活一點,而不是E接受任何擴展爲Engine<M, A, E>的引擎。

protected abstract M applyTo(AbstractEngine<M, A, E> engine, Object guarantee); 

還考慮儘可能減少類型參數的數量。例如Engine是否需要引用它自己的類型?它是否有任何接受或返回的方法以及必須是相同類型/類的引擎?

編輯

如果你想保持E類型參數applyTo另一種選擇是創建一個字段中鍵入AbstractEngine E這將是一個傳遞給申請。實際上這個字段會參考this,但是一旦它在施工中被安全地「鑄造」了。:

public class AbstractEngine<M extends ..., A extends ..., E extends ...> { 
    private final E engine; 

    protected AbstractEngine(final E engine) { 
     this.engine = Objects.requiresNonNull(engine); 
    } 
} 

public class MyEngine extends AbstractEngine<MyMessage, MyAction, MyEngine> { 
    public MyEngine() { 
     super(this); 
    } 
} 

這部作品的原因是,當我們宣佈MyEngine那麼編譯器不知道MyEngineE等等「投」是安全的。然後,AbstractEngine中的代碼可以在此後安全地使用鑄造值。

明顯的不便之處是參考this的額外字段,雖然在實踐中有點內存浪費可能可以忽略不計。

在這裏,我們增加了引擎可能指定代理引擎用於其方法調用apply的可能性。也許這可能是有用的......但是如果你真的想使這裏不可能使用第三個引擎,那麼你可以改變AbstractEngine構造函數中的代碼來比較傳遞的引擎和this,並且在運行時如果它們不是相同。

protected AbstractEngine(final E engine) { 
    if (engine != this) { 
     throw new IllegalArgumentException(); 
    } 
    this.engine = engine; 
} 

不幸的是,這不可能是檢查在編譯的時候......你可以做第二個最好的事情是讓你的代碼測試,以確認所有AbstractEngine擴展類符合,因此將在建設 - 部分失敗時間。

+0

引擎不需要它自己的類型,但它確實需要知道消息屬於THAT引擎,而不是其他類型的引擎。我試過其他的變化,最終總是需要所有三個三個。就像你說的,泛型總是麻煩。 – corsiKa

+0

至於你的解決方案,我現在正在鑄造它。但我不想。我寧願它按我想要的方式工作。至於你的第二種解決方案,更靈活的是與我想要的相反。我想要它僵化。 – corsiKa

+0

@corsika與THAT引擎,你的意思是引擎類型或實例?如果晚些時候你總是需要使用ref來引擎並在運行時進行驗證。 –