从另一个 class 到 getter/setter 的 MethodHandle 给出了 NoSuchFieldError
MethodHandle to a getter/setter from another class gives a NoSuchFieldError
假设我有一个简单的 javabean MyPerson
,带有 name
getter 和 setter:
public class MyPerson {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
现在我 运行 这个主要代码只是获取和设置 name
字段:
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle getterMethodHandle = lookup.findGetter(MyPerson.class, "name", String.class);
MethodHandle setterMethodHandle = lookup.findSetter(MyPerson.class, "name", String.class);
MyPerson a = new MyPerson();
a.setName("Batman");
System.out.println("Name from getterMethodHandle: " + getterMethodHandle.invoke(a));
setterMethodHandle.invoke(a, "Robin");
System.out.println("Name after setterMethodHandle: " + a.getName());
}
如果我在 class MyPerson
上添加 main()
方法,我会得到我期望的结果:
Name from getterMethodHandle: Batman
Name after setterMethodHandle: Robin
如果我在另一个包class上添加相同的main()
方法,我会得到这个奇怪的错误:
Exception in thread "main" java.lang.NoSuchFieldException: no such field: batman.other.MyMain.name/java.lang.String/getField
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:875)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:990)
at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1373)
at java.lang.invoke.MethodHandles$Lookup.findGetter(MethodHandles.java:1022)
at batman.other.MyMain.main(MyMain.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.NoSuchFieldError: name
at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:962)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:987)
为什么? MyPerson
的 getter/setters 是 public,所以没有理由 MyMain
不应该使用它们,即使是通过 MethodHandles。
使用 JDK 8 和 source/target 级别 java 8.
你在混合东西。您需要的是:
MethodHandle getterMethodHandle = lookup.findVirtual(MyPerson.class,
"getName", MethodType.methodType(String.class));
MethodHandle setterMethodHandle = lookup.findVirtual(MyPerson.class,
"setName", MethodType.methodType(void.class, String.class));
findGetter
和 findSetter
方法不会像 Java Beans 中那样尝试查找某些 getXXX 或 setXXX 方法。不要忘记方法句柄是非常底层的东西。这些方法实际上构建了一个方法句柄,它不指向现有方法,而只是设置具有给定名称的字段。使用 findGetter
,您不需要在 class 中使用实际的 getter 方法,但您必须可以直接访问该字段。如果您想像 getName
一样使用 getter 方法,您仍然需要调用 findVirtual
。
一般来说,方法句柄比仅仅引用方法更强大。例如,您可以将方法句柄绑定到一个或多个参数,这样您就不需要在调用时指定它们。
如果您查看 Lookup 的文档,您会看到:
Each method handle created by a factory method is the functional equivalent of a particular bytecode behavior
例如 getter 查找:
lookup.findGetter(C.class,"f",FT.class)
翻译成:
(T) this.f;
因此出现奇怪的错误 NoSuchFieldError: name
,因为您从外部引用了私有字段。对于 getters/setters 你应该选择 findVirtual
.
假设我有一个简单的 javabean MyPerson
,带有 name
getter 和 setter:
public class MyPerson {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
现在我 运行 这个主要代码只是获取和设置 name
字段:
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle getterMethodHandle = lookup.findGetter(MyPerson.class, "name", String.class);
MethodHandle setterMethodHandle = lookup.findSetter(MyPerson.class, "name", String.class);
MyPerson a = new MyPerson();
a.setName("Batman");
System.out.println("Name from getterMethodHandle: " + getterMethodHandle.invoke(a));
setterMethodHandle.invoke(a, "Robin");
System.out.println("Name after setterMethodHandle: " + a.getName());
}
如果我在 class MyPerson
上添加 main()
方法,我会得到我期望的结果:
Name from getterMethodHandle: Batman
Name after setterMethodHandle: Robin
如果我在另一个包class上添加相同的main()
方法,我会得到这个奇怪的错误:
Exception in thread "main" java.lang.NoSuchFieldException: no such field: batman.other.MyMain.name/java.lang.String/getField
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:875)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:990)
at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1373)
at java.lang.invoke.MethodHandles$Lookup.findGetter(MethodHandles.java:1022)
at batman.other.MyMain.main(MyMain.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.NoSuchFieldError: name
at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:962)
at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:987)
为什么? MyPerson
的 getter/setters 是 public,所以没有理由 MyMain
不应该使用它们,即使是通过 MethodHandles。
使用 JDK 8 和 source/target 级别 java 8.
你在混合东西。您需要的是:
MethodHandle getterMethodHandle = lookup.findVirtual(MyPerson.class,
"getName", MethodType.methodType(String.class));
MethodHandle setterMethodHandle = lookup.findVirtual(MyPerson.class,
"setName", MethodType.methodType(void.class, String.class));
findGetter
和 findSetter
方法不会像 Java Beans 中那样尝试查找某些 getXXX 或 setXXX 方法。不要忘记方法句柄是非常底层的东西。这些方法实际上构建了一个方法句柄,它不指向现有方法,而只是设置具有给定名称的字段。使用 findGetter
,您不需要在 class 中使用实际的 getter 方法,但您必须可以直接访问该字段。如果您想像 getName
一样使用 getter 方法,您仍然需要调用 findVirtual
。
一般来说,方法句柄比仅仅引用方法更强大。例如,您可以将方法句柄绑定到一个或多个参数,这样您就不需要在调用时指定它们。
如果您查看 Lookup 的文档,您会看到:
Each method handle created by a factory method is the functional equivalent of a particular bytecode behavior
例如 getter 查找:
lookup.findGetter(C.class,"f",FT.class)
翻译成:
(T) this.f;
因此出现奇怪的错误 NoSuchFieldError: name
,因为您从外部引用了私有字段。对于 getters/setters 你应该选择 findVirtual
.