標籤:

詭異了,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

TAG:Kotlin | Android |