Skip to content

How to structure code to prevent incompatible capture of ? extends _?

I was wondering what is the best way to structure code to ensure compiler knows in very simple cases that passed variable is of correct type. I have created very simple code where class O has its execute method execute that accepts some kind of payload (that extends class P) and performs some kind of operation. The thing is that I do not know how to structure my code to ensure that compiler knows what kind of specific payload I am passing to execute method. First thing that came to my mind was to introduce method that would return classType of used payload just to came to realization that it is of no use because I am trying to pass some potentially different type of payload to execute method that accepts specific payload.

By the end of the day I could just use reflection but I was wondering how to solve this kind of problem without the use of reflection – whether there is some kind of pattern to solve this problem or some other way to structure my code,…

I know that this kind of simple in terms of printing payload can be solved by introducing method that would print desired payload,… but let’s say that I would like to do something more than just printing to console.

Thanks in advance!

import java.util.List;

class Scratch {

    public static abstract class O<I extends P> {
        public abstract void execute(I payload);
    }

    public static class O1 extends O<P1> {
        @Override
        public void execute(P1 payload) {
            System.out.println(payload.a);
        }
    }

    public static class O2 extends O<P2> {
        @Override
        public void execute(P2 payload) {
            System.out.println(payload.b);
        }
    }

    public static abstract class P {
        abstract Class<? extends P> getClassType();
    }

    public static class P1 extends P {
        public String a = "a";

        @Override
        Class<? extends P> getClassType() {
            return P1.class;
        }
    }

    public static class P2 extends P {
        public Integer b = 1;

        @Override
        Class<? extends P> getClassType() {
            return P2.class;
        }
    }

    public static void main(String[] args) {
        List<O<? extends P>> os = List.of(new O1(), new O2());
        List<P> ps = List.of(new P1(), new P2());

        for (int i = 0; i < 2; i++) {
            var p = ps.get(i);
            // this line would prevent compilation
            os.get(i).execute(p);
            // this line would also prevent compilation
            os.get(i).execute(p.getClassType().cast(p));
        }
    }
}

Answer

The problem with your current code is that you have a list of “any Os” and a list of “any Ps” that aren’t related in any way as far as the compiler is concerned. You seem to want to tell the compiler, that actually, os.get(i).execute accepts the same type as ps.get(i) for any valid index i.

To do that, you need to create another class that “groups” an O and a P together, then you can create one list containing these groups:

public static class OPGroup<PType extends P> {
    private final O<? super PType> o;
    private final PType p;

    public OPGroup(O<? super PType> o, PType p) {
        this.o = o;
        this.p = p;
    }

    public O<? super PType> getO() {
        return o;
    }

    public PType getP() {
        return p;
    }
}
List<OPGroup<?>> ops = List.of(
    new OPGroup<>(new O1(), new P1()),
    new OPGroup<>(new O2(), new P2())
);

for (int i = 0; i < 2; i++) {
    var op = ops.get(i);
    execute(op);
}

where execute can just be a static method declared anywhere you like:

// you can also make this an instance method of "OPGroup"
public static <PType extends P> void execute(OPGroup<PType> group) {
    group.getO().execute(group.getP());
}

The purpose of this is to capture the wildcard in OPGroup<?>.

Anyway, if you can’t introduce a new class for some reason, then your code is inherently unsafe. The contents of the lists can be literally any P and any O, so os.get(i).execute(p); could very well fail. If you are willing to accept that, you can do an unsafe (unchecked) cast:

((O<? super P>)os.get(i)).execute(p);