Why Does the following code with Cyclic Generics n

2020-06-13 05:28发布

问题:

Following is my code

class A<B2 extends B, A2 extends A<B2, A2>> {
    C<B2, A2> c;

    void test() {
        c.acceptParameterOfTypeA(this);
    }

}

class B {

}

class C<B2 extends B, A2 extends A<B2, A2>> {
    void acceptParameterOfTypeA(A2 a) {

    }
}

The error occurs at c.acceptParameterOfTypeA(this);.

The error is

The method acceptParameterOfTypeA(A2) in the type C is not applicable for the arguments (A)

From what I see, the method acceptParameterOfTypeA expects a parameter of type A, and this at the line giving the error is of type A.

What am I doing wrong? How to fix this problem?

If its important, I'm using Java8

回答1:

I will again rename your classes, so that everything is more readable. So, let's have:

public class First<T extends Second, U extends First<T, U>> {
    Third<T, U> c;

    void test() {
        c.acceptParameterOfTypeA(this);
    }

}

class Second {

}

public class Third<X extends Second, Y extends First<X, Y>> {
    void acceptParameterOfTypeA(Y a) {

    }
}

From the definition of the c member (Third<T, U>), we can conclude that c will expose a method with this signature:

void acceptParameterOfTypeA(U a) { .. }

What is U? U is a sub-type of First<T, U>.

But if U can be substituted with First after type-erasure, this will mean that First extends First<T, First>, which is not true, because U stands for sub-type of First, which is parameterized with some concrete sub-types of Second and First.

In order to get to U, you can apply the so-called Get This approach.

First, since you need U, which is a sub-type of First, but can't get it from First, you can introduce an abstract method that returns it:

abstract class First<T extends Second, U extends First<T, U>> {
    Third<T, U> c;

    void test() {
        c.acceptParameterOfTypeA(getU());
    }

    abstract U getU();

}

Then, implement a sample sub-class of First, called Fourth, which extends First with some concrete types for T and U, for example:

class Fourth extends First<Second, Fourth> {
    Fourth getU() {
        return this;
    }
}

In the getU() method, just do return this; as this will return the correct substitute for U in the super-class.

More info:

  • What is the "getThis" trick?
  • Strategy Pattern with Generics


回答2:

Simply put, c.acceptParameterOfTypeA() accepts A2. this has type A<B2, A2>, which is not known to extend A2. It's only known that A2 extends A<B2, A2>.



回答3:

Based on kocko's answer, the original question had the same solution:

public class Main {
    abstract class A<A2 extends A<A2, B2>, B2 extends B<A2, B2>> {
        B2 b;

        void test() {
            b.testMethod(getThis()); //getThis() instead of this;
        }

        abstract A2 getThis();
    }

    class B<A2 extends A<A2, B2>, B2 extends B<A2, B2>> {
        void testMethod(A2 a) {

        }
    }

    public void execute() {

    }

    public static void main(String[] args) {
        Main main = new Main();
        main.execute();
    }
}


回答4:

We can simplify it by removing the B part which doesn't contribute to the problem -

class A<T extends A<T>>
{
    void test(C<T> c)
    {
        c.acceptParameterOfTypeA(this);  // ERROR
    }
}

class C<T extends A<T>>
{
    void acceptParameterOfTypeA(T a) {}
}

this type is A<T>; and the question is whether A<T> <: T, which is false.

What we really want here is "self type", so that this type is T. We don't have that in Java.

Usually we use T extends A<T> for "self type"; but it is flawed and inadequate in some use cases.

One remedy for that is T getThis(), as kocko mentioned.

You could simply do a brute cast (T)this, which is obviously correct by the intention of T.


My preferred approach is to simply omit the bound of T, and rename it to This to indicate the purpose of the type variable. Casting (This)this looks obviously correct. See my other post. That approach usually works; but it doesn't work here, since C would need This to have the bound A<This>. The deeper problem is A and C depends on each other, which might be redesigned.