Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing generic subtype class information to superclass in Java

I've long used an idiom in Java for using the class information of a (non-abstract) class in the methods of its (generally abstract) ancestor class(es) (unfortunately I can't find the name of this pattern):

public abstract class Abstract<T extends Abstract<T>> {
    private final Class<T> subClass;

    protected Abstract(Class<T> subClass) {
        this.subClass = subClass;
    }

    protected T getSomethingElseWithSameType() {
        ....
    }
}

An example of a subclass thereof:

public class NonGeneric extends Abstract<NonGeneric> {
    public NonGeneric() {
        super(NonGeneric.class);
    }
}

However, I'm having trouble defining a subclass of Abstract which has its own generic parameters:

public class Generic<T> extends Abstract<Generic<T>> {
    public Generic() {
        super(Generic.class);
    }
}

This example is not compilable; likewise, it is not possible to specify the generic types using e.g. Generic<T>.class or even to use a wildcard like Generic<?>.

I also tried replacing the declaration of generic type T in the superclass to ? extends T, but that isn't compilable either.

Is there any way I can get this pattern to work with generic base classes?

like image 843
errantlinguist Avatar asked Oct 11 '13 07:10

errantlinguist


People also ask

Can superclass access subclass fields?

Does a superclass have access to the members of a subclass? Does a subclass have access to the members of a superclass? No, a superclass has no knowledge of its subclasses.

How do I restrict a generic type in Java?

Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.

Can generics be used with inheritance in several ways what are they?

Generics also provide type safety (ensuring that an operation is being performed on the right type of data before executing that operation). Hierarchical classifications are allowed by Inheritance. Superclass is a class that is inherited. The subclass is a class that does inherit.

Can superclass inherit from subclass?

A subclass inherits all the members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses, but the constructor of the superclass can be invoked from the subclass.


2 Answers

The "pattern" (idiom) of passing an instance of Class<T> (typically to the constructor) is using Class Literals as Runtime-Type Tokens, and is used to keep a runtime reference to the generic type, which is otherwise erased.

The solution is firstly to change the token class bound to:

Class<? extends T>

and then to put a similar requirement on your generic subclass as you did with your super class; have the concrete class pass a type token, but you can type it properly as a parameter:

These classes compile without casts or warnings:

public abstract class Abstract<T extends Abstract<T>> {
    private final Class<? extends T> subClass;

    protected Abstract(Class<? extends T> subClass) {
        this.subClass = subClass;
    }
}

public class NonGeneric extends Abstract<NonGeneric> {
    public NonGeneric() {
        super(NonGeneric.class);
    }
}

public class Generic<T> extends Abstract<Generic<T>> {
    public Generic(Class<? extends Generic<T>> clazz) {
        super(clazz);
    }
}

And finally at the concrete class, if you declare the usage as its own class, it doesn't require a cast anywhere:

public class IntegerGeneric extends Generic<Integer> {
    public IntegerGeneric() {
        super(IntegerGeneric.class);
    }
}

I haven't figured out how to create an instance of Generic (anonymous or not) without a cast:

// can someone fill in the parameters without a cast?
new Generic<Integer>(???);     // typed direct instance
new Generic<Integer>(???) { }; // anonymous

I don't think it's possible, but I welcome being shown otherwise.

like image 119
Bohemian Avatar answered Oct 12 '22 01:10

Bohemian


The major problem you have got here is, there is no class literal for concrete parameterized type. And that makes sense, since parameterized types don't have any runtime type information. So, you can only have class literal with raw types, in this case Generic.class.

Reference:

  • Java Generics FAQs
    • Why is there no class literal for concrete parameterized types? :

Well, that's fine, but Generic.class gives you a Class<Generic> which is not compatible with Class<Generic<T>>. A workaround is to find a way to convert it to Class<Generic<T>>, but that too you can't do directly. You would have to add an intermediate cast to Class<?>, which represents the family of all the instantiation of Class. And then downcast to Class<Generic<T>>, which will remove the compiler error, though you will an unchecked cast warning. You can annotate the constructor with @SuppressWarnings("unchecked") to remove the warning.

class Generic<T> extends Abstract<Generic<T>> {     
    public Generic() {
        super((Class<Generic<T>>)(Class<?>)Generic.class);
    }
}
like image 33
Rohit Jain Avatar answered Oct 12 '22 01:10

Rohit Jain