Generics Hell: Могу ли я построить TypeLiteralиспользуя дженерики? - PullRequest
18 голосов
/ 07 января 2012

Единственный способ заставить работать приведенный ниже обобщенный метод - передать кажущийся избыточным параметр 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;
}

Ответы [ 2 ]

15 голосов
/ 08 января 2012

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

Причина проблемы - стирание типа, как вы уже знаете.Чтобы избавиться от стирания типа, 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;
    }
}

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

12 голосов
/ 08 января 2012

Полностью указано означает, что значения всех параметров типа известны.Создание полностью указанного 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's 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.Это даст полностью указанный тип только в том случае, если класс времени выполнения определяет параметр типа.Тем не менее, поскольку все экземпляры универсального класса совместно используют один и тот же класс времени выполнения (то есть 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 для этого ...

...