Подтвердить что ты не робот

Наследование вложенных дженериков

У меня есть следующие классы:

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)

Итак, как это сделать? Или весь подход как-то "пахнет"?

4b9b3361

Ответ 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 является абстрактным, вам необходимо подклассифицировать его (это объясняет {} в конце его объявления.