承接前兩篇關於重載、重寫、靜態/動態分派的討論,本文聚焦多態(override)的底層實現——方法表,並對比 invokevirtual(類引用調用)與 invokeinterface(接口引用調用)的性能差異。
基於基類的調用(invokevirtual)性能高於基於接口的調用(invokeinterface)。
前者基於偏移量直接定位,後者需掃描整個方法表搜索。
類加載後,JVM 在方法區建立類型信息,包含方法代碼、變量、方法表。方法表是實現動態綁定的關鍵數據結構。
Person boy = new Boy();
Person girl = new Girl();
boy.speak(); // 動態綁定到 Boy.speak()
girl.speak(); // 動態綁定到 Girl.speak()
#9 // Method Person.speak:()VPerson 方法表,得到 speak 的偏移量(假設 15)this 指針確定實際類型(Boy 或 Girl)關鍵點:父子類方法表中相同簽名方法擁有相同索引,故只需一次偏移量定位,極快。
IDance dancer = new Dancer();
IDance snake = new Snake();
dancer.dance(); // invokeinterface
snake.dance(); // invokeinterface
invokeinterface #6, 1 多一個參數(方法數提示),也是為了優化搜索。若 Dancer 不重寫 toString(),調用 System.out.println(dancer) 會輸出父類 Person 的實現,驗證了「沿繼承鏈向上搜索」的機制同樣適用於接口引用調用非接口方法的情況。
invokevirtual:偏移量定位 → O(1),適合高頻調用;invokeinterface:方法表掃描 → O(n),有性能開銷;