And*_*niy 4 java reflection lambda jls java-8
我对以下情况感到困惑。
考虑两个包a并b具有以下类:
1)MethodInvoker仅call()在给定对象上调用:
package b;
import java.util.concurrent.Callable;
public class MethodInvoker {
public static void invoke(Callable r) throws Exception {
r.call();
}
}
Run Code Online (Sandbox Code Playgroud)
2)
package a;
import b.MethodInvoker;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
public class Test {
private static Void method() {
System.out.println("OK");
return null;
}
public static void main(String[] args) throws Exception {
Method method = Test.class.getDeclaredMethod("method");
method.invoke(null); // ok
// TEST 1
MethodInvoker.invoke(() -> {
return method.invoke(null); // ok (hmm....
});
// TEST 2
MethodInvoker.invoke(new Callable() {
@Override
public Object call() {
return method(); // ok (hm...???
}
});
// TEST 3
MethodInvoker.invoke(new Callable() {
@Override
public Object call() throws Exception {
return method.invoke(null); // throws IllegalAccessException, why???
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
我明确地将其method() 私有化,以测试如何在Test类范围之外调用它。我通常对这三个案例感到困惑,因为我发现它们是矛盾的。我通常希望他们所有人都应该以相同的方式工作。至少,我希望如果TEST 3抛出IllegalAccessException,那么TEST 2应该也这样做。但是TEST 2工作正常!
有人可以根据JLS给出严格的解释,为什么每种情况都起作用呢?
TEST1和TEST3之间的差异归结为lambda和匿名类的实现方式之间的差异。
查看这些特殊情况的实际字节码总是很有趣。 https://javap.yawk.at/#jXcoec
TEST1 lambda:
Lambda表达式将转换为定义的类中的方法。传递了对该方法的方法引用。由于lambda方法是该类的一部分,因此可以直接访问该类的私有方法。method.invoke()作品。
TEST3匿名类:
匿名类将转换为类。在该类中调用method.invoke(),该类不应访问私有方法。由于反射,使用合成方法的工作环境无法正常工作。
TEST2:为了允许嵌套类访问其外部类的私有成员,引入了综合方法。如果您查看字节码,将会看到带有签名的方法,该方法static java.lang.Void access$000();将调用转发给Void method()
关于语言层面的可访问性,在JLS §6.6.1, Determining Accessibility 中有一个直接的声明:
…
- 否则,声明成员或构造函数
private,当且仅当它发生在包含成员或构造函数声明的顶级类(第7.6 节)的主体内时,才允许访问。
由于所有嵌套类和 lambda 表达式都驻留在同一个“顶级类的主体”中,这已经足以解释访问的有效性。
但是无论如何,lambda 表达式与内部类有着根本的不同:
出现在匿名类的声明不同的代码,名称的含义和
this和super出现在拉姆达身体,用引用声明的可访问性以及关键字,都一样在周围环境(除了拉姆达参数引入新的名称)。
这使得 lambda 表达式很明显可以访问private其类的成员,即定义它的类,而不是函数接口。lambda 表达式没有实现函数式接口,也没有从中继承成员。它将与目标类型类型兼容,并且将有一个函数接口的实例,当函数方法在运行时调用时执行 lambda 表达式的主体。
此实例的生成方式是有意未指定的。作为技术细节的说明,参考实现中生成的类可以访问private另一个类的方法,这是必要的,因为为 lambda 表达式生成的合成方法private也是如此。这可以通过添加MethodInvoker.invoke(Test::method);到您的测试用例来说明。此方法引用允许method直接调用,而无需在类中使用任何合成方法Test。
不过,反射是另一回事。它甚至没有出现在语言规范中。这是图书馆的特色。这个库在内部类可访问性方面存在已知问题。这些问题与内部类特性本身一样古老(自 Java 1.1 起)。还有JDK-8010319,在嵌套类Java访问规则JVM支持其当前状态的目标是Java的10 ...
如果你真的需要内部类中的反射访问,你可以使用这个java.lang.invoke包:
public class Test {
private static Void method() {
System.out.println("OK");
return null;
}
public static void main(String[] args) throws Exception {
// captures the context including accessibility,
// stored in a local variable, thus only available to inner classes of this method
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle method = lookup.findStatic(Test.class, "method",
MethodType.methodType(Void.class));
// TEST 2
MethodInvoker.invoke(new Callable() {
public Object call() throws Exception {
// invoking a method handle performs no access checks
try { return (Void)method.invokeExact(); }
catch(Exception|Error e) { throw e; }
catch(Throwable t) { throw new AssertionError(t); }
}
});
// TEST 3
MethodInvoker.invoke(new Callable() {
// since lookup captured the access context, we can search for Test's
// private members even from within the inner class
MethodHandle method = lookup.findStatic(Test.class, "method",
MethodType.methodType(Void.class));
public Object call() throws Exception {
// again, invoking a method handle performs no access checks
try { return (Void)method.invokeExact(); }
catch(Exception|Error e) { throw e; }
catch(Throwable t) { throw new AssertionError(t); }
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
当然,由于MethodHandles.Lookup对象和MethodHandle包含private无需进一步检查即可访问其创建者成员的能力,因此必须小心不要将它们分发给无意的人。但是为此,您可以确定现有的语言级别可访问性。如果在private字段中存储查找对象或句柄,则只有同一顶级类中的代码才能访问它,如果使用局部变量,则只有同一局部范围内的类才能访问它。
由于只有直接调用者java.lang.reflect.Method,另一种解决方案是使用蹦床:
public class Test {
private static Void method() {
System.out.println("OK");
return null;
}
public static void main(String[] args) throws Exception {
Method method = Test.class.getDeclaredMethod("method");
// TEST 3
MethodInvoker.invoke(new Callable() {
@Override
public Object call() throws Exception {
return invoke(method, null); // works
}
});
}
private static Object invoke(Method m, Object obj, Object... arg)
throws ReflectiveOperationException {
return m.invoke(obj, arg);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2369 次 |
| 最近记录: |