Передача обобщенной информации о классе подтипа в суперкласс в Java

Я уже давно использовал идиому в Java для использования информации о классе (не абстрактного) класса в методах его (обычно абстрактного) класса предков (к сожалению, я не могу найти имя этого шаблон):

public abstract class Abstract<T extends Abstract<T>> {
    private final Class<T> subClass;

    protected Abstract(Class<T> subClass) {
        this.subClass = subClass;
    }

    protected T getSomethingElseWithSameType() {
        ....
    }
}

Пример подкласса:

public class NonGeneric extends Abstract<NonGeneric> {
    public NonGeneric() {
        super(NonGeneric.class);
    }
}

Однако мне трудно определить подкласс Abstract, который имеет свои собственные общие параметры:

public class Generic<T> extends Abstract<Generic<T>> {
    public Generic() {
        super(Generic.class);
    }
}

Этот пример не компилируется; аналогичным образом, невозможно указать общие типы, например, Generic<T>.class или даже использовать подстановочный знак, например Generic<?>.

Я также попытался заменить объявление универсального типа T в суперклассе на ? extends T, но это тоже не компилируется.

Есть ли способ заставить этот шаблон работать с базовыми базовыми классами?

Ответ 1

"Шаблон" (идиома) передачи экземпляра Class<T> (обычно для конструктора) использует Class Literals в качестве маркеров типа Runtime, и используется, чтобы сохранить ссылку на runtime на общий тип, который иначе удаляется.

Решение, во-первых, должно изменить класс маркера, связанный с:

Class<? extends T>

а затем поставить аналогичное требование в ваш общий подкласс, как это было с вашим суперклассом; иметь конкретный класс, передать токен типа, но вы можете ввести его правильно как параметр:

Эти классы компилируются без бросков или предупреждений:

public abstract class Abstract<T extends Abstract<T>> {
    private final Class<? extends T> subClass;

    protected Abstract(Class<? extends T> subClass) {
        this.subClass = subClass;
    }
}

public class NonGeneric extends Abstract<NonGeneric> {
    public NonGeneric() {
        super(NonGeneric.class);
    }
}

public class Generic<T> extends Abstract<Generic<T>> {
    public Generic(Class<? extends Generic<T>> clazz) {
        super(clazz);
    }
}

И, наконец, в конкретном классе, если вы объявляете использование его собственным классом, он не требует приведения в любом месте:

public class IntegerGeneric extends Generic<Integer> {
    public IntegerGeneric() {
        super(IntegerGeneric.class);
    }
}

Я не понял, как создать экземпляр Generic (анонимно или нет) без приведения:

// can someone fill in the parameters without a cast?
new Generic<Integer>(???);     // typed direct instance
new Generic<Integer>(???) { }; // anonymous

Я не думаю, что это возможно, но я приветствую, что меня показывают иначе.

Ответ 2

Основная проблема, с которой вы столкнулись, - это не литерал класса для конкретного параметризованного типа. И это имеет смысл, поскольку параметризованные типы не имеют никакой информации о времени выполнения. Таким образом, вы можете иметь только литерал класса с необработанными типами, в данном случае Generic.class.

Ссылка:

Ну, это прекрасно, но Generic.class дает вам Class<Generic>, который несовместим с Class<Generic<T>>. Обходной путь - найти способ конвертировать его в Class<Generic<T>>, но это тоже вы не можете сделать напрямую. Вам нужно будет добавить промежуточный листинг в Class<?>, который представляет семейство всех экземпляров Class. И затем опускаем до Class<Generic<T>>, что приведет к удалению ошибки компилятора, хотя вы получите предупреждение о немедленном броске. Вы можете аннотировать конструктор с помощью @SuppressWarnings("unchecked"), чтобы удалить предупреждение.

class Generic<T> extends Abstract<Generic<T>> {     
    public Generic() {
        super((Class<Generic<T>>)(Class<?>)Generic.class);
    }
}

Ответ 3

В аргументе Class<T> subClass нет необходимости. Изменение:

protected Abstract(Class<T> subClass) {
    this.subClass = subClass;
}

в

protected Abstract(Class subClass) {
    this.subClass = subClass;
}

и все будет компилироваться.