Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the "cannot select from a type variable"

I have the following classes:

public abstract class A {

    public String att;

    public static abstract class Builder<T extends A> {

        public T a;

        public abstract T build();

        public T.Builder setAtt(String a) {
            this.a.att = a;
            return this;
        }
    }
}


public class A1 extends A {

    public static class Builder extends A.Builder<A1> {

        public Builder() {
            this.a = new A1();
        }

        public A1 build() {
            return this.a;
        }
    }
} 


public class A2 extends A {

    public String subAtt;

    public static class Builder extends A.Builder<A2> {

        public Builder() {
            this.a = new A2();
        }

        public A2 build() {
            return this.a;
        }

        public Builder setSubAtt(String subAtt) {
            a.subAtt = subAtt;
            return this;
        }
    }
}

Why do I get "cannot select from a type variable" error on the A.setAtt?

Type erasure shouldn't apply. T is either A1 or A2 but this is known at compile-time.

How should I return the subclass builder then? My main objective is to be able to do setter after setter mixing subclass and superclass.

like image 682
Gust Avatar asked Feb 10 '15 14:02

Gust


2 Answers

This cannot possibly work:

T.Builder

Since T is a type variable and therefore not bound to any specific type, you cannot expect the compiler to resolve a nested type of an unknown type.

T is either A1 or A2 but this is known at compile-time.

This assumption is wrong: imagine you offer your code as a JAR and another developer uses it, introducing his own subclass of A. If Java code was compiled under the assumption of a closed world, Maven would be a most useless service.

The assumption is also irrelevant: you would need a quite more sophisticated type system to work out what general type T.Builder conforms to.

like image 116
Marko Topolnik Avatar answered Oct 22 '22 06:10

Marko Topolnik


As Marko explained, you cannot simply use 'T.Builder' and expect the compiler to determine the nested class of an unknown type.

What you can do is to force a subclass of Builder to identify itself:

public class A
{

    public String att;    

    public static abstract class Builder<T extends A, U extends Builder<T, U>>
    {

        public T a;

        public abstract T build();

        public U setAtt(String a)
        {
            this.a.att = a;
            return getBuilder();
        }

        public abstract U getBuilder();        


    }        
}


public class A1 extends A
{

    public static class Builder extends A.Builder<A1, A1.Builder>
    {

        public Builder()
        {
            this.a = new A1();
        }

        @Override
        public A1 build()
        {
            return this.a;
        }


        @Override
        public A1.Builder getBuilder()
        {
            return this;
        }
    }
}

This way a Statement like

A1 build = new A1.Builder().setAtt("x").build();   

would work.

like image 3
flo Avatar answered Oct 22 '22 06:10

flo