object 關鍵字
Kotlin 中有一種特殊的類,它本身也是一個例項(單例),這種既是類又是物件的類需要使用 object 關鍵字宣告(普通類宣告使用 class),它跟普通類一樣,也可以實現介面和繼承父類:
object MusicPlayer : Player(), OnStatusChangeListener { var state: Int = 0 fun play(url: String) { ... } fun stop() { ... } override fun onMount(driver: Driver) { } override fun onUnmount(driver: Driver) { }}
這種 object 類的方法可以透過類名直接呼叫:
fun main() { MusicPlayer.play("http://qqmusic.com/123213.mp3") MusicPlayer.stop()}
藉助 Show Kotlin Bytecode 工具,將上述 Kotlin 程式碼反編譯成 java 程式碼如下:
public final class MusicPlayer extends Player implements OnStatusChangeListener { public static final MusicPlayer INSTANCE; private MusicPlayer() { } static { MusicPlayer var0 = new MusicPlayer(); INSTANCE = var0; } ...}
可以發現,Kotlin 中的 object 本質上就是 Java 中簡單的單例模式,同時它的構造方法是 private 的,因此 object 類不能自定義構造方法!
伴生物件(companion object)日常開發中,經常會編寫一些 Bean 類,用於描述一類事物,Bean 中會包含事物的屬性,有的 Bean 類還會提供一系列"工具方法"來幫助開發者快速建立 Bean 物件,就比如在 Java 中,Integer.valueOf() 就可以幫助開發者快速將字串轉成整型,進入原始碼一看,可以發現這是一個使用了 static 關鍵字修飾的靜態方法:
public static Integer valueOf(String s) throws NumberFormatException { return Integer.valueOf(parseInt(s, 10));}
可是,在 Kotlin 中是沒有 static 關鍵字的!!!想要實現透過 類名.xxx() 的方式呼叫工具方法,可以使用上述的 object 類,但 object 本身就是一個物件,我們無法在外部呼叫其構造方法,既然想擁有 object 類(透過類名呼叫方法)與 class 類(外部可以呼叫構造方法)的兩種特性,這時伴生物件 companion object 就完全可以滿足這個需求:
class Rectangle(val width: Int, val height: Int) { companion object { fun ofSize(width: Int, height: Int): Rectangle { return Rectangle(width, height) } fun ofRectangle(rectangle: Rectangle): Rectangle { return Rectangle(rectangle.width, rectangle.height) } }}fun main() { val width = 4 val height = 5 val rectangle1 = Rectangle(width, height) // 呼叫構造方法 val rectangle2 = Rectangle.ofRectangle(rectangle1) // 透過類名呼叫方法 val rectangle3 = Rectangle.ofSize(width, height) // 透過類名呼叫方法}
伴生物件顧名思義,就是與類一起誕生的物件,類一載入,它的伴生物件也就被建立了,每個類都可以對應一個伴生物件,並且該伴生物件的成員全域性獨一份,也就是說伴生物件 companion object 也是一種單例,再次藉助 Show Kotlin Bytecode 工具,將上述 Kotlin 程式碼反編譯成 java 程式碼如下:
public final class Rectangle { ... public static final Rectangle.Companion Companion = new Rectangle.Companion((DefaultConstructorMarker)null); public static final class Companion { @NotNull public final Rectangle ofSize(int width, int height) { return new Rectangle(width, height); } @NotNull public final Rectangle ofRectangle(@NotNull Rectangle rectangle) { Intrinsics.checkParameterIsNotNull(rectangle, "rectangle"); return new Rectangle(rectangle.getWidth(), rectangle.getHeight()); } private Companion() { } // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } }}
可以發現,Kotlin 中的 companion object 其實對應到 Java 中也就只是 Rectangle 的一個靜態內部類例項而已。
JVM 靜態成員(@JvmStatic)我們把上面 Kotlin 的 main 方法中的程式碼用 java 重寫一遍:
int width = 4;int height = 5;Rectangle rectangle1 = new Rectangle(width,height);Rectangle rectangle2 = Rectangle.Companion.ofSize(width,height);Rectangle rectangle3 = Rectangle.Companion.ofRectangle(rectangle2);
發現 ofSize 和 ofRectangle 方法並不是透過 Rectangle 類直接呼叫的,結合上述反編譯後的 java 程式碼,應該不難理解,因為 Kotlin 中 Rectangle 的伴生物件本質上就是這個 Rectangle.Companion 例項,ofSize 和 ofRectangle 都是 Rectangle.Companion 的方法,所以,用 Rectangle.Companion 物件呼叫它自己的方法這完全沒有毛病,可是,難道就不能在 Java 中也像 Kotlin 那樣,直接透過 類名.xxx() 的方法來呼叫伴生物件方法嗎?答案肯定是有的啦,Kotlin 提供了 @JvmStatic 和 @JvmField,可以讓伴生物件中的方法和屬性在 java 中透過類名直接呼叫:
class Rectangle(val width: Int, val height: Int) { companion object { @JvmStatic fun ofSize(width: Int, height: Int): Rectangle { return Rectangle(width, height) } @JvmStatic fun ofRectangle(rectangle: Rectangle): Rectangle { return Rectangle(rectangle.width, rectangle.height) } @JvmField val Tag = "Rectangle" }}
這次 java 程式碼也可以使用 Rectangle 類名來呼叫 ofSize 和 ofRectangle 方法了:
int width = 4;int height = 5;Rectangle rectangle1 = new Rectangle(width, height);Rectangle rectangle2 = Rectangle.ofSize(width, height);Rectangle rectangle3 = Rectangle.ofRectangle(rectangle2);System.out.println(Rectangle.Tag);
再來反編譯一次,看其究竟是什麼原理,原來就只是在 Rectangle 中增加了對應的 static 屬性和方法而已:
public final class Rectangle { public static final Rectangle.Companion Companion = new Rectangle.Companion((DefaultConstructorMarker)null); @JvmField @NotNull public static final String Tag = "Rectangle"; @JvmStatic @NotNull public static final Rectangle ofSize(int width, int height) { return Companion.ofSize(width, height); } @JvmStatic @NotNull public static final Rectangle ofRectangle(@NotNull Rectangle rectangle) { return Companion.ofRectangle(rectangle); } public static final class Companion { @JvmStatic @NotNull public final Rectangle ofSize(int width, int height) { ... } @JvmStatic @NotNull public final Rectangle ofRectangle(@NotNull Rectangle rectangle) { ... } }}