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

Древовидный ад: Могу ли я построить TypeLiteral <Set <T>> с использованием дженериков?

Единственный способ, с помощью которого я мог использовать нижеприведенный общий метод, - передать лишний TypeLiteral<Set<T>> параметр. Я считаю, что это можно было бы программировать с помощью другого параметра, но не могу понять, как это сделать.

protected <T> Key<Set<T>> bindMultibinder(
 TypeLiteral<Set<T>> superClassSet, TypeLiteral<T> superClass) {
   final Key<Set<T>> multibinderKey = Key.get(superClassSet, randomAnnotation);
   return multibinderKey;
}

Клиентский код выглядит следующим образом:

bindMultibinder(new TypeLiteral<Set<A<B>>>(){}, new TypeLiteral<A<B>>(){});

Где A и B - интерфейсы.

Если я попробую следующее (удаление параметра TypeLiteral<Set<T>> superClassSet), я получаю ошибку выполнения java.util.Set<T> cannot be used as a key; It is not fully specified..

protected <T> Key<Set<T>> bindMultibinder(TypeLiteral<T> superClass) {
   final Key<Set<T>> multibinderKey = Key.get(
    new TypeLiteral<Set<T>>() {}, randomAnnotation);
   return multibinderKey;
}
4b9b3361

Ответ 1

Полностью указанный означает, что значения всех параметров типа известны. Построение полностью указанного TypeLiteral<Set<T>> из TypeLiteral<T> представляется невозможным с использованием общедоступного API Guice. В частности, TypeLiteral имеет только два конструктора. Первое:

/**
 * Constructs a new type literal. Derives represented class from type
 * parameter.
 *
 * <p>Clients create an empty anonymous subclass. Doing so embeds the type
 * parameter in the anonymous class type hierarchy so we can reconstitute it
 * at runtime despite erasure.
 */
@SuppressWarnings("unchecked")
protected TypeLiteral() {
  this.type = getSuperclassTypeParameter(getClass());
  this.rawType = (Class<? super T>) MoreTypes.getRawType(type);
  this.hashCode = type.hashCode();
}

Этот конструктор пытается вывести значения параметров типа из класса TypeLiteral runtime. Это даст полностью определенный тип, только если класс среды выполнения определяет параметр типа. Однако, поскольку все экземпляры родового класса имеют один и тот же класс времени выполнения (т.е. new HashSet<String>().getClass() == new HashSet<Integer>().getClass(), параметр типа известен только при создании экземпляра не общего подкласса TypeLiteral. То есть мы не можем повторно использовать то же объявление класса для разных значений T, но должно определять новый класс для каждого T. Это довольно громоздко, как демонстрирует ответ alf.

Это оставляет нас с другим конструктором, который более полезен, но не является частью общедоступного API:

/**
 * Unsafe. Constructs a type literal manually.
 */
@SuppressWarnings("unchecked")
TypeLiteral(Type type) {
  this.type = canonicalize(checkNotNull(type, "type"));
  this.rawType = (Class<? super T>) MoreTypes.getRawType(this.type);
  this.hashCode = this.type.hashCode();
}

Мы можем использовать этот конструктор следующим образом:

package com.google.inject;

import java.util.Set;

import com.google.inject.internal.MoreTypes;

public class Types {
    public static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> lit) {
        return new TypeLiteral<Set<T>>(new MoreTypes.ParameterizedTypeImpl(null, Set.class, lit.getType())); 
    }
}

TestCase:

public static void main(String[] args) {
    System.out.println(setOf(new TypeLiteral<String>() {}));
}

В идеальном мире Guice предложит публичный API для выполнения этого...

Ответ 2

Пожалуйста, простите меня, если вы уже знаете большую часть ответа: вам сложно сделать предположение на вашем уровне.

Причиной проблемы является стирание типа, как вы уже знаете. Чтобы избавиться от стирания стилей, Guice использует трюк с конкретными предками, как показано ниже:

class Trick<T> {
    T t;
}

public class GenericTest {
    public static void main(String[] args) {
        Trick<Set<String>> trick = new Trick<Set<String>>() {
        };

        // Prints "class org.acm.afilippov.GenericTest$1"
        System.out.println(trick.getClass());
        // Prints "org.acm.afilippov.Trick<java.util.Set<java.lang.String>>"
        System.out.println(trick.getClass().getGenericSuperclass());
    }
}

Точка - это когда вы создаете класс, который расширяет общий суперкласс и явно задает параметр типа, вам обычно нужно написать методы, которые принимают это очень специфическое тип, а подписи этих методов не могут быть стерты. В этом случае проблема не обсуждалась в FAQ, но компилятор сохраняет информацию о типе. В любом случае пользователям вашего класса необходимо будет знать точные типы, чтобы использовать методы.

Теперь ваша версия не имеет конкретного класса, унаследованного от TypeLiteral<Set<YourSpecificType>>, она имеет только TypeLiteral<Set<T>> - и то, где все это не удается.

Изменив мой маленький пример, это будет:

public class GenericTest {
    public static void main(String[] args) {
        tryMe(String.class);
    }

    private static <T> void tryMe(Class<T> clazz) {
        Trick<Set<T>> trick = new Trick<Set<T>>() {
        };

        // Prints "class org.acm.afilippov.GenericTest$1"
        System.out.println(trick.getClass());
        // Prints "org.acm.afilippov.Trick<java.util.Set<T>>"
        System.out.println(trick.getClass().getGenericSuperclass());
    }
}

Как вы можете видеть, наш GenericTest$1 уже не является конкретным: он все еще имеет параметр типа, а его конкретное значение здесь String теряется во время компиляции.

Конечно, вы можете этого избежать, но для этого вам нужно создать класс с определенным параметром типа, используемым для наследования, чтобы Guice смог обработать детали. Подождите немного, я попытаюсь привести пример.

Обновление: оказалось, что это ОЧЕНЬ длинный бит. Итак, вот обновленная версия для вас:

public class GenericTest {
    public static void main(String[] args) throws Exception {
        tryMe(String.class);
    }

    private static <T> void tryMe(Class<T> clazz) throws IllegalAccessException, InstantiationException {
        Class c = loadClass("org.acm.afilippov.ASMTrick", generateClass(clazz));

        Trick<Set<T>> trick = (Trick<Set<T>>) c.newInstance();

        // Prints "class org.acm.afilippov.ASMTrick"
        System.out.println(trick.getClass());
        // Prints "org.acm.afilippov.Trick<java.util.Set<java.lang.String>>"
        System.out.println(trick.getClass().getGenericSuperclass());
    }

    private static byte[] generateClass(Class<?> element) {
        ClassWriter cw = new ClassWriter(0);
        MethodVisitor mv;

        cw.visit(V1_6, ACC_FINAL + ACC_SUPER, "org/acm/afilippov/ASMTrick",
                "Lorg/acm/afilippov/Trick<Ljava/util/Set<L" + element.getName().replaceAll("\\.", "/") + ";>;>;",
                "org/acm/afilippov/Trick", null);

        {
            mv = cw.visitMethod(0, "<init>", "()V", null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "org/acm/afilippov/Trick", "<init>", "()V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }

    private static Class loadClass(String className, byte[] b) {
        //override classDefine (as it is protected) and define the class.
        Class clazz = null;
        try {
            ClassLoader loader = ClassLoader.getSystemClassLoader();
            Class cls = Class.forName("java.lang.ClassLoader");
            java.lang.reflect.Method method =
                    cls.getDeclaredMethod("defineClass", new Class[]{String.class, byte[].class, int.class, int.class});

            // protected method invocaton
            method.setAccessible(true);
            try {
                Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length)};
                clazz = (Class) method.invoke(loader, args);
            } finally {
                method.setAccessible(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        return clazz;
    }
}

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