寫了多年的Java,直到看到Kotlin,原來程式碼可以如此優雅!
寫了多年的Java,直到看到Kotlin,原來程式碼可以如此優雅!
如果你是像我一樣是一名 優秀 的Java開發者 _ ,而且已經想用kotlin來實現你的程式,那麼,抱歉!不要用Java的語法思維來寫Kotlin,不要讓kotlin的優雅埋沒。如果你沒有Java開發經驗,下面的內容也對你會有幫助。。。
1.儘可能的少用 !!
個人感覺對於Null的檢查是Koltin最語法糖的東西了,強制在編碼過程中考慮空指標,因此 《十億美元的錯誤》 ,也許你不會再有這個機會犯錯了(也許可以說成,你賺了十億美金 _ )。
首先需要介紹是 !! 操作符。
!! 操作符: 這是為空指標愛好者準備的 ,非空斷言運算子(!!)將任何值轉換為非空型別,若該值為空則丟擲異常。我們可以寫 a!! ,這會返回一個非空的 a 值 (例如:在我們例子中的 String)或者如果 a 為空,就會丟擲一個 空指標 異常:
val b = a!!.length
所以,我們能不用 !! 操作符就不要用。。。
下面介紹幾種方式避免使用 !! 操作符
1).多用 val 而不是 var
在 Kotlin 中 val 代表只讀, var 代表可變。建議儘可能多的使用 val 。 val 是執行緒安全的,並且必須在定義時初始化,所以不需要擔心 null 的問題。只需要注意 val 在某些情況下也是可變的就行了。
val 和 var 是用於表示屬性是否有 getter/setter:
var:同時有 getter 和 setter
val:只有 getter
所以,強烈推薦能用 val 的地方就用 val 。
2).使用 lateinit
有些時候並不能用val,比如在 spring boot 介面測試中就不能使用 val ,對於這種情況,可以使用 lateinit 關鍵字。。
依賴倒轉,物件的建立是通過spring完成的,而val要求定義的時候初始化
@RunWith(SpringRunner::class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApplicationTests {
val log = LogFactory.getLog(ApplicationTests::class.java)!!
@Autowired
lateinit var restTemplate: TestRestTemplate
@Test
fun `GET when given quanke then returns "Hello, quanke"`() {
// Given
val name = "quanke"
// When
val body = restTemplate.getForObject("/users/hello/{name}", String::class.java, name)
// Then
assertThat(body).isEqualTo("Hello, $name")
}
}
注意:lateinit很好用,但也有坑
訪問未初始化的 lateinit 屬性會導致 UninitializedPropertyAccessException。
lateinit 不支援基礎資料型別,比如 Int。對於基礎資料型別,我們可以這樣:
private var mNumber: Int by Delegates.notNull<Int>()
3).Elvis 操作符
當b為可空引用時,我們可以使用if表示式處理
val l: Int = if (b != null) b.length else -1
但更加優雅的方式是使用Elvis 操作符 ?:
val l = b?.length ?: -1
如果 ?: 左側表示式非空,elvis 操作符就返回其左側表示式,否則返回右側表示式。
注意:當且僅當左側為空時,才會對右側表示式求值。
4).也許可以嘗試一下let函式
let 函式一般與安全呼叫操作符一起使用,我們首先介紹安全呼叫操作 ?.
b?.length
如果 b 非空,就返回 b.length,否則返回 null,這個表示式的型別是 Int?。
安全呼叫在鏈式呼叫中很有用。例如,如果一個員工Quanke可能會(或者不會)分配給一個部門, 並且可能有另外一個員工是該部門的負責人,那麼獲取 Quanke 所在部門負責人(如果有的話)的名字,我們寫作:
quanke?.department?.head?.name
如果任意一個屬性(環節)為空,這個鏈式呼叫就會返回 null。
如果要只對非空值執行某個操作,安全呼叫操作符可以與 let 一起使用:
val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
item?.let { println(it) } // 輸出 A 並忽略 null
}
還有一種常見的錯誤(放ide裡面試試就知道什麼錯誤了):
private var a: String? = null
fun aLetDemo() {
if (a != null) {
test(a)
}
}
我們可以這樣:
private var a: String? = null
fun aLetDemo() {
if (a != null) {
test(a!!)
}
}
但是這樣的後果就是你還是需要在test函式裡處理空指標。
我們充分利用 ?. 加 let 的特點,更加優雅的解決這個編譯錯誤,如下
private var a: String? = null
fun aLetDemo() {
if (a != null) {
a?.let {
test(it)
}
}
}
2.少寫點Util類和繼承
很多時候框架提供給我的方法是比較原子,或者某些常用的方法框架並沒有提供,Java一般是寫一個工具類:
public final class StringUtil {
/**
* 刪除所有的標點符號
*
* @param str 處理的字串
*/
public static String trimPunct(String str) {
if(isEmpty(str)){
return "";
}
return str.replaceAll("[\pP\p{Punct}]", "");
}
}
Kotlin可以通過擴充套件函式的形式實現:
/**
* 刪除所有的標點符號
*
* @param str 處理的字串
*/
fun String.trimPunct(): String {
return if (this.isEmpty()) {
""
} else this.replace("[\pP\p{Punct}]".toRegex(), "")
}
呼叫:
fun main(args: Array<String>) {
val a = "把我的標點符號去掉吧,全科。"
print(a.trimPunct())
}
列印:
把我的標點符號去掉吧全科
3.別再用+號拼接字串: 用字串模板!
無論是Java還是Android開發,我們都會用到字串拼接,比如進行日誌輸出等等。在Kotlin中,支援字串模板,我們可以很輕鬆的完成一個字串數的拼接,當然你可能會說使用StringBuilder效能更好,比如:
val site = "http://woquanke.com"
val sb: StringBuilder = StringBuilder()
sb.append("我的部落格名字叫《我全科》,我的部落格地址是:")
sb.append(site)
println(sb.toString())
但kotlin的字串模版可以優雅的做這個事情:
val site = "http://woquanke.com"
println("我的部落格名字叫《我全科》,我的部落格地址是:$site")
4.也許可以忘記getters/setters了
我們經常建立一些只儲存資料的類。在這些類中,一些標準函式往往是操作一下ide生成的。在 Kotlin 中,這叫做 資料類 並標記為 data:
data class User(val name: String, val age: Int)
data class 自動生成getter,setting,hashcode和equals等方法
5.請忘記三元運算子
在 Kotlin 中,if是一個表示式,即它會返回一個值。因此就不需要三元運算子(條件 ? 然後 : 否則),因為普通的 if 就能勝任這個角色。
// 作為表示式
val max = if (a > b) a else b
6.哪裡還有switch
when 取代了類java 語言的 switch 操作符。其最簡單的形式如下:
when (x) {
-> print("x == 1")
-> print("x == 2")
else -> { // 注意這個塊
print("x is neither 1 nor 2")
}
}
如果很多分支需要用相同的方式處理,則可以把多個分支條件放在一起,用逗號分隔:
when (x) {
, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
可以用任意表示式(而不只是常量)作為分支條件
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
也可以檢測一個值在(in)或者不在(!in)一個區間或者集合中:
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
另一種可能性是檢測一個值是(is)或者不是(!is)一個特定型別的值。注意: 由於智慧轉換,你可以訪問該型別的方法和屬性而無需任何額外的檢測。
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
when 也可以用來取代 if-else if鏈。 如果不提供引數,所有的分支條件都是簡單的布林表示式,而當一個分支的條件為真時則執行該分支:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
7.去你的ClassCastException
Kotlin智慧轉換(Smart Casts)
對於不可變的值,Kotlin一般不需要顯式轉換物件型別,編譯器能跟蹤is檢查型別,在需要時會自動插入型別轉換程式碼(安全):
fun classCast(a: Any) {
if (a is String) {
print(a.length) //編譯器自動把a轉換為String型別
}
}
Kotlin編譯器很聰明,能識別反向檢查型別!is操作符,會自動插入型別轉換程式碼:
if (a !is String) return
print(a.length) //編譯器自動把x轉換為String型別:
// ||右側, a自動轉換為String型別
if (a !is String || a.length == 0) return
// &&右側, a自動轉換為String型別
if (a is String && a.length > 0) {
print(a.length) // a 自動轉換為字串
}
//智慧轉換(smart casts)也用於when表示式和while迴圈
when (a) {
is Int -> print(a + 1)
is String -> print(a.length + 1)
is IntArray -> print(a.sum())
}
如果不能保證變數在型別檢查 is / !is 操作符和變數使用之間不可改變時,智慧轉換不能用。智慧轉換的適用條件或規則:
val區域性變數-總是適用!
val屬性-適用於private或internal,或者型別檢查is/!is在宣告屬性的同一模組中執行;
不適用於open的屬性,或者具有自定義getter的屬性!
var區域性變數—適用於變數在型別檢查和使用之間沒有修改,且不在修改它的lambda中捕獲!
var屬性-不適用(因為該變數可隨時被修改)
安全(可空)轉換-操作符as?
為避免丟擲異常,可用安全轉換操作符 as? ,在失敗時返回null
val a: String? = b as? String
儘管 as? 右邊是一個非空型別String,但是 as? 轉換失敗時返回可空(null),換句話說就是, as? 函式引數String不能為null,但是as?函式的返回值可以是null
8.真的要習慣Koltin的for迴圈,太強大了
Kotlin沒有Java中的for(初始值;條件;增減步長)這個規則。但是Kotlin中對於for迴圈語句新增了其他的規則,來滿足剛提到的規則。
for迴圈提供迭代器用來遍歷任何東西
for迴圈陣列被編譯為一個基於索引的迴圈,它不會建立一個迭代器物件
新增的規則,去滿足for(初始值;條件;增減步長)這個規則
遞增
關鍵字:until
範圍:until[n,m) => 即大於等於n,小於m
例:
// 迴圈5次,且步長為1的遞增
for (i in 0 until 5){
print("i => $i ")
}
輸出結果為
i => 0 i => 1 i => 2 i => 3 i => 4
遞減
關鍵字:downTo
範圍:downTo[n,m] => 即小於等於n,大於等於m ,n > m
例:
// 迴圈5次,且步長為1的遞減
for (i in 15 downTo 11){
print("i => $i ")
}
輸出結果為:
i => 15 i => 14 i => 13 i => 12 i => 11
符號(’ .. ‘) 表示遞增的迴圈的另外一種操作
使用符號( ‘..’).
範圍:..[n,m]=> 即大於等於n,小於等於m
和until的區別,一是簡便性。二是範圍的不同。
例:
print("使用 符號`..`的列印結果
")
for (i in 20 .. 25){
print("i => $i ")
}
println()
print("使用until的列印結果
")
for (i in 20 until 25){
print("i => $i ")
}
輸出結果為:
使用 符號 .. 的列印結果
i => 20 i => 21 i => 22 i => 23 i => 24 i => 25
使用until的列印結果
i => 20 i => 21 i => 22 i => 23 i => 24
設定步長
關鍵字:step
例:
for (i in 10 until 16 step 2){
print("i => $i ")
}
輸出結果為:
i => 10 i => 12 i => 14
迭代
for迴圈提供一個迭代器用來遍歷任何東西。
for迴圈陣列被編譯為一個基於索引的迴圈,它不會建立一個迭代器物件
遍歷字串
此用法在資料型別章節中的字串型別中用到過。還不甚清楚的可以檢視 Kotlin——最詳細的資料型別介紹。
例:
for (i in "abcdefg"){
print("i => $i ")
}
輸出結果為:
i => a i => b i => c i => d i => e i => f i => g
遍歷陣列
此用法在資料型別章節中的陣列型別中用到過。還不甚清楚的可以檢視 Kotlin——最詳細的資料型別介紹。
例:
var arrayListOne = arrayOf(10,20,30,40,50)
for (i in arrayListOne){
print("i => $i ")
}
輸出結果為:
i => 10 i => 20 i => 30 i => 40 i => 50
使用陣列的indices屬性遍歷
例:
var arrayListTwo = arrayOf(1,3,5,7,9)
for (i in arrayListTwo.indices){
println("arrayListTwo[$i] => " + arrayListTwo[i])
}
輸出結果為:
arrayListTwo[0] => 1
arrayListTwo[1] => 3
arrayListTwo[2] => 5
arrayListTwo[3] => 7
arrayListTwo[4] => 9
使用陣列的withIndex()方法遍歷
例:
var arrayListTwo = arrayOf(1,3,5,7,9)
for ((index,value) in arrayListTwo.withIndex()){
println("index => $index value => $value")
}
輸出結果為:
index => 0 value => 1
index => 1 value => 3
index => 2 value => 5
index => 3 value => 7
index => 4 value => 9
使用列表或陣列的擴充套件函式遍歷
陣列或列表有一個成員或擴充套件函式iterator()實現了Iterator介面,且該介面提供了next()與hasNext()兩個成員或擴充套件函式
其一般和while迴圈一起使用
可以檢視Array.kt這個類。可以看見其中的iterator()函式,而這個函式實現了Iterator介面。
/**
* Creates an iterator for iterating over the elements of the array.
*/
public operator fun iterator(): Iterator<T>
檢視Iterator.kt這個介面類,這個介面提供了hasNext()函式和next()函式。
public interface Iterator<out T> {
/**
* Returns the next element in the iteration.
*/
public operator fun next(): T
/**
* Returns `true` if the iteration has more elements.
*/
public operator fun hasNext(): Boolean
}
例:
var arrayListThree = arrayOf(2,`a`,3,false,9)
var iterator: Iterator<Any> = arrayListThree.iterator()
while (iterator.hasNext()){
println(iterator.next())
}
輸出結果為:
a
false
9.kotlin stream 真心可以
流式處理給我們的集合操作帶來了很大的方便,其實Java 8 一樣支援流式處理,我只是想在這裡推廣一下 stream。
下面舉例:
val names = arrayOf("Amy", "Alex", "Bob", "Cindy", "Jeff", "Jack", "Sunny", "Sara", "Steven")
//篩選S開頭的人名
val sName = names.filter { it.startsWith("S") }.toList()
//按首字母分組並排序
val nameGroup = names.groupBy { it[0] }
.toSortedMap( Comparator { key1, key2 -> key1.compareTo(key2) })
10.少寫點方法過載
因為kotlin支援預設引數,所以在封裝方法時會少很多的方法過載的。
如果沒有預設引數的需要實現下面的日誌列印,需要寫多個方法:
fun log(tag: String, content: String) {
println("tag:$tag-->$content")
}
fun log( content: String) {
log("quanke","")
}
使用預設引數只需要一個方法:
fun log(tag: String="quanke", content: String) {
println("tag:$tag-->$content")
}
最後我還是想說:抱歉!不要用Java的語法思維來寫Kotlin!
相關文章
- 如何提高Java程式碼質量-優雅的寫程式碼Java
- 如何寫出優雅的程式碼?
- 寫出優雅的js程式碼JS
- 你的 JS 程式碼本可以更加優雅JS
- 助您寫出優雅的Java程式碼七點建議Java
- 編寫更優雅的 JavaScript 程式碼JavaScript
- 如何寫出優雅耐看的JavaScript程式碼JavaScript
- 編寫優雅程式碼的最佳實踐
- 程式碼質量管理——如何寫出優雅的程式碼
- 玩轉 React(三)- JavaScript程式碼裡寫HTML一樣可以很優雅ReactJavaScriptHTML
- 優雅的程式碼
- golang如何優雅的編寫事務程式碼Golang
- 我是如何將業務程式碼寫優雅的
- 如何用 SpringBoot 優雅的寫程式碼Spring Boot
- 原來如此
- 【優雅寫程式碼系統】springboot+mybatis+pagehelper+mybatisplus+druid教你如何優雅寫程式碼Spring BootMyBatisUI
- 風變程式設計:原來Python學習也可以如此有趣!程式設計Python
- 編寫高效且優雅的 Python 程式碼(1)Python
- PHPer這樣寫程式碼也許更優雅PHP
- 程式碼這樣寫更優雅 (Python 版)Python
- 程式碼這樣寫更優雅(Python 版)Python
- 如何用 es6+ 寫出優雅的 js 程式碼JS
- 30個python教你學會優雅的寫程式碼Python
- 看promise教你如何優雅的寫js非同步程式碼PromiseJS非同步
- 【硬核】23種設計模式娓娓道來,助你優雅的編寫出漂亮程式碼!設計模式
- 程式碼這樣寫不止於優雅(Python版)Python
- 程式碼這樣寫不止於優雅(Python 版)Python
- 原來 Java 遠端除錯如此簡單Java除錯
- xmake入門,構建專案原來可以如此簡單
- 全面吃透JAVA Stream流操作,讓程式碼更加的優雅Java
- 如何優雅的打包前端程式碼前端
- 如何優雅的寫Markdown
- 停止使用迴圈 教你用underscore優雅的寫程式碼
- 終於可以愉快的擼Java非同步程式碼了!Java非同步
- 你見過哪些優雅的 Java 程式碼最佳化技巧?Java
- 原來MSSQL還可以這樣調優SQL
- Java程式碼編寫、程式碼優化技巧總結Java優化
- 不依賴 Gulp、Babel、WebPack,還能優雅地寫程式碼嗎?BabelWeb