Re: [問題] 多繼承super的問題

作者: sbrhsieh (十年一夢)   2015-01-03 00:06:42
※ 引述《egheee (阿平)》之銘言:
: class A {
: void tell() {
: Log.e("", "I am a");
: }
: }
: class B extends A{
: void tell() {
: Log.e("", "I am b");
: }
: }
: class C extends B {
: void tell() {
: super.super.tell(); // 問題
: Log.e("", "I am c");
: }
: }
: 如上列所示,這樣的寫法是有問題的,super好像規定只能用一次
: 請問我要怎麼從C裡面call到A的tell()呢?
依所示的情境與需求,就我所知是無法單從 Java 語法層面去解決的。
有些版友提到在 C::tell method 裡另外建構一個 A instance,由此 instance
去調用 tell method。我想這應該只能針對上面範例之類的個案(tell method 對
調用的 object 本身沒有副作用),能適用的實例不多,不能算是一種解法。
如果不限制在語法層面,也暫時不考慮這樣的設計是否合理...等等,的確是有方式
可以轉寰的。我提供一個的可行做法是透過 bytecode 工程去加工。
先定義一下需求與條件,以上述的 sample 來說:
1. 你沒有 class A, class B 的 source code,只有 class file。
2. 有 class C 的 source 可供編輯。
3. 基於某種因素,你需要明確在 C::tell method 裡調用 A::tell implemention,
不論 C 的 base classes 有無 override tell method。
我做了一個 bytecode 加工的工具,可在此網址下載(jar file):
http://ul.to/2k6o1dzz
將 C.java 修改為:
import ptt.java.tool.RedirectSuperCall;
class C extends B {
@RedirectSuperCall(targetType=A.class)
void tell() {
super.tell();
Log.e("", "I am c");
}
}
編譯 C.java 時將下載的 jar 加入 classpath。
編譯完後,執行 jar 內的 ptt.java.tool.Main class,執行時 classpath 除了
下載的 jar 外也需要包含你的 project binary(你的 project class file 不能
打包成 jar)。
java -cp <path_to_RedirectSuper.jar>;<project_output> ptt.java.tool.Main
<path_to_C.class>
後面參數可以是(多個)檔案或檔案夾的路徑,如果檔案是 .class 檔案,tool 會去
看是否有使用 RedirectSuperCall annotation 來做加工,如果是檔案夾則會查看
檔案夾內的所有 .class 檔案(包括子檔案夾)。
為了比較快做出這個 tool 我有省略一些考量,算是比較簡化的版本。暫不考慮
generic method 以及 method exception clause,但 return type 與 parameter
type/數量倒是沒有限制。
這個 tool 做的事情如下:
去修改以 RedirectSuperCall(targetType=XXX.class) 標注的 method bytecode
(假設被標注的 method 是 someMethod),將 super.someMethod(...) expression
改成 invoke tool 注入在 XXX class 內的 static someMethod$hook method。
(被注入的 method 內容大致如下:
class XXX {
public static ... someMethod$hook(XXX obj, ...) {
return obj.someMethod(...); // but use invokespecial instruction
}
}
雖然說 annotation 只標注在 C class,但是單只是去加工 C class bytecode 是
不足以做所需的效果,若是把 super.someMethod(...) expression 中
invokespecial instruction 的目標 class 從 B 改成 A,還是會執行到 B class
定義的 overriding 版本。(因為此時的 context 是 C class)
我的做法是把原來的 invokespecial call 改成一個 invokestatic call,而被
指定的 method 裡使用 invokespecial instruction 來調用真正想要調用的
non-static virtual method。
這裡 hook method 簡單地透過 invokespecial instruction 調用另一個 virtual
method 的做法應該是比較不保險的做法,但我在幾種 JRE 上測試過都正確。
(如果有人測試時沒有成功 redirect,歡迎來信告知/分享你使用的 JRE)
比較保險的做法是把目標 method inline 在 hook method 裡,但若是 targetType
並沒有真的 declare/override 被標注的 method,實作上會很麻煩。所以是我偷懶
沒錯,但是額外好處是 targetType 可以指定任一個 base class,他的意思是
「我要 redirect 到 targetType 為止的最新版本」。
*tool bytecode version 是 50.0,故你需要 JRE 1.6+ 才能使用之。
**理論上,這個 tool 應該可以做成 annotation processor 成為編譯的一部份,
或是做成 instrumentation agent 在 runtime 時部署。前者我經驗還不夠,要搞
比較久;後者則是因為當 JVM 載入使用 annotation 標記的 class 時,他的 base
class 已經載入,如果還要加工 base class 的話,必須要使用的 JVM 有支援
retransform。所以我做出了這樣的工具...呵呵,反正只是 demo 用途。

Links booklink

Contact Us: admin [ a t ] ucptt.com