簡體   English   中英

Java 協作泛型類:我們可以避免未經檢查的強制轉換嗎?

[英]Java cooperating generic classes: can we avoid unchecked cast?

我有兩個抽象的泛型類。 他們合作,因此相互依賴。 偶爾一個需要通過this其他。 我試圖找到一種類型安全的方法來做到這一點:

public abstract class AbstractA<T extends AbstractB<? extends AbstractA<T>>> {

    protected void foo() {
        T aB = createB();
        aB.setA(this);
    }

    /** factory method */
    abstract public T createB();
    
}

public abstract class AbstractB<T extends AbstractA<? extends AbstractB<T>>> {

    private T theA;
    
    @SuppressWarnings("unchecked")
    public void setA(AbstractA<? extends AbstractB<?>> theA) { // dreamed of parameter list (T theA)
        // Unchecked cast from AbstractA<capture#1-of ? extends AbstractB<?>> to T
        this.theA = (T) theA;
    }
    
    protected T getA() {
        return theA;
    }
    
}

我的問題是我是否能找到一種更簡潔的方法來避免AbstractB.setA()的未經檢查的強制轉換。 我曾希望將它聲明為setA(T theA) ,但隨后對它的調用將無法編譯:類型 AbstractB<capture#1-of 中的方法 setA(capture#1-of ? extends AbstractA<T>) ? extends AbstractA<T>> 不適用於參數 (AbstractA<T>)。 我仍在努力理解編譯器是否應該足夠了解以允許它。

我在想我的問題可能與Java 泛型編譯錯誤中討論的問題有關- The method method(Class<capture#1-of ? extends Interface>) in the type <type> is not available for the arguments 我的未經檢查的演員陣容的靈感來自那里。 我喜歡 Tom Hawtin 的回答 - 解決問題,但我還沒有找到將其應用於我的情況的方法。

我的用戶將聲明具體的子類並實例化一個ConcreteA和任意數量的ConcreteB

public class ConcreteA extends AbstractA<ConcreteB> {

    @Override
    public ConcreteB createB() {
        return new ConcreteB();
    }
    
    public void concreteAMethod() {
        // ...
    }
    
}

public class ConcreteB extends AbstractB<ConcreteA> {

    public void bar() {
        ConcreteA a = getA();
        a.concreteAMethod();
    }
    
}

class AbstractA<T extends AbstractB<? extends AbstractA<T>>>看起來有點復雜;我以為我需要它讓具體的子類知道彼此的確切類型,但顯然它沒有給我。)

如果我理解正確,這應該會創建您想要的綁定。

class Demo {

    public static void main(String[] args) {
        ConcreteA a = new ConcreteA();
        ConcreteB b = new ConcreteB();
        a.foo(b);
        b = (ConcreteB) a.getB();
    }
}

abstract class AbstractA<T extends AbstractB<?>>{

    private AbstractB<?> b;

    public AbstractB<?> getB(){
        return b;
    }

    void foo(AbstractB<?> aB) {
        b = aB;
        aB.bar(this);
    }
}

abstract class AbstractB<T extends AbstractA<?>> {

    private AbstractA<?> a;

    public AbstractA<?> getA(){
        return a;
    }

    public void bar(AbstractA<?> theA) {
        a = theA;
        theA.foo(this);
    }
}

class ConcreteA extends AbstractA<ConcreteB>{

}

class ConcreteB extends AbstractB<ConcreteA>{

}

我想這就是你自己的結局。 我無法將演員表移除到 ConcreteB,getB() 根本無法確定它所持有的類型。 我現在明白為什么你的聲明中有多個通用語句。 :)

如果您願意,請繼續搜索,如果找到,請發布您自己的答案,我很樂意看到它。

我希望解決您的一半問題對任何事情都很重要。 ;)

我想我現在明白了為什么我不能在AbstractB聲明public void setA(T theA)然后在foo() aB.setA(this)其稱為aB.setA(this) foo() 假設我們有:

class IntermediateConcreteA extends AbstractA<ConcreteB> {

    @Override
    public ConcreteB createB() {
        return new ConcreteB();
    }

}

class SubConcreteA1 extends IntermediateConcreteA {}

class SubConcreteA2 extends IntermediateConcreteA {}

class ConcreteB extends AbstractB<SubConcreteA2> {}

現在,如果我有一個SubConcreteA1並調用它的foo() ,那么createB()將返回一個可以作為AbstractB<SubConcreteA2>傳遞但不能作為AbstractB<SubConcreteA1>傳遞的AbstractB<SubConcreteA1> 因此它的setA()不應該接受this作為參數。 畢竟編譯器錯誤消息是合乎邏輯的。

每個抽象類將使用兩個類型參數進行參數化,一個用於 A 的實際具體類,另一個用於 B 的實際具體類:

public abstract class AbstractA<A extends AbstractA<A,B>, B extends AbstractB<A,B>> {

    protected void foo() {
        B aB = createB();
        aB.setA(getThis());
    }

    abstract public A getThis();
    abstract public B createB();

}

public abstract class AbstractB<A extends AbstractA<A,B>, B extends AbstractB<A,B>> {

    private A theA;

    public void setA(A theA) {
        this.theA = theA;
    }

    protected A getA() {
        return theA;
    }

}

public class ConcreteA extends AbstractA<ConcreteA, ConcreteB> {

    @Override
    public ConcreteA getThis() {
        return this;
    }

    @Override
    public ConcreteB createB() {
        return new ConcreteB();
    }

    public void concreteAMethod() {
        // ...
    }

}

public class ConcreteB extends AbstractB<ConcreteA, ConcreteB> {

    public void bar() {
        ConcreteA a = getA();
        a.concreteAMethod();
    }

}

一個工廠可以解決它:

public abstract class AbstractA {

    public void abstractAMethod() {
        // ...
    }

}

public abstract class AbstractB<A> {

    private A theA;

    public void setA(A theA) {
        this.theA = theA;
    }

    protected A getA() {
        return theA;
    }

}

public abstract class AbstractFactory<A extends AbstractA, B extends AbstractB<A>> {

    private A theA = createA();

    public A getA() {
        return theA ;
    }

    public B getNextB() {
        B newB = createB();
        newB.setA(theA);
        return newB;
    }

    protected abstract A createA();
    protected abstract B createB();
}

現在用戶可以去:

public class ConcreteA extends AbstractA {

    public void concreteAMethod() {
        // ...
    }

}

public class ConcreteB extends AbstractB<ConcreteA> {

    public void bar() {
        ConcreteA a = getA();
        a.abstractAMethod();
        a.concreteAMethod();
    }

}

public class ConcreteFactory extends AbstractFactory<ConcreteA, ConcreteB> {

    @Override
    protected ConcreteA createA() {
        return new ConcreteA();
    }

    @Override
    protected ConcreteB createB() {
        return new ConcreteB();
    }

}

我不認為這是抽象工廠模式的典型應用,不過……

@Chris Wohlert,我確實放棄了我的生產代碼,因為我認為工廠矯枉過正,但我​​不能放棄理論問題。

我開始意識到我的問題真的來自將兩個不屬於一起的概念塞進 AbstractA/ConcreteA 層次結構中。 盡管對很多人來說可能並不有趣,但我發布此見解有兩個原因:(1)我覺得我欠 Chris Wohlert 我自己找到的答案(2)更重要的是,我很樂意激勵其他面臨類似棘手問題的人泛型問題從更高的層次上審查您的設計,而不僅僅是解決泛型和/或類轉換問題。 它當然幫助了我。 演員表/泛型問題表明更基本的東西不太正確。

public abstract class AbstractA {

    public void foo() {
        AbstractB aB = createB();
        aB.setA(this);
    }

    /** factory method */
    abstract public AbstractB createB();

}

public abstract class AbstractB {

    private AbstractA theA;

    public void setA(AbstractA theA) {
        this.theA = theA;
    }

    // methods that use theA

}

沒有泛型,也沒有類轉換。 將不屬於 A 類層次結構的東西取出到 ConcreteC 中(沒有 AbstractC):

public class Client {

    public void putTheActTogether() {
        ConcreteC theC = new ConcreteC();

        // the concrete A
        AbstractA theA = new AbstractA() {
            @Override
            public AbstractB createB() {
                return new ConcreteB(theC);
            }
        };
        // call methods in theA
    }

}

public class ConcreteB extends AbstractB {

    private final ConcreteC c;

    public ConcreteB(ConcreteC c) {
        super();
        this.c = c;
    }

    public void bar() {
        c.concreteCMethod();
    }

}

public class ConcreteC {

    public void concreteCMethod() { // was concreteAMethod(); moved and renamed
        // ...
    }

}

客戶端需要比以前多幾行。 在我的實際代碼中,我需要在 AbstractA 和 ConcreteC 中復制最后一個字段,但這樣做是有意義的。 總而言之,我認為對於其他純粹而簡單的設計來說,這是一個低廉的價格。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM