Наследование вложенных дженериков
У меня есть следующие классы:
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;
}
}
и класс вопрос связан с:
class PickField<T> extends Field<Pick<T>> {
public PickField(Class<Pick<T>> type) {
super(type);
}
}
Теперь это похоже на компилятор. К сожалению, я не знаю/не понимаю, как создать новый экземпляр PickField
, например. для String
выбирает.
Это - конечно - не работает:
new PickField<String>(Pick.class)
Это не разрешено (думаю, я понимаю почему):
new PickField<String>(Pick<String>.class)
Итак, как это сделать? Или весь подход как-то "пахнет"?
Ответы
Ответ 1
Я думаю, что PickField
должен быть параметризован только с экземплярами Pick
.
Поэтому делать это должно быть хорошо:
class PickField<T extends Pick<T>> extends Field<T> {
public PickField(Class<T> c) {
super(c);
}
}
Затем вы можете просто создать его с помощью:
PickField<SomeSpecificPick> instance = new PickField<>(SomeSpecificPick.class);
где SomeSpecificPick
определяется как:
public class SomeSpecificPick extends Pick<SomeSpecificPick> {
public SomeSpecificPick(SomeSpecificPick value, Class<SomeSpecificPick> type) {
super(value, type);
}
}
Дополнительная информация (связанная с темой):
Ответ 2
Здесь есть различные проблемы.
Во-первых, как вы указываете, вы не можете получить класс параметризованного типа во время компиляции, так как только один класс скомпилирован для общих типов, а не по одному параметру заданного типа (например, idiom Pick<String>.class
не компилируется и на самом деле не имеет смысла).
Опять же, как вы отметили, параметрирование конструктора PickField<String>
с Pick.class
только не будет компилироваться снова, так как подписи не совпадают.
Вы можете использовать идиому runtime, чтобы вывести правильный параметр Pick<T>
, но это создает еще одну проблему: из-за типа erasure ваш аргумент type для T
будет неизвестен во время выполнения.
Таким образом, вы можете параметризовать вызов своего конструктора путем явного литья следующим образом:
new PickField<String>(
(Class<Pick<String>>)new Pick<String>("", String.class).getClass()
);
..., который будет компилироваться с предупреждением "непроверенного броска" (Type safety: Unchecked cast from Class<capture#1-of ? extends Pick> to Class<Pick<String>>
).
Возможно, вопрос реальный, почему вам нужно знать значение type
в вашем классе Pick
.
Ответ 3
Есть способ, но не совсем хороший.
Вам нужно создать такой метод:
public static <I, O extends I> O toGeneric(I input) {
return (O) input;
}
Затем вы создаете объект:
new PickField<String>(toGeneric(Pick.class));
Как я уже сказал, на самом деле не очень хороший способ, поскольку вы в основном просто лжете компилятору, но он работает.
Ответ 4
Чтобы передать информацию дженериков в качестве аргумента, Class<T>
недостаточно. Для этого вам нужна дополнительная сила. См. в этой статье, где объясняется, что такое маркер супер-типа.
Короче говоря, если у вас есть следующий класс:
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;
}
}
Вы можете использовать его для хранения информации типа дженериков, например Pick<String>.class
(что является незаконным). Хитрость заключается в использовании информации об универсальном типе суперкласса, доступной через Class.getGenericSuperclass()
и ParameterizedType.getActualTypeArguments()
.
Я немного изменил ваши классы Pick
, Field
и PickField
, так что они используют токен супер-типа вместо Class<T>
. См. Измененный код:
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);
}
}
И вот пример использования:
TypeToken<Pick<String>> type = new TypeToken<Pick<String>>() {};
PickField<String> pickField = new PickField<>(type);
Поскольку класс TypeToken
является абстрактным, вам необходимо подклассифицировать его (это объясняет {}
в конце его объявления.