Reputation: 4385
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"?
Upvotes: 18
Views: 1489
Reputation: 34460
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.
Upvotes: 0
Reputation: 62864
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):
Upvotes: 16
Reputation: 51
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.
Upvotes: 1
Reputation: 48404
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.
Upvotes: 7