gpt4 book ai didi

Java 反射转换方法 ReturnType

转载 作者:行者123 更新时间:2023-12-04 15:03:37 24 4
gpt4 key购买 nike

为了提供一些背景知识,我正在创建一个小型依赖项注入(inject)器,但在将方法调用转换回它们的返回类型时遇到了问题。一个最小的例子是:

public class MinimalExample {
public static <T> void invokeMethod(Class<T> aClass) throws ReflectiveOperationException {
Optional<Method> myOptMethod = resolveMethod(aClass);
if (myOptMethod.isPresent()) {
Method myMethod = myOptMethod.get();
Object myInstance = myMethod.invoke(myMethod);
doSomething(myMethod.getReturnType(), myMethod.getReturnType().cast(myInstance));
}
}

private static <T> Optional<Method> resolveMethod(Class<T> aClass) {
return Stream.of(aClass.getMethods())
.filter(aMethod -> Modifier.isStatic(aMethod.getModifiers()))
.filter(aMethod -> aMethod.getParameterCount() == 0)
.findAny();
}

private static <U> void doSomething(Class<U> aClass, U anInstance) {
// E.g. Map aClass to anInstance.
}
}

这里的问题是doSomething需要用 Class<U>, U 调用, 但目前正在使用 Class<capture of ?>, capture of ? 调用它由于 invoke方法的通配符返回类型。

我可以改变 doSomethingdoSomething(Class<?> aClass, Object anInstance)但后来我失去了类型安全,这不一定是唯一调用该方法的地方。

我的问题是:为什么编译器不能推断它们具有相同的底层类型,U ,给定显式类型转换?


编辑(2021 年 3 月 9 日):

我通过反编译字节码的自由来了解为什么 rzwitserloot's helper method确实解决了类型问题。由于类型删除,它们似乎是相同的调用。我猜想编译器不够聪明,无法在转换后推断它们是相同的捕获类型,需要类型绑定(bind)来提供帮助。

我添加了以下功能

private static <U> void doSomethingWithTypeBinding(Class<U> aClass, Object anObject) {
doSomething(aClass, aClass.cast(anObject));
}

private static void doSomethingUnsafe(Class<?> aClass, Object anInstance) {}

我现在分别从第 15 行和第 16 行调用

doSomethingWithTypeBinding(myMethod.getReturnType(), myInstance);
doSomethingUnsafe(myMethod.getReturnType(), myMethod.getReturnType().cast(myInstance));

产生以下字节码:

L5
LINENUMBER 15 L5
ALOAD 2
INVOKEVIRTUAL java/lang/reflect/Method.getReturnType ()Ljava/lang/Class;
ALOAD 3
INVOKESTATIC depinjection/handspun/services/MinimalExample.doSomethingWithTypeBinding (Ljava/lang/Class;Ljava/lang/Object;)V
L6
LINENUMBER 16 L6
ALOAD 2
INVOKEVIRTUAL java/lang/reflect/Method.getReturnType ()Ljava/lang/Class;
ALOAD 2
INVOKEVIRTUAL java/lang/reflect/Method.getReturnType ()Ljava/lang/Class;
ALOAD 3
INVOKEVIRTUAL java/lang/Class.cast (Ljava/lang/Object;)Ljava/lang/Object;
INVOKESTATIC depinjection/handspun/services/MinimalExample.doSomethingUnsafe (Ljava/lang/Class;Ljava/lang/Object;)V

// access flags 0xA
// signature <U:Ljava/lang/Object;>(Ljava/lang/Class<TU;>;TU;)V
// declaration: void doSomething<U>(java.lang.Class<U>, U)
private static doSomething(Ljava/lang/Class;Ljava/lang/Object;)V
L0
LINENUMBER 30 L0
RETURN
L1
LOCALVARIABLE aClass Ljava/lang/Class; L0 L1 0
// signature Ljava/lang/Class<TU;>;
// declaration: aClass extends java.lang.Class<U>
LOCALVARIABLE anInstance Ljava/lang/Object; L0 L1 1
// signature TU;
// declaration: anInstance extends U
MAXSTACK = 0
MAXLOCALS = 2

// access flags 0xA
// signature <U:Ljava/lang/Object;>(Ljava/lang/Class<TU;>;Ljava/lang/Object;)V
// declaration: void doSomethingWithTypeBinding<U>(java.lang.Class<U>, java.lang.Object)
private static doSomethingWithTypeBinding(Ljava/lang/Class;Ljava/lang/Object;)V
L0
LINENUMBER 33 L0
ALOAD 0
ALOAD 0
ALOAD 1
INVOKEVIRTUAL java/lang/Class.cast (Ljava/lang/Object;)Ljava/lang/Object;
INVOKESTATIC depinjection/handspun/services/MinimalExample.doSomething (Ljava/lang/Class;Ljava/lang/Object;)V
L1
LINENUMBER 34 L1
RETURN
L2
LOCALVARIABLE aClass Ljava/lang/Class; L0 L2 0
// signature Ljava/lang/Class<TU;>;
// declaration: aClass extends java.lang.Class<U>
LOCALVARIABLE anObject Ljava/lang/Object; L0 L2 1
MAXSTACK = 3
MAXLOCALS = 2

// access flags 0xA
// signature (Ljava/lang/Class<*>;Ljava/lang/Object;)V
// declaration: void doSomethingUnsafe(java.lang.Class<?>, java.lang.Object)
private static doSomethingUnsafe(Ljava/lang/Class;Ljava/lang/Object;)V
L0
LINENUMBER 37 L0
RETURN
L1
LOCALVARIABLE aClass Ljava/lang/Class; L0 L1 0
// signature Ljava/lang/Class<*>;
// declaration: aClass extends java.lang.Class<?>
LOCALVARIABLE anInstance Ljava/lang/Object; L0 L1 1
MAXSTACK = 0
MAXLOCALS = 2

我们可以看到INVOKEVIRTUAL直接转换到INVOKESTATIC由于它们的运行时类型删除,看起来完全相同。


编辑(2021 年 3 月 12 日):

@霍尔格pointed out in the comments , Method#getReturnType返回 Class<?> .因为它是通配符,所以从编译器的角度来看,该方法无法保证后续方法调用返回具有相同捕获类型的类。

最佳答案

类型变量是编译器想象的产物:它们在编译后无法存活(删除*)。最好将它们视为链接事物。仅在一个地方使用的类型变量永远是完全无用的;一旦它们出现在两个地方,这就很有用了:它允许您将类型的多种用法链接在一起,表示出现次数相同。比如可以绑定(bind).add(Obj thingToAdd)的参数类型, 和 .get(int idx) 的返回类型一起为java.util.List .

在这里,您要链接 Class<X>myMethod.getReturnType连同 myInstance多变的。正如您所意识到的,这是不可能的,因为编译器不知道它们最终将成为同一类型。但是,通过调用 cast() Class<X>的方法| ,我们围绕该部分进行工作。

但您仍然需要一些类型变量作为将事物联系在一起的工具,而您没有。 ?类似于一次性使用类型变量; Class<?> cls和 myMethod.getReturnType().cast(myInstance)` 是“不同的”?s:是的,您的眼球可以看出它是同一类型,但 java 不能。你需要一个类型变量。当然可以介绍一个:

private static <X> helper(Class<X> x, Object myInstance) {
doSomething(x, x.cast(myInstance));
}

将此方法添加到您的代码中并调用此方法,而不是调用 doSomething . <X>我们在这里创建的用于将结果联系在一起。

*) 当然,它们保留在公共(public)签名中,但在其他任何地方,在运行时它们都被删除了。

您可以在此处使用替代选项:doSomething方法是私有(private)的,因此您可以完全控制它。因此,您可以只在其中移动类型转换,这可以解决所有问题,或者,您可以这样写:

/** precondition: o must be an instance of c */
private static void doSomething(Class<?> c, Object o) {
}

因为是私有(private)方法,引入前置条件就好了。您可以完全控制调用此方法的所有代码。如果你真的想要,你可以添加一个运行时检查(顶部的 if (!c.isInstanceof(o)) throw new IllegalArgumentException("o not instance of c");),但是否值得这样做是 java 生态系统中针对私有(private)方法的公开辩论。通常判决是不这样做,或者使用 assert它的关键字。

注意:这有一些糟糕的空/可选处理。如果找不到要解决的方法,你..只是默默地什么都不做?这就是 NPE 更好的原因:至少粗心的编码会导致异常,而不是徒劳的追逐。

关于Java 反射转换方法 ReturnType,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66552153/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com