Skip to content

Generics visibility

public class Outer<A, B, C> {        

    // constructor generic hides class generic C            
    public <C> Outer(C arg){}

    // method generic hides class generic A    
    public <A> A doSmth() {} 

    // inner class hides outer generic (can't see)
    public class Inner<A, T> {

            // method generic hides outer class generic
            public <B> B doSmth2() {}        
    }
}

public class Root extends Outer<String, Integer, Long> {

    // field with inner class
    Inner<Boolean, String> field1;

    // field with inner class, but with direct outer generics definition    
    Outer<String, String, String>.Inner<Boolean, String> field2;
} 
GenericsContext context = GenericsResolver.resolve(Root.class);

Type generics

// no generics declared on Root class
context.genericsMap() == [:]

// context.toString():
// class Root    <-- current
//   extends Outer<String, Integer, Long>

// Outer type generics, resolved from Root hierarchy
context.type(Outer.class)
    .genericsMap() == ["A": String, "B": Integer, "C": Long]

// context.type(Outer.class).toString():
// class Root
//   extends Outer<String, Integer, Long>    <-- current

Constructor generics

// switch context to constructor
context = context.constructor(Outer.class.getConstructor(Object.class))
// generics map shows generics of context type (Outer)!
context.genericsMap() == ["A": String, "B": Integer, "C": Long]
context.constructorGenericsMap() == ["C": Object]
// but actually visible generics are (note that constructor generic C override):
context.visibleGenericsMap() == ["A": String, "B": Integer, "C": Object]

// context.toString():
// class Root
//   extends Outer<String, Integer, Long>
//     Outer(Object)    <-- current

Method generics

// switch context to method
context = context.method(Outer.getMethod("doSmth"))

// generics map shows generics of context type (Outer)!
context.genericsMap() == ["A": String, "B": Integer, "C": Long]
context.methodGenericsMap() == ["A": Object]
// but actually visible generics are (note that method generic A override):
context.visibleGenericsMap() == ["A": Object, "B": Integer, "C": Long]

// context.toString():
// class Root
//   extends Outer<String, Integer, Long>
//     Object doSmth()    <-- current

Outer class generics

// build context for type of field 1 (using outer generics knowledge)
context = context.fieldType(Root.getDeclaredField("field1"))

context.genericsMap() == ["A": Boolean, "T": String]
// inner class could use outer generics, so visible outer generics are also stored,
// but not A. as it could never be used in context of this inner class
context.ownerGenericsMap() == ["B": Integer, "C": Long]
context.visibleGenericsMap() == ["A": Boolean, "T": String, "B": Integer, "C": Long]

// context.toString()
// class Outer<Object, Integer, Long>.Inner<Boolean, String>  resolved in context of Root    <-- current

Note

Tere was an assumption that as Root context contains Outer class (outer for Inner), then inner class was created inside it and so root generics could be used. It is not always the case, but in most cases inner class is used inside of outer.

Different case, when outer class generics are explicitly declared:

// build context for type of field 2 (where outer generics are explicitly declared)
// note that first we go from previous field1 context into root context (by using rootContext())
// and then resolve second field context 
context = context.rootContext().fieldType(Root.getDeclaredField("field2"))

context.genericsMap() == ["A": Boolean, "T": String]
// outer class generics taken directly from field declaration, but note that A 
// is not counted as not reachable for inner class
context.ownerGenericsMap() == ["B": String, "C": String]
context.visibleGenericsMap() == ["A": Boolean, "T": String, "B": Integer, "C": Long]

// context.toString() (first Object is not a mistake! A was ignored in outer class):
// class Outer<Object, String, String>.Inner<Boolean, String>  resolved in context of Root    <-- current

Resulted inlying context can do everything root context can (context was just resolved with extra information)

// navigate to method in inner class
context = context.method(Outer.Inner.getMethod("doSmth2"))

context.genericsMap() == ["A": Boolean, "T": String]
// owner generics always shown as is even when actually overridden (for consistency)
context.ownerGenericsMap() == ["B": String, "C": String]
context.methodGenericsMap() == ["B": Object]
// note that outer generic B is not visible (because of method generic)
context.visibleGenericsMap() == ["A": Boolean, "T": String, "B": Object, "C": String]

// context.toString():
// class Outer<Object, String, String>.Inner<Boolean, String>  resolved in context of Root
//   Object doSmth2()    <-- current