如何找到真正的 public 方法
摘要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<String> 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<Method> 。
getTrulyPublicMethods() 會遞迴找到 class 層次結構中所有真正的 public 方法。為了模擬掩蓋(shadow)效果,只有當層次結構下游沒有相同的方法簽名時,才會加入結果集合。在例子中,方法簽名由返回型別、方法名和引數型別組成。集合的 key 是一個字串,由這三個元素組成。
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;
public
class Reflections {
public
static Optional<Method> 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<Method> getTrulyPublicMethods(
Class<?> clazz) {
Map<String, Method> result =
new HashMap<>();
findTrulyPublicMethods(clazz, result);
return List.copyOf(result.values());
}
private
static
void
findTrulyPublicMethods
(
Class<?> clazz, Map<String, Method> 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> 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(){ //
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() 方法。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946337/viewspace-2662477/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 如何找到使用者的真正需求?
- Oracle RAC修改Scan IP,Public IP的方法Oracle
- 【Java小疑問】類和方法 前面新增public和不新增public的區別Java
- Laravel 5中去掉URL中的public路徑方法Laravel
- 如何找到埠的程式號
- Check the existence of public synonyms Remove the public synonymsREM
- 真正做到降本增效的方法有哪些?
- 為什麼Java的main方法必須是public static void?JavaAI
- Android setUserVisibleHint-- fragment真正的onResume和onPause方法AndroidFragment
- python是如何找到對應的package的?PythonPackage
- var和public的區別
- Oracle 12. 2 RAC public IP與vip 互換方法Oracle
- TestFlight Public Link
- Nginx啟動不了報錯未找到命令的解決方法(- bash: nginx: 未找到命令)Nginx
- public interface View介面和public interface ViewResolver介面介紹View
- 攔截|篡改|偽造.NET類庫中不限於public的類和方法
- 如何找到企業資源的最佳配比?
- 想要文化輸出,真正有效的方法是做出好作品
- Swift 3.0 的 open,public,internal,fiSwift
- PostgreSQL-PostgreSQL中的public(九)SQL
- 找不到 main 方法, 請將 main 方法定義為: public static void main(String[] args)AI
- 告別“魚塘局”! 這套匹配機制為你找到真正“勢均力敵”的對手
- 如何找到JAVA_HOME | BaeldungJava
- 車停哪裡忘記了怎麼找到?如何找到自己的停車位置?
- 2.2.4.4 Grants to PUBLIC in a CDB
- public-image-mirror
- 真正能用,還有點效果的CSS掛馬程式碼的方法CSS
- 如何找到計數表在不同的行嗎
- Python3 如何找到字典的下標 indexPythonIndex
- 如何成為真正專業的程式設計師程式設計師
- Oracle 12.2 RAC修改public ip address或public ip(subnet (netmask) or interface)Oracle
- [BUG反饋]public function group() 方法缺少網頁標題賦值Function網頁賦值
- 私有屬性的Get Set 與 Public
- 修改 Laravel 的預設 public 路徑Laravel
- Dotnet Core Public API的安全實踐API
- 如何解決win10系統office無法找到此應用程式的許可證的方法Win10
- 新的里程碑 | Windows Defender已找到對抗“HIV”的方法Windows
- 如何更快的找到自己所需的模型關聯型別?模型型別