詭異了,AtomicInteger 在 Kotlin 裡面居然是 Abstract 的?
1. 人生自古哪兒沒坑
作為一個用了兩年 Kotlin 的人,最近越來越控制不住自己,於是乎各種 Java 代碼都開始變成 Kt,於是,也就發現了更多好玩的東東~
話說呀,有個叫做 Retrofit 的框架,它呢有個叫 CallAdapter 的東西,其中有個 RxJava 版本的實現,讓某一個類繼承 AtomicInteger 來存儲一個線程安全的狀態值,如果大家有興趣的話,可以去看下這個類:CallArbiter.java
而我呢,最近在閑暇時間仿照 Retrofit 寫了一個叫做 RetroApollo 的項目,這個項目主要是對Apollo-Android這個項目做了封裝,讓我們更方便的訪問 GraphQL Api,這其中呢,就涉及到對 RxJava 的支持了。
我當時就想,我也搞一個CallArbiter吧,只不過我是用 Kotlin 寫的,顯然根據以往的經驗,Kotlin 根本就不會是什麼問題好嘛,結果剛開個頭就傻眼了:
class CallArbiter: AtomicInteger{ //錯誤!你有三個方法需要實現!n constructor(initialValue: Int) : super(initialValue)n constructor() : super()n}n
就這麼一段代碼,打死我都想不到居然會報錯,報錯理由也挺逗:
Error:(8, 1) Kotlin: Class CallArbiter must be declared abstract or implement abstract base class member public abstract fun toByte(): Byte defined in java.util.concurrent.atomic.AtomicInteger
這哪兒跟哪兒呢你說,AtomicInteger人家本身就是一個具體的類啊,哪兒來的沒實現的方法呢?這錯誤報的雖然是說沒有實現toByte方法,可仔細觀察一下就會發現,沒實現的方法居然還有toShort和toChar。。
2. 此坑真是久長時啊
我以為這是在逗我玩呢,畢竟看了下AtomicInteger和它的父類Number,找了半天也沒有找到所謂的toByte方法啊。
不過這方法名咋看著這麼眼熟呢,好像 Kotlin 裡面所有的數都有這個方法吧,追查了一下 Kotlin 源碼,居然發現 Kotlin 自己有個叫Number的抽象類!
public abstract class Number {n public abstract fun toDouble(): Doublen public abstract fun toFloat(): Floatn public abstract fun toLong(): Longn public abstract fun toInt(): Intn public abstract fun toChar(): Charn public abstract fun toShort(): Shortn public abstract fun toByte(): Byten}n
所以會不會哪些所謂的沒有實現的抽象方法都是來自這個Number的?
這還用猜?必然是啊,不過這事兒也有點兒奇怪了,畢竟AtomicInteger繼承的可是java.lang.Number,Kotlin 和 Java 中的這兩個Number之間有什麼關係么?
3. 解密時刻
我之前很早的時候就寫過一篇文章為什麼不直接使用 Array 而是 IntArray ?提到了 Kotlin 類型到 Java 類型的映射問題,這裡我們其實也是遇到了相同的問題。
kotlin.Number編譯後映射成了java.lang.Number,也就是說,AtomicInteger在 Kotlin 當中被認為是kotlin.Number的子類,而巧了,toByte這樣的方法在AtomicInteger和java.lang.Number當中都沒有具體實現,這就導致了前面的情況發生。
不過這裡還是有問題的,Java 中的Number有類似doubleValue這樣的方法,Kotlin 當中的toDouble與之有何關係?
我們定義這麼一個類繼承自 Kotlin 的Number:
class MyNumber: Number(){ n override fun toByte(): Byte { ... }n override fun toChar(): Char { ... }n override fun toDouble(): Double { ... }n override fun toFloat(): Float { ... }n override fun toInt(): Int { ... }n override fun toLong(): Long { ... }n override fun toShort(): Short { ... }n}n
編譯之後看看位元組碼就會發現,編譯器自動為我們合成了 Java 中Number對應的方法,例如doubleValue:
// access flags 0x51n public final bridge doubleValue()Dn L0n LINENUMBER 19 L0n ALOAD 0n INVOKEVIRTUAL test/TestNumber.toDouble ()Dn DRETURNn MAXSTACK = 2n MAXLOCALS = 1n
而這個doubleValue正是轉而去調用了toDouble這個方法!
好,那麼前面一直出問題的toByte呢?也是一樣,生成了一個叫做byteValue的方法,然後去調用了toByte。
等等!!這裡有問題!人家 Java 中Number的byteValue方法是有實現的!你這樣不是把人家原來的實現給搞沒了么。。
java.lang.Number
public byte byteValue() {n return (byte)intValue();n}n
嗯啊,是沒了。。。除了這個之外,還有一個shortValue,這二位都在 Java 中默認調用了intValue,在 Kotlin 當中則被要求單獨實現(toByte/toShort),於是乎我們想要繼承AtomicInteger就得實現這兩個方法。
至於toChar,這個在 Java 的Number版本中沒有對應的charValue,所以我們也得自己實現咯。
4. 小結
經過上面的討論,我們知道了 Kotlin 和 Java 之間存在各式各樣的類型和方法的映射,為了兼容 Java 而又保持自己獨特的風格,Kotlin 顯然不得不這樣做,相比其他語言,它也是做得比較不錯的。
而對於我們遇到的問題,從邏輯上講,AtomicInteger這個類不應該是open的,我們繼承它和把它作為一個組件進行組合實際上是沒有區別的,對於組合就可以解決的問題,就不應該使用繼承。換句話說,文章開頭的代碼正確的寫法應該是:
class CallArbiter<T>{nn val atomicState = AtomicInteger(STATE_WAITING)n ... n}n
關注公眾號 Kotlin ,獲取最新的 Kotlin 動態。
推薦閱讀:
※Kotlin實現的自己列印代碼自己
※在 Kotlin 代碼中慎用 lateinit 屬性
※Kotlin 泛型中的 in 和 out
※【譯】當 Kotlin 遇見 Gradle