摘要Class.getMethods() 能返回 class 及父类中所有 public方法。然而,这些方法并不一定能被调用,比如在 private inner class 或 lambda 表达式中声明的方法。这篇文章会介绍如何找到所有真正的 public 方法。
昨天,我试图找出某个未知对象的所有 public 方法。这件事应该很容易,用 getClass() 找出 class,然后调用 getMethods() 。即使启用了 SecurityManager 应该也可以工作。第一个测试对象为 java.util.ArrayList ,运行结果一切正常。然后把代码重构一下,使用 Arrays.asList() 创建并初始化第一步创建的列表。结果,抛出了 IllegalAccessException 。
import java.lang.reflect.*;
import java.util.*;
public
class ReflectionPuzzle {
public
static
void
main
(String... args) {
Collection names =
new ArrayList<>();
Collections.addAll(names,
"Goetz",
"Marks",
"Rose");
printSize(names);
printSize(Arrays.asList(
"Goetz",
"Marks",
"Rose"));
printSize(List.of(
"Goetz",
"Marks",
"Rose"));
printSize(Collections.unmodifiableCollection(names));
}
private
static
void
printSize
(Collection> col) {
System.out.println(
"Size of " + col.getClass().getName());
try {
Method sizeMethod = col.getClass().getMethod(
"size");
System.out.println(sizeMethod.invoke(col));
}
catch (ReflectiveOperationException e) {
System.err.println(e);
}
}
}
上面的程序运行结果如下:
Size of java.util.ArrayList
3
Size of java.util.Arrays$ArrayList
java.lang.IllegalAccessException:
class ReflectionPuzzle cannot
access a member of
class java.util.Arrays$ArrayList (in
module
java.base) with modifiers
"public"
Size of java.util.ImmutableCollections$ListN
java.lang.IllegalAccessException:
class ReflectionPuzzle cannot
access a member of
class java.util.ImmutableCollections$ListN (in
module java.base) with modifiers
"public"
Size of java.util.Collections$UnmodifiableCollection
java.lang.IllegalAccessException:
class ReflectionPuzzle cannot
access a member of
class
java.util.Collections$UnmodifiableCollection (in
module
java.base) with modifiers
"public"
只有 ArrayList 执行 printSize() 时能得到结果。虽然 Size() 是定义在 java.util.Collection 中的 public 方法,但是定义的 class 也必须为 public。
我把这个问题发到了 Twitter,Brian Goetz 看到后告诉我,这种做法使用的是语言级别反射(reflection),应该用 class 反射。事实也是如此。如果对所有列表对象执行 List.class.getMethod("size") ,那么所有列表都会自动报告它们的大小(size)。
Java 没有“private” class 或类似的东西,class 只有 public 或 package 访问权限。当在内部类定义为“private”时,class 文件并没有在内部标记为 private。相反,它的访问权限为“package 可见”,外部类(outer class)访问时会遵守相应规则。之前的版本中,所有访问都是通过 synthetic method 完成,Java 12 对此进行了改变。
让我们定义一个稍微复杂的 class,它实现了多个接口继承并且采用协变返回值类型。
首先,在“problem” package 中定义两个 interface A 和 B。每个接口都定义了方法 foo() 但是返回值类型不同。
package problem;
public interface A {
CharSequence
foo
();
}
package problem;
public interface B {
java.io.
Serializable
foo
();
}
接下来,我们在另一个 package problem.inner 中定义 class “Hidden”,包含两个工厂方法。 getLambda() 实现了接口 A 返回一个 lambda。 getPrivateInnerClass() 返回一个 C 的实例。请注意: C 同时实现了接口 A 和 B ,并且 foo() 返回类型同时实现了这两个接口。还有另外一个 public 方法 bar() ,虽然为 public,但因为内部类为 private,同样也无法访问。
package problem.inner;
import problem.*;
public
class Hidden {
public
static A
getPrivateInnerClass
() {
return
new C();
}
private
static
class C implements A, B {
public String
foo
() {
return
"Hello World";
}
public String
bar
() {
return
"Should not be visible";
}
}
public
static A
getMethodClass
() {
class D implements A {
public CharSequence
foo
() {
return
"inside method";
}
}
return
new D();
}
public
static A
getLambda
() {
return () ->
"Hello Lambert";
}
}
下面这个示例展示了如何用普通的反射调用 foo() :
import problem.*;
import problem.inner.*;
import java.lang.reflect.*;
import java.util.stream.*;
public
class TestPlainReflection {
public
static
void
main
(String... args) {
System.out.println(
"Testing private inner class");
test(Hidden.getPrivateInnerClass());
System.out.println();
System.out.println(
"Testing method inner class");
test(Hidden.getMethodClass());
System.out.println();
System.out.println(
"Testing lambda");
test(Hidden.getLambda());
}
private
static
void
test
(A a) {
Stream.of(a.getClass().getMethods())
.forEach(System.out::println);
printMethodResult(a,
"foo");
printMethodResult(a,
"bar");
}
private
static
void
printMethodResult
(Object o, String name) {
try {
Method method = o.getClass().getMethod(name);
System.out.println(method.invoke(o));
}
catch (NoSuchMethodException e) {
System.out.println(
"Method " + name +
"() not found");
}
catch (IllegalAccessException e) {
System.out.println(
"Illegal to call " + name +
"()");
}
catch (InvocationTargetException e) {
throw
new IllegalStateException(e.getCause());
}
}
}
结果同样不理想。通过 getMethod() 找到 foo() ,由于属于 lambda 和私有内部类,同样也无法调用。而且,这些方法掩盖(shadow)了 A 和 B 中的方法。在私有内部类 C 上调用 getMethods() 会返回三个名为“foo”的方法,这些方法参数列表都为空,只有返回类型不同。 String foo() 在 Hidden$C 中定义,另外两个是合成(synthetic)方法。它们由编译器生成,用来产生协变返回类型。
Testing
private inner
class
public CharSequence problem.inner.Hidden$C.foo()
public java.io.Serializable problem.inner.Hidden$C.foo()
public String problem.inner.Hidden$C.foo()
public String problem.inner.Hidden$C.bar()
public Class Object.getClass()
public boolean Object.equals(Object)
public
int Object.hashCode()
public String Object.toString()
public
void Object.wait() throws InterruptedException
public
void Object.wait(
long) throws InterruptedException
public
void Object.wait(
long,
int) throws InterruptedException
public
void Object.notify()
public
void Object.notifyAll()
Illegal to call
foo
()
Illegal to call
bar
()
Testing method inner
class
public CharSequence problem.inner.Hidden$1D.
foo
()
public Class Object.
getClass
()
public boolean Object.
equals
(Object)
public
int Object.
hashCode
()
public String Object.
toString
()
public
void Object.
wait
() throws InterruptedException
public
void Object.
wait
(
long) throws InterruptedException
public
void Object.
wait
(
long,
int) throws InterruptedException
public
void Object.
notify
()
public
void Object.
notifyAll
()
Illegal to call
foo
()
Method
bar
() not found
Testing lambda
public CharSequence problem.inner.Hidden$$Lambda$23/0x67840.
foo
()
public Class Object.
getClass
()
public boolean Object.
equals
(Object)
public
int Object.
hashCode
()
public String Object.
toString
()
public
void Object.
wait
() throws InterruptedException
public
void Object.
wait
(
long) throws InterruptedException
public
void Object.
wait
(
long,
int) throws InterruptedException
public
void Object.
notify
()
public
void Object.
notifyAll
()
Illegal to call
foo
()
Method
bar
() not found
注意:我们无法通过这些对象调用 foo() 。
在
Reflections
类中,我们尝试找到 public 方法且方法所在的 class 也是 public。为了确认这一点,把方法以及包含方法的类使用的修饰符进行“与(AND)”操作。如果结果仍然是 public,那么就能知道这是一个真正的
public
方法。在旧的反射机制中,当在指定的类中找不到方法时,会抛出 NoSuchMethodException。这里会返回
Optional
getTrulyPublicMethods() 会递归找到 class 层次结构中所有真正的 public 方法。为了模拟掩盖(shadow)效果,只有当层次结构下游没有相同的方法签名时,才会加入结果集合。在例子中,方法签名由返回类型、方法名和参数类型组成。集合的 key 是一个字符串,由这三个元素组成。
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;
public
class Reflections {
public
static Optional getTrulyPublicMethod(
Class> clazz, String name, Class>... paramTypes) {
return getTrulyPublicMethods(clazz)
.stream()
.filter(method -> matches(method, name, paramTypes))
.reduce((m1, m2) -> {
Class> r1 = m1.getReturnType();
Class> r2 = m2.getReturnType();
return r1 != r2 && r1.isAssignableFrom(r2) ? m2 : m1;
});
}
public
static Collection getTrulyPublicMethods(
Class> clazz) {
Map result =
new HashMap<>();
findTrulyPublicMethods(clazz, result);
return List.copyOf(result.values());
}
private
static
void
findTrulyPublicMethods
(
Class> clazz, Map result) {
if (clazz == null)
return;
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (isTrulyPublic(method))
result.putIfAbsent(toString(method), method);
}
for (Class> intf : clazz.getInterfaces()) {
findTrulyPublicMethods(intf, result);
}
findTrulyPublicMethods(clazz.getSuperclass(), result);
}
private
static boolean
isTrulyPublic
(Method method) {
return Modifier.isPublic(method.getModifiers()
& method.getDeclaringClass().getModifiers());
}
private
static String
toString
(Method method) {
String prefix = method.getReturnType().getCanonicalName() +
method.getName() +
" (";
return Stream.of(method.getParameterTypes())
.
map(Class::getCanonicalName)
.collect(Collectors.joining(
", ",
prefix,
")"));
}
private
static boolean
matches
(
Method method, String name, Class>... paramTypes) {
return method.getName().equals(name)
&& Arrays.equals(method.getParameterTypes(), paramTypes);
}
}
可以确定的是,这里没有考虑所有返回类型和一些可能出错的 class 层次结构。但是,这段代码的确通过了我的测试。另外,使用 Optional 要比捕捉异常好。下面是 TestTrulyPublic 实现:
import problem.*;
import problem.inner.*;
import java.lang.reflect.*;
import java.util.*;
public
class TestTrulyPublic {
public
static
void
main
(String... args) throws Exception {
System.out.println(
"Testing private inner class");
test(Hidden.getPrivateInnerClass());
System.out.println();
System.out.println(
"Testing method inner class");
test(Hidden.getMethodClass());
System.out.println();
System.out.println(
"Testing lambda");
test(Hidden.getLambda());
}
private
static
void
test
(A a) {
Reflections.getTrulyPublicMethods(a.getClass()).forEach(
System.out::println);
printMethodResult(a,
"foo");
printMethodResult(a,
"bar");
}
private
static
void
printMethodResult
(
Object o, String methodName) {
Optional method = Reflections.getTrulyPublicMethod(
o.getClass(), methodName);
method.
map(m -> {
try {
System.out.println(
"m = " + m);
return m.invoke(o);
}
catch (IllegalAccessException e) {
throw
new IllegalStateException(e);
}
catch (InvocationTargetException e) {
throw
new IllegalStateException(e.getCause());
}
}).ifPresentOrElse(System.out::println,
() -> System.out.println(
"Method " +
methodName +
"() not found"));
}
}
运行第二个测试,得到以下输出结果:
Testing
private
inner
class
public
abstract java.io.Serializable problem.B.foo()
public
abstract CharSequence problem.A.foo()
public
Class Object.getClass()
public
boolean Object.equals(Object)
public
int
Object.hashCode()
public
String Object.toString()
public
void
Object.wait() throws InterruptedException
public
void
Object.wait(
long
) throws InterruptedException
public
void
Object.wait(
long
,
int
) throws InterruptedException
public
void
Object.notify()
public
void
Object.notifyAll()
m =
public
abstract java.io.Serializable problem.B.foo()
Hello World
Method bar() not found
Testing method inner
class
public
abstract CharSequence problem.A.foo()
public
Class Object.getClass()
public
boolean Object.equals(Object)
public
int
Object.hashCode()
public
String Object.toString()
public
void
Object.wait() throws InterruptedException
public
void
Object.wait(
long
) throws InterruptedException
public
void
Object.wait(
long
,
int
) throws InterruptedException
public
void
Object.notify()
public
void
Object.notifyAll()
m =
public
abstract CharSequence problem.A.foo()
inside method
function(){ //
外汇跟单
www.gendan5.com
Method bar() not found
Testing lambda
public
abstract CharSequence problem.A.foo()
public
Class Object.getClass()
public
boolean Object.equals(Object)
public
int
Object.hashCode()
public
String Object.toString()
public
void
Object.wait() throws InterruptedException
public
void
Object.wait(
long
) throws InterruptedException
public
void
Object.wait(
long
,
int
) throws InterruptedException
public
void
Object.notify()
public
void
Object.notifyAll()
m =
public
abstract CharSequence problem.A.foo()
Hello Lambert
Method bar() not found
可以看到,现在所有 foo() 调用都成功了,还可以看到这里没有找到 bar() 方法。