java - ジェネリクス - ジェネリック地獄: ジェネリックを使用して Typeリテラル<Set<T>> を構築できますか?



generics types (2)

完全指定は、すべての型パラメータの値がわかっていることを意味します。 TypeLiteral<T>から完全に指定されたTypeLiteral<Set<T>>TypeLiteral<T>することは、Guice公開APIを使用すると不可能に見えます。 具体的には、 TypeLiteralは2つのコンストラクタしかあり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()するため、typeパラメータはつまり、我々は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())); 
    }
}

テストケース:

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

完璧な世界では、Guiceはこれを達成するための公開APIを提供します...

https://ffff65535.com

以下の一般的なメソッドを動作させるための唯一の方法は、一見冗長な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パラメータをTypeLiteral<Set<T>> superClassSetすると)、 java.util.Set<T> cannot be used as a key; It is not fully specified. 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;
}

あなたはすでに答えの大部分を知っていれば私を許してください:あなたのレベルで前提を作るのは難しいです。

あなたがすでに知っているように、問題の理由はタイプ消去です。 型の消去を取り除くために、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 (ここでは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;
    }
}

ご覧のように、タイプ情報が保存されるようになりました。 私は、このアプローチではあまりにも痛いことがあるので、このアプローチは使用されないと考えています。





guice