偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

如何避免Kotlin里的陷阱?

開(kāi)發(fā) 后端
最近 Kotlin 特別流行,并且我也贊同 Kotlin 是一個(gè)經(jīng)過(guò)深思熟慮后被設(shè)計(jì)出的語(yǔ)言,除了下面提到的缺點(diǎn)之外。我會(huì)在本文向你分析一些我在開(kāi)發(fā)過(guò)程中遇到的陷阱,并且教你如何避免他們。

最近 Kotlin 特別流行,并且我也贊同 Kotlin 是一個(gè)經(jīng)過(guò)深思熟慮后被設(shè)計(jì)出的語(yǔ)言,除了下面提到的缺點(diǎn)之外。我會(huì)在本文向你分析一些我在開(kāi)發(fā)過(guò)程中遇到的陷阱,并且教你如何避免他們。

[[207040]]

謎一樣的 null

在 Kotlin 當(dāng)中,你可以不用考慮在你的代碼中如何處理 null 的問(wèn)題,這會(huì)讓你忘記 null 是無(wú)處不在的這個(gè)說(shuō)法,只不過(guò)被隱藏了起來(lái)。看看下面這個(gè)表面看起來(lái)沒(méi)有問(wèn)題的類(lèi): 

  1. class Foo { 
  2.     private val c: String 
  3.     init { 
  4.         bar() 
  5.         c = "" 
  6.     } 
  7.     private fun bar() { 
  8.         println(c.length) 
  9.     } 

如果你嘗試初始化這個(gè)類(lèi),那么代碼就會(huì)拋出一個(gè) NullPointerException。因?yàn)?bar 方法嘗試在 c 變量初始化之前就訪(fǎng)問(wèn)它。

盡管這個(gè)代碼本身就是有問(wèn)題的,才導(dǎo)致異常拋出。但是更糟糕的是你的編譯器不會(huì)發(fā)現(xiàn)這一點(diǎn)。

Kotlin 可以幫你在絕大部分情況下避免 null,但是你不能因此而忘記 null 的存在。否則遲早有一天你會(huì)碰上類(lèi)似的問(wèn)題。

來(lái)自 JDK 的 null

Kotlin 的標(biāo)準(zhǔn)庫(kù)能夠很好地處理 null。但是如果你使用了 JDK 中的類(lèi),你需要自己處理關(guān)于 JDK 方法調(diào)用可能產(chǎn)生的空指針。

大部分情況下 Kotlin 的標(biāo)準(zhǔn)庫(kù)就足夠了,但是有時(shí)你需要使用到 ConcurrentHashMap: 

  1. val map = ConcurrentHashMap<String, String>() 
  2. map["foo"] = "bar" 
  3. val bar: String = map["foo"]!! 

這時(shí),你需要使用 !! 操作符。但某些情況下你還可以使用像 (?) 這樣的對(duì) null 安全的操作符來(lái)替換它。盡管如此,當(dāng)你使用 !! 或者 ? ,或者編寫(xiě)了一個(gè)適配器來(lái)使用 Java 類(lèi)庫(kù)的時(shí)候,你會(huì)發(fā)現(xiàn)代碼因?yàn)檫@些修改而變的混亂。這是你無(wú)法避免的問(wèn)題。

你還可能會(huì)碰上更多更可怕的問(wèn)題。當(dāng)你使用 JDK 類(lèi)中的方法的時(shí)候,返回值可能是null,而且沒(méi)有什么像 Map 訪(fǎng)問(wèn)一樣的語(yǔ)法糖。

考慮如下例子: 

  1. val queue: Queue<String> = LinkedList() 
  2. queue.peek().toInt() 

這種情況下,你使用了可能返回 null 值的 peek 方法。但是 Kotlin 編譯器不會(huì)提示你這個(gè)問(wèn)題,所以當(dāng)你的 Queue 是空隊(duì)列的的時(shí)候,可能會(huì)觸發(fā) NullPointerException 異常。

問(wèn)題在于我們使用的 Queue 是 JDK 的一個(gè)接口,并且當(dāng)你查看 peek 方法的文檔時(shí): 

  1. /** 
  2.   * Retrieves, but does not remove, the head of this queue, 
  3.   * or returns {@code null} if this queue is empty. 
  4.   * 
  5.   * @return the head of this queue, or {@code null} if this queue is empty 
  6.   */ 
  7.   E peek(); 

文檔中說(shuō) peek 方法會(huì)返回一個(gè) E 類(lèi)型的對(duì)象,但是 Kotlin 認(rèn)為 E 是不可空的。在接下來(lái)的 Kotlin 版本中可能會(huì)解決這個(gè)問(wèn)題,但是現(xiàn)在當(dāng)你在你的工程中使用類(lèi)似接口的時(shí)候,一定要注意: 

  1. val queue: Queue<String?> = LinkedList() 
  2. queue.peek()?.toInt() 

內(nèi)部 it

當(dāng)一個(gè) lambda 表達(dá)式只有一個(gè)參數(shù)的時(shí)候,你可以在你的代碼中將其省略,并用 it 代替。

  • it:?jiǎn)螀?shù)的內(nèi)部名稱(chēng)。當(dāng)你表達(dá)式只有一個(gè)參數(shù)的時(shí)候,這是一個(gè)很有用的特性,聲明的過(guò)程可以省略(就像 ->),并且參數(shù)名稱(chēng)為 it。

問(wèn)題是,當(dāng)你的代碼中存在向下面例子一樣的嵌套函數(shù)的時(shí)候: 

  1. val list = listOf("foo.bar""baz.qux"
  2. list.forEach { 
  3.     it.split(".").forEach { 
  4.         println(it) 
  5.     } 

it 參數(shù)會(huì)混淆。解決方法就是像下面這樣顯示的聲明: 

  1. list.forEach { item -> 
  2.     item.split(".").forEach { part -> 
  3.         println(part) 
  4.     } 

看起來(lái)是不是好多了!

隱藏的復(fù)制

注意觀(guān)察下面的類(lèi):

  1. data class Foo(val bars: MutableList) 

data 類(lèi)提供了一系列的方法,并且你可以通過(guò)拷貝得到其鏡像。猜猜下面的代碼會(huì)輸出什么? 

  1. val bars = mutableListOf("foobar""wombar"
  2. val foo0 = Foo(bars) 
  3. val foo1 = foo0.copy() 
  4. bars.add("oops"
  5. println(foo1.bars.joinToString()) 

 控制臺(tái)會(huì)輸出 foobar, wombar, oops。問(wèn)題出在 copy 方法并沒(méi)有真正地復(fù)制一個(gè)完整的對(duì)象, 而是復(fù)制了對(duì)象的引用。當(dāng)你忘記編寫(xiě)單元測(cè)試類(lèi),并且將你的 data 類(lèi)按照不可變類(lèi)來(lái)傳遞的時(shí)候,就可能出現(xiàn)這種問(wèn)題。

解決方法就是當(dāng)你使用 data 類(lèi)的時(shí)候一定要多加小心,并且當(dāng)你必須將其作為值對(duì)象的時(shí)候,像下面這樣:

  1. data class Foo(val bars: List) 
  • data 類(lèi)還有一個(gè)問(wèn)題:其 equals / hashCode 方法所用到的屬性不可變。你只能通過(guò)手工重寫(xiě)這些方法的方式來(lái)修改返回值。謹(jǐn)記上面這一點(diǎn)。

內(nèi)部方法暴露

仔細(xì)思考下面的例子: 

  1. class MyApi { 
  2.     fun operation0() { 
  3.     } 
  4.     internal fun hiddenOperation() {             
  5.     } 

當(dāng)你在 Kotlin 的項(xiàng)目中引用這個(gè)類(lèi)的時(shí)候,internal 關(guān)鍵字是生效的。但是當(dāng)你從一個(gè) Java 項(xiàng)目中使用的時(shí)候,hiddenOperation 就變成了一個(gè)公共方法!為了避免這種情況,我建議使用接口的方式來(lái)隱藏實(shí)現(xiàn)的細(xì)節(jié): 

  1. interface MyApi { 
  2.     fun operation0() 
  3. class MyApiImpl: MyApi { 
  4.     override fun operation0() { 
  5.     } 
  6.     internal fun hiddenOperation() { 
  7.     } 

特殊的全局?jǐn)U展

毫無(wú)疑問(wèn),擴(kuò)展函數(shù)的功能非常重要。但通常,能力越大責(zé)任越大。例如,你可以編寫(xiě)全局的 JDK 類(lèi)擴(kuò)展函數(shù)。但是當(dāng)這個(gè)函數(shù)只在本地上下文中有意義,卻是全局可見(jiàn)的時(shí)候,就會(huì)帶來(lái)很多麻煩。 

  1. fun String.extractCustomerName() : String { 
  2.     // ... 

每個(gè)跳轉(zhuǎn)到你的方法的人都會(huì)不知所措。所以我認(rèn)為在你編寫(xiě)這樣的方法之前務(wù)必三思。下面就是一個(gè)建議: 

  1. /** 
  2.  * Returns an element of this [List] wrapped in an Optional 
  3.  * which is empty if `idx` is out of bounds. 
  4.  */ 
  5. fun <T> List<T>.getIfPresent(idx: Int) = 
  6.         if (idx >= size) { 
  7.             Optional.empty() 
  8.         } else { 
  9.             Optional.of(get(idx)) 
  10.         } 
  11. /** 
  12.  * Negates `isPresent`. 
  13.  */ 
  14. fun <T> Optional<T>.isNotPresent() = isPresent.not() 

lambdas Unit 返回值 vs Java SAM 轉(zhuǎn)換

如果你的函數(shù)參數(shù)是 lambdas 表達(dá)式,并且返回值類(lèi)型是 Unit 的時(shí)候,你可以省略return 關(guān)鍵字: 

  1. fun consumeText(text: String, fn: (String) -> Unit) { 
  2. // usage 
  3. consumeText("foo") { 
  4.     println(it) 

這是一個(gè)很有趣的特性,但是當(dāng)你在 Java 代碼中調(diào)用該方法的時(shí)候會(huì)比較尷尬: 

  1. consumeText("foo", (text) -> { 
  2.     System.out.println(text); 
  3.     return Unit.INSTANCE; 
  4. }); 

這對(duì)于 Java 端來(lái)說(shuō)是不友好的,如果你想在 Java 中成功調(diào)用該方法,你需要定義如下接口: 

  1. nterface StringConsumer { 
  2.     fun consume(text: String) 
  3. fun consumeText(text: String, fn: StringConsumer) { 

然后你就能使用 Java 的 SAM 轉(zhuǎn)換。

  1. consumeText("foo", System.out::println); 

但是在 Kotlin 這邊看起來(lái)就很糟糕了: 

  1. consumeText("foo", object: StringConsumer { 
  2.     override fun consume(text: String) { 
  3.         println(text) 
  4.     } 
  5. }) 

問(wèn)題關(guān)鍵點(diǎn)在于只有 Java 支持 SAM 轉(zhuǎn)換,Kotlin 并不支持。我的建議是簡(jiǎn)單的場(chǎng)景中,只是用 Java 的 SAM 接口作為一個(gè)消費(fèi)者: 

  1. fun consumeText(text: String, fn: Consumer<String>) { 
  2. // usage from Kotlin 
  3. consumeText("foo", Consumer { 
  4.     println(it) 
  5. }) 
  6. // usage from Java 
  7. consumeText("foo", System.out::println); 

Java 中使用不可變集合

Kotlin 提供了 JDK 集合類(lèi)的不可變版本。 

  1. fun createSet(): Set<String> = setOf("foo"
  2. // ... 
  3. createSet().add("bar") // oops, compile error 

這是一個(gè)很好的補(bǔ)充。但是當(dāng)你在看 Java JDK 的 Set 類(lèi) API 的時(shí)候會(huì)發(fā)現(xiàn):

  1. createSet().add("bar"); // UnsupportedOperationException 

當(dāng)你嘗試修改這個(gè) Set 的時(shí)候,就會(huì)拋出這個(gè)異常,就像你使用了Collections.unmodifiableSet() 方法一樣。我不知道這種情況是否合理,但是你在使用 Kotlin 不可變版本的 Java 集合類(lèi)的時(shí)候,需要謹(jǐn)記這一點(diǎn)。

接口中沒(méi)有重載

Kotlin 在接口上不支持使用 @JvmOverloads 注解,當(dāng)然 override 也不行。 

  1. interface Foo { 
  2.     @JvmOverloads // OUCH! 
  3.     fun bar(qux: String) 
  4. class FooImpl : Foo { 
  5.  
  6.     @JvmOverloads // YIKES! 
  7.     override fun bar(qux: String) { 
  8.     } 

你只能像下面這樣手動(dòng)定義: 

  1. interface Foo { 
  2.     fun bar() 
  3.     fun bar(qux: String) 

要記住你可以使用 Kotlin 中的 KEEP (Kotlin Evolution and Enhancement Process) 來(lái)改善。KEEP 與 Java 中的 JEP 類(lèi)似,但是與 JEP 相比要簡(jiǎn)潔許多。

總結(jié)

Kotlin 現(xiàn)下很流行,并且我也認(rèn)為他是一個(gè)增強(qiáng)版的 Java。但是在使用 Kotlin 的時(shí)候你仍需要保持清醒,尤其是當(dāng)你身處各種各樣的關(guān)于 Kotlin 的宣傳之中時(shí)。如果你要使用 Kotlin 的話(huà),一定要注意我們?cè)谏厦嫣岬降?Kotlin 相關(guān)的缺陷。

***我還是想說(shuō),上述提到的問(wèn)題都比較容易解決,并且不會(huì)對(duì)語(yǔ)言的使用方面帶來(lái)本質(zhì)性的傷害。

責(zé)任編輯:未麗燕 來(lái)源: 程序師
相關(guān)推薦

2021-03-01 15:52:14

開(kāi)源開(kāi)源軟件陷阱

2018-06-11 15:26:47

云計(jì)算企業(yè)云陷阱

2021-04-22 11:22:12

云計(jì)算數(shù)據(jù)遷移混合云

2019-02-11 10:00:23

云網(wǎng)絡(luò)云平臺(tái)微服務(wù)

2022-09-19 09:19:24

云存儲(chǔ)TCO云服務(wù)

2014-07-11 10:23:54

2015-06-02 11:10:20

2011-06-07 15:34:15

2024-11-06 09:44:22

2022-10-11 07:20:56

YAML字符串語(yǔ)言

2025-05-28 03:20:00

布爾值編程True

2024-03-04 13:23:00

數(shù)字化轉(zhuǎn)型

2022-03-17 18:26:42

微前端

2021-07-16 10:27:07

ITIT領(lǐng)導(dǎo)IT管理

2013-01-06 10:15:02

大數(shù)據(jù)分析數(shù)據(jù)分析師大數(shù)據(jù)

2013-06-21 10:15:33

BYOD

2024-03-04 07:50:04

Python字符編碼網(wǎng)絡(luò)通信

2024-01-22 08:20:40

Python編程語(yǔ)言代碼陷阱

2018-11-18 16:31:14

Kubernetes監(jiān)控容器

2021-02-28 13:19:42

大數(shù)據(jù)IT數(shù)據(jù)管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)