getActualTypeArguments с родителем, который получает свой параметризованный тип от дочернего элемента

Я провел некоторое исследование перед публикацией, но без конкретного ответа.

Можно ли получить фактические аргументы типа для суперкласса, который получает свой параметризованный тип от своего дочернего элемента? Например..

GenericSubclass<T> extends GenericClass<T>

Путем создания экземпляра GenericSubclass как:

GenericSubclass<String> genericClass = new GenericSubclass<>();

и выполнение приведенного ниже возвращает "T"

Type type = ((ParameterizedType)this.getClass().
           getGenericSuperclass()).getActualTypeArguments()[0];

Но мне нужен java.lang.String. Любые комментарии приветствуются


person Dimitrios Posnakidis    schedule 17.07.2018    source источник
comment
Каков ваш конкретный вариант использования? Возможно, вы могли бы добиться этого без размышлений, например. передав Class<T> вашему GenericClass из подкласса   -  person Lino    schedule 17.07.2018
comment
Общее эмпирическое правило: если вам нужно использовать отражение, то, вероятно, у вас есть недостаток дизайна или вы слишком усложняете вещи   -  person Lino    schedule 17.07.2018
comment
Я знаю о таком решении, но если это возможно, я бы хотел избежать модификации конструктора, передавая Class‹T›.   -  person Dimitrios Posnakidis    schedule 17.07.2018
comment
Это, вероятно, невозможно из-за стирания типов java-дженериков. Любая информация о фактическом типе теряется во время выполнения.   -  person Lino    schedule 17.07.2018
comment
Спасибо @Лино. Я предполагаю, что в java все еще есть некоторые ограничения   -  person Dimitrios Posnakidis    schedule 17.07.2018
comment
Пожалуйста. Стирание типа — это одна из вещей, которая является просто ограничением в java, но обходной путь с Class<T>, вероятно, лучший способ, поскольку он не зависит от медленного отражения.   -  person Lino    schedule 17.07.2018
comment
@Lino, да, мне никогда не нравилось это правило, большая часть java-вещей просто лежит в огромной куче отражений.   -  person GotoFinal    schedule 18.07.2018


Ответы (1)


Я просто предполагаю, что любой, кто читает этот ответ, уже знает о стирании типа. Если нет, следуйте Введите Erasure< /em>, и я бы также рекомендовал прочитать вопросы и ответы стирание типа дженериков Java: когда и что бывает.

Объяснение стирания типа дает некоторую полезную информацию, но на самом деле оно не объясняет, что делает getActualTypeArguments() и почему код в вопросе не работает.


Для Class c c.getGenericSuperclass() эффективно получает тип в предложении extends c.

Так, например, если бы у нас было это (с использованием реального класса):

public class ArrayList<E> extends AbstractList<E> {...}

И мы сказали ArrayList.class.getGenericSuperclass(), мы получили параметризованный тип, представляющий AbstractList<E>. Опять же, это тип в предложении extends.

Если бы у нас было это:

ArrayList<Long> al = new ArrayList<Long>();

al.getClass() возвращает ArrayList.class, поэтому al.getClass().getGenericSuperclass() снова возвращает AbstractList<E>.

«Фактическим» аргументом типа для AbstractList<E> является переменная типа E, поэтому, если бы мы сказали:

Type actualTypeArgument =
    ((ParameterizedType)
        al.getClass()
          .getGenericSuperclass())
          .getActualTypeArguments() [0];

Тогда actualTypeArgument ссылается на переменную типа E, объявленную ArrayList. (И на самом деле это так, что actualTypeArgument == ArrayList.class.getTypeParameters()[0] верно.)


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

ArrayList<Long> al = new ArrayList<Long>() {};
//                                    Note ^^

Это неявно объявляет такой класс:

class AnonymousArrayList1 extends ArrayList<Long> {}

(За исключением того, что у анонимного класса нет имени. Кроме того, мы могли бы объявить подобный подкласс явно, вместо того, чтобы использовать анонимный класс.)

Теперь, когда мы вызываем al.getClass(), мы получаем AnonymousArrayList1.class, чей общий суперкласс — ArrayList<Long>. Теперь вы можете получить фактический аргумент типа ArrayList<Long>, то есть Long.

(Вот короткая программа, демонстрирующая все вышеперечисленное.)


Что касается разрешения уровня косвенности, то это возможно, но код более сложен. Вы также по-прежнему связаны приведенным выше правилом, согласно которому фактический класс должен быть предоставлен в качестве аргумента типа в предложении extends.

Вот пример этого, который позволяет любое количество промежуточных суперклассов:

package mcve;

import java.lang.reflect.*;

public abstract class KnowMyType<T> {
    public KnowMyType() {
        System.out.println(getMyType());
    }

    @SuppressWarnings("unchecked")
    private Class<T> getMyType() {
        try {
            return (Class<T>) findMyType(getClass(), null);
        } catch (RuntimeException x) {
            throw new IllegalArgumentException("illegal type argument", x);
        }
    }

    private Type findMyType(Class<?> plainClass, Type genericClass) {
        Class<?> plainSuper = plainClass.getSuperclass();
        Type genericSuper = plainClass.getGenericSuperclass();

        Type t;

        if (plainSuper == KnowMyType.class) {
            t = ((ParameterizedType) genericSuper).getActualTypeArguments()[0];
        } else {
            t = findMyType(plainSuper, genericSuper);
        }

        if (t instanceof TypeVariable<?>) {
            TypeVariable<?>[] vars = plainClass.getTypeParameters();

            for (int i = 0; i < vars.length; ++i) {
                if (t == vars[i]) {
                    t = ((ParameterizedType) genericClass).getActualTypeArguments()[i];
                    break;
                }
            }
        }

        return t;
    }
}

Это позволит вам сделать это:

class KnowMyTypeSub<T> extends KnowMyType<T> {}
new KnowMyTypeSub<Long>() {}; // constructor prints 'class java.lang.Long'

Гуава TypeToken также делает подобные вещи автоматически.

person Radiodef    schedule 17.07.2018