Skip to content
Advertisement

Java cooperating generic classes: can we avoid unchecked cast?

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.)

Advertisement

Answer

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.

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement