Java cooperating generic classes: can we avoid unchecked cast?

浪尽此生 提交于 2021-02-07 11:13:33

问题


Edit: Sorry; eager to give you a minimal example, I didn’t provide enough information about the requirements in my first version of this question.

I have two abstract generic classes. They cooperate and hence depend on each other. Occasionally one needs to pass this to the other. I am trying to find a type safe way to do 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;
    }

}

My question is whether I can find a cleaner way so I avoid the unchecked cast in AbstractB.setA(). I had hoped to declare it setA(T theA), but then the call to it won’t compile: The method setA(capture#1-of ? extends AbstractA<T>) in the type AbstractB<capture#1-of ? extends AbstractA<T>> is not applicable for the arguments (AbstractA<T>). I am still struggling to understand whether the compiler should know enough to allow it or not.

I was thinking my problem may be related to the one discussed in Java generics compilation error - The method method(Class<capture#1-of ? extends Interface>) in the type <type> is not applicable for the arguments. My unchecked cast was inspired from there. I liked the reply by Tom Hawtin - tackling, but I have not found a way to apply it to my situation.

My user will declare concrete subclasses and instantiate one ConcreteA and any number of ConcreteBs:

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>>> looks a bit complicated; I thought I needed it for concrete subclasses to know each other’s exact types, but apparently it doesn’t give me that.)


回答1:


If I've understood you correctly, this should create the binding you want.

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>{

}

I think this is what you ended up at yourself. I am not able to remove the cast to ConcreteB, getB() simply cannot be sure of the type it is holding. I now see why you had multiple generic statements in your declaration. :)

If you're up for it, continue searching, and post your own answer if you find one, I'd love to see it.

I hope solving half your problem counts for anything. ;)




回答2:


I think I got it now why I cannot declare public void setA(T theA) in AbstractB and then call it as aB.setA(this) in foo(). Suppose we had:

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

Now if I have a SubConcreteA1 and call its foo(), then createB() will return an object that can pass as an AbstractB<SubConcreteA2> but cannot pass as an AbstractB<SubConcreteA1>. Therefore its setA() shouldn’t accept this as an argument. The compiler error message is logical after all.




回答3:


Each abstract class would be parameterized with two type parameters, one for the actual concrete class of A, and one for the actual concrete class of 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();
    }

}



回答4:


A factory can solve it:

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

Now the user can go:

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

}

I don’t think it’s a typical application of the abstract factory pattern, though …

@Chris Wohlert, I did give up in my production code since I considered the factory overkill, but I could not let go of the theoretical question.




回答5:


I have come to realize that my problem really came out of stuffing two concepts into the AbstractA/ConcreteA hierarchy that didn’t belong together. Though maybe not interesting to very many, I am posting this insight for two reasons: (1) I feel I owe Chris Wohlert the answer I have found myself (2) more importantly, I’d love to inspire anyone else facing a similar tricky generics issue to review your design from a higher level than just solving the generics and/or class cast issue. It certainly helped me. The cast/generics problem was a sign that something more fundamental was not quite right.

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

}

No generics and no class cast. Taking out the stuff that didn’t belong in the A class hierarchy into ConcreteC (with no 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
        // ...
    }

}

The client needs a few more lines than before. In my real-world code I needed to duplicate one final field in AbstractA and ConcreteC, but it made sense to do. All in all I consider it a low price for a design that is otherwise pure and simple.



来源:https://stackoverflow.com/questions/35013179/java-cooperating-generic-classes-can-we-avoid-unchecked-cast

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!