有没有更好(更安全)的方法来获取参数化类型的class?

Is there a better (more safe) way to get the class of the parameterized type?

我想创建一个 return 参数化类型 class 的方法。

考虑 interface:

private static interface MyInterface<T> {
    void run(T parameter);
}

和一种实现:

private static class MyInterfaceImplString implements MyInterface<String> {
    @Override
    public void run(String parameter) {     }
}

现在,我想将 MyInterfaceImplString.class 传递给一个方法,此方法将 return String.class

我正在打印周围的东西,我可以看到那里的信息,但我就是无法获取它。或者至少以更安全的方式。

public class TypesTest {

    public static void main(String[] args) {
        Class<?> genericParameter1 = getGenericParamaterType(MyInterfaceImplVoid.class);
        System.out.println(genericParameter1); //expect Void here

        Class<?> genericParameter2 = getGenericParamaterType(MyInterfaceImplString.class);
        System.out.println(genericParameter2); //expect String here
    }

    private static Class<?> getGenericParamaterType(Class<? extends MyInterface<?>> clazz) {
        for (Method m : clazz.getMethods()) {
            if ("run".equals(m.getName()) && m.getParameterCount() == 1) {
                System.out.println(TypeLiteral.get(clazz).getParameterTypes(m));
            }
        }

        return null;
    }

    private static class MyInterfaceImplVoid implements MyInterface<Void> {

        @Override
        public void run(Void parameter) {           }
    }

    private static class MyInterfaceImplString implements MyInterface<String> {

        @Override
        public void run(String parameter) {         }

    }

    private static interface MyInterface<T> {
        void run(T parameter);
    }
}

忽略方法中的null return值。我只是想打印东西,看看我得到什么,然后 return 它。但是,我当前的实现似乎有点不正统,因为 class 可能有不止一个 run 方法与 MyInterface.

无关

a XY problem 的情况下,我希望能够识别这些实现中的哪些包含 extends Employee(比方说)的参数化类型。我在给定 HourlyPaidEmployeeMonthlyPaidEmployee 的情况下间接调用接口的 run 方法。因此,如果底层实现是 MyInterface<Employee>,我可以注入我的实际雇员(按月或按小时支付)。但是如果实现是MyInterface<HourlyEmployee>,我就不能注入月付了。因此,获取参数化类型的 class 可以帮助我了解可以将哪些类型的员工安全地注入 run 方法。

我在 class 路径中有 java-8 和 Guice 依赖项,其中包含 guava 依赖项。

编辑: @Glorfindel 在他的回答中提出了很好的观点。但是如果实现看起来像:

private static class MyInterfaceImplVoid implements MyInterface<Void> {

    @Override
    public void run(Void parameter) {
    }

    public void run(Integer par) {
    }
}

我不知道引用接口的方法是什么,因此他的回答可能 return 我 Integer.cass.

如果我替换

System.out.println(TypeLiteral.get(clazz).getParameterTypes(m));

return m.getParameterTypes()[0];

我得到以下输出:

class java.lang.Void  
class java.lang.String

不需要 Guice/Guava,普通的 Java 似乎工作正常。我不知道另一个问题的好的解决方案;似乎反射甚至无法检测到一种方法是覆盖而另一种不是。如果您对实现 类 有足够的控制权,我建议您简单地使用另一个名称而不是 run 作为其他方法。

您的 T 未绑定。

这意味着此接口的每个实现,甚至您的 MyInterfaceImplVoid 都将 有一个 run(Object) 方法,即使您没有在all - 在这种情况下,它会将参数转换为 Void,并将调用向前传递给 run(Void)。因此,如果您想确保调用接口方法,请始终调用 run(Object),并发送一个正确类型的对象(如通过@Glorfindel 的回答所示)。

请注意,'trick' 计算边界通常是一个糟糕的计划。当我写这个的时候你打算做什么class:

class DynamicMyInterface<Z> implements MyInterface<Z> {
    private Class<Z> forcedType;

    public DynamicMyInterface(Class<Z> type) {
        this.forcedType = type;
    }

    public void run(Z in) {
        forcedType.cast(in);
        ....
    }
}

你进入 .getGenericType() 的短途旅行 永远不会 能够弄清楚它是什么,因为已删除。你不会比 Z extends Object.

更进一步

我找到了一种获取参数化类型 class 的方法,使用这种“丑陋的”-“搞什么鬼”的方法:

private static Class<?> getGenericParamaterType(Class<? extends MyInterface<?>> clazz) {
    for (TypeToken<?> typeToken : TypeToken.of(clazz).getTypes()) {
        Class<?> rawType = typeToken.getRawType();
        if (rawType == MyInterface.class) {
            TypeLiteral<?> typeLiteral = TypeLiteral.get(typeToken.getType());
            for (Method method : rawType.getMethods()) {
                if ("run".equals(method.getName())) {
                    TypeLiteral<?> typeLiteral2 = typeLiteral.getParameterTypes(method).get(0);
                    return typeLiteral2.getRawType();
                }
            }
        }
    }
    return null;
}

它完全符合我的要求。但是,我不确定它的性能。另外,我真的认为 Guava 提供了一种更简洁的方法来实现我想要的。另一方面,我找不到它。

下面是显示它有效的完整示例。即使在实现实现多个 run 方法的复杂情况下,甚至在不实现该方法的抽象 classes 中也是如此。

public class TypesTest {

    public static void main(String[] args) {
        Class<?> genericParameter1 = getGenericParamaterType(MyInterfaceImplVoid.class);
        System.out.println(genericParameter1); //expect Void here

        Class<?> genericParameter2 = getGenericParamaterType(MyInterfaceImplString.class);
        System.out.println(genericParameter2); //expect String here

        Class<?> genericParameter3 = getGenericParamaterType(AbstractImpl.class);
        System.out.println(genericParameter3); //expect Integer here
    }

    private static Class<?> getGenericParamaterType(Class<? extends MyInterface<?>> clazz) {
        for (TypeToken<?> typeToken : TypeToken.of(clazz).getTypes()) {
            Class<?> rawType = typeToken.getRawType();
            if (rawType == MyInterface.class) {
                TypeLiteral<?> typeLiteral = TypeLiteral.get(typeToken.getType());
                for (Method method : rawType.getMethods()) {
                    if ("run".equals(method.getName())) {
                        TypeLiteral<?> typeLiteral2 = typeLiteral.getParameterTypes(method).get(0);
                        return typeLiteral2.getRawType();
                    }
                }
            }
        }
        return null;
    }

    private static class MyInterfaceImplVoid extends JPanel implements MyInterface<Void>, Runnable {

        @Override
        public void run(Void parameter) { 
        }

        public void run(Integer par) {          }

        public void run(String s) {         }

        @Override
        public void someOtherMethod() {         }

        @Override
        public void run() {         }

    }

    private static abstract class AbstractImpl implements MyInterface<Integer> {

    }

    private static class MyInterfaceImplString implements MyInterface<String> {

        @Override
        public void run(String parameter) {         }

        @Override
        public void someOtherMethod() {         }

    }

    private static interface MyInterface<T> {
        void run(T parameter);

        void someOtherMethod();
    }
}

我不会将此答案标记为已接受,因为我很确定有更方便的方法。

当您想要找出 type 的实际类型参数时,您不应该处理方法。您可以像使用

一样简单地为场景实现它
public class TypesTest {
    public static void main(String[] args) {
        Type genericParameter1 = getGenericParameterType(MyInterfaceImplVoid.class);
        System.out.println(genericParameter1+", "+(genericParameter1==Void.class));

        Type genericParameter2 = getGenericParameterType(MyInterfaceImplString.class);
        System.out.println(genericParameter2+", "+(genericParameter2==String.class));
    }

    private static Type getGenericParameterType(Class<? extends MyInterface<?>> clazz) {
        for(Type interfaceType: clazz.getGenericInterfaces()) {
            if(interfaceType == clazz)
                throw new IllegalArgumentException("raw implementation");
            if(interfaceType instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType)interfaceType;
                if(pt.getRawType() == MyInterface.class)
                    return pt.getActualTypeArguments()[0];
            }
        }
        throw new UnsupportedOperationException("not implemented directly");
    }
}
class java.lang.Void, true
class java.lang.String, true

但请注意 return 类型是 Type,而不是 class,因为类型参数不能保证是 Class(可具体化的类型或原始类型)。它本身可以是参数化类型,也可以是类型变量。

考虑这样一个案例

abstract class InTheMiddle<T> implements MyInterface<T> {}
class Implementation extends InTheMiddle<String> {
    @Override
    public void run(String parameter) {
    }
}

这可以通过遍历类型层次结构、查询 declared type variables 并在它们出现在您要解析的目标类型变量中时将它们与实际类型参数匹配来解决。

但请注意以下情况也是可能的:

abstract class Outer<T> {
    class Inner implements MyInterface<T> {
        @Override
        public void run(T parameter) {
        }
    }
}
class SubclassOfOuter extends Outer<Integer> {
}
SubclassOfOuter outer = new SubclassOfOuter();
MyInterface<Integer> object = outer.new Inner();

这将需要遍历类型层次结构和词法范围的工件(除了外部 classes,实现 classes 可以是放置的通用方法的本地 classes在一个通用的 class 中,它可能是另一个方法的本地…)。

然后,由于类型擦除,有些构造根本无法在运行时查询:

class GenericImpl<X> implements MyInterface<X> {
    @Override
    public void run(X parameter) {
    }
}
GenericImpl<String> a = new GenericImpl<>();
GenericImpl<Integer> b = new GenericImpl<>();

MyInterface<Thread> another = t -> {};

因此,在使代码依赖于在运行时确定实际类型参数的能力之前请三思。