18

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"?

JDC
  • 4,247
  • 5
  • 31
  • 74
  • What do you want to do with it? There are a couple of patterns for doing one or two specific things with T, but if you need to do a bunch of stuff where the class of T actually matters, then the code probably does smell. – Morgen Feb 01 '16 at 15:35

4 Answers4

16

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):

Community
  • 1
  • 1
Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
  • Thanks for the answer, but there is an error I think inside `SomeSpecificPick`? If the type parameter is the class itself this will give some strange circular reference, right? – JDC Feb 01 '16 at 14:47
  • No, there's no error. And this won't give circular references - this is the so-called self-reference type and the JDK actually defines few such, for example `Integer extends Comparable`, etc. So just try the snippet out and get familiar with the links I shared. :) – Konstantin Yovkov Feb 01 '16 at 14:52
  • Ok, seems to work after trying it out ;). One additional bonus question: what if the `PickField` would look like this: `PickField> extends Field>` so that I have a list of picks? I can't manage to get the `super(type)` correct. – JDC Feb 02 '16 at 15:16
  • 1
    In such case, you will need a second type parameter. Should be something like: `PickField, L extends List> extends Field` – Konstantin Yovkov Feb 02 '16 at 15:32
7

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.

Mena
  • 47,782
  • 11
  • 87
  • 106
  • I do not need to know the type of the field, but the type of the pick. The field would be of type `Pick` and the value of the `Pick` would be of type `String`. Edited the question for clarification. – JDC Feb 01 '16 at 13:24
  • 2
    @JDC Semantics IMO. The "smell" here as you mention it, is that you want to know the type your generic class `Pick` has been parametrized with, within the instance scope. This generally defiles the concept behind generics, and likely indicates a broader issue with your design. – Mena Feb 01 '16 at 13:26
1

There is a way, but is not exactly a good one. You need to create a method like this:

public static <I, O extends I> O toGeneric(I input) {
    return (O) input;
}

Then, you create the object:

new PickField<String>(toGeneric(Pick.class));

Like I said, not really a good way, since you basically just lie to the compiler, but it works.

ZetQ
  • 51
  • 4
  • 1
    This is equivalent to explicitly casting to `(Class>)` and will generate another `unchecked cast` warning. No to mention the fact that, since it's presented as some general utility method, it might be misused **a lot** before someone starts finding bugs... – Mena Feb 01 '16 at 13:19
0

In order to pass generics information as an argument, Class<T> is not enough. You need some extra power to achive that. Please see this article, where it is explained what a super type token is.

In short, if you have the following class:

public abstract class TypeToken<T> {

    protected final Type type;

    protected TypeToken() {
        Type superClass = getClass().getGenericSuperclass();
        this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }

    public Type getType() {
        return this.type;
    }
}

You could use it to store generics type information, such as Pick<String>.class (which is illegal). The trick is to use the superclass' generic type information, which is accessible via the Class.getGenericSuperclass() and ParameterizedType.getActualTypeArguments() methods.

I have slightly modified your Pick, Field and PickField classes, so that they use a super type token instead of a Class<t>. Please see the modified code:

class Field<T> {

    private final TypeToken<T> type;

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

class Pick<V> {

    private final V value;

    private final TypeToken<V> type;

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

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

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

And here is a sample usage:

TypeToken<Pick<String>> type = new TypeToken<Pick<String>>() {};
PickField<String> pickField = new PickField<>(type);

As TypeToken class is abstract, you need to subclass it (this explains the {} at the end of its declaration.

fps
  • 33,623
  • 8
  • 55
  • 110