Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Generics Inheritance

I have the following classes:

class Field<T> {

  private final Class<T> type;

  public Field(Class<T> type) {
    this.type = type;
  }
}

class Pick<V> {
  private final V value;
  private final Class<V> type;

  public Pick(V value, Class<V> type) {
    this.value = value;
    this.type = type;
  }
}

and the class the question is related to:

class PickField<T> extends Field<Pick<T>> {

  public PickField(Class<Pick<T>> type) {
    super(type);
  }
}

Now this seems to be accepted by the compiler. Unfortunately I do not know/understand how I could create a new instance of PickField, e.g. for String picks.

This does - of course - not work:
new PickField<String>(Pick.class)

This is not allowed (I think I understand why):
new PickField<String>(Pick<String>.class)

So how to do it? Or does the whole approach somehow "smell"?

like image 437
JDC Avatar asked Feb 01 '16 12:02

JDC


2 Answers

I think PickField should be parameterized with Pick instances only.

So doing this should be fine:

class PickField<T extends Pick<T>> extends Field<T> {

    public PickField(Class<T> c) {
        super(c);
    }
}

Then, you could just instantiate it with:

PickField<SomeSpecificPick> instance = new PickField<>(SomeSpecificPick.class);

where SomeSpecificPick is defined as:

public class SomeSpecificPick extends Pick<SomeSpecificPick> {

    public SomeSpecificPick(SomeSpecificPick value, Class<SomeSpecificPick> type) {
        super(value, type);
    }
}

More info (related with the topic):

  • Why is there no class literal for concrete parameterized types?
  • How to get a class instance of generics type T?
  • Erasure of Generic Types
like image 144
Konstantin Yovkov Avatar answered Sep 20 '22 01:09

Konstantin Yovkov


There are various issues here.

Firstly as you point out, you cannot obtain the class of a parametrized type at compile time, since only one class is compiled for generic types, not one per given type parameter (e.g. the Pick<String>.class idiom does not compile, and doesn't actually make sense).

Again as you mention, parametrizing the PickField<String> constructor with Pick.class only will not compile again, as the signatures aren't matched.

You could use a runtime idiom to infer the right Pick<T> parameter, but that creates another problem: due to to type erasure, your type argument for T will be unknown at runtime.

As such, you can parametrize your constructor invocation by explicitly casting, as follows:

new PickField<String>(
    (Class<Pick<String>>)new Pick<String>("", String.class).getClass()
);

... which will compile with an "unchecked cast" warning (Type safety: Unchecked cast from Class<capture#1-of ? extends Pick> to Class<Pick<String>>).

The real question is likely why do you need to know the value of type in your Pick class.

like image 29
Mena Avatar answered Sep 22 '22 01:09

Mena