人生苦短,要用Kotlin
這是一種對程序猿更為友好的語言,可以減少開發(fā)者的工作量,原本由開發(fā)者干的事情,其實很多都可以由編譯器實現(xiàn)了,這是一種更為高級的語言。Java雖然嚴謹,但卻過于繁瑣,太啰嗦了,一個小事情卻要寫大量的代碼,而且有些代碼又是非常機械式的,在實際編碼過程中都是用IDE來自動生成。Java,C,C++,Object C這些都是上世紀的編程語言。
現(xiàn)在到了新時代了,編程也發(fā)展了很多,像lambda表達式,函數(shù)式編程,等等一些新的概念和范式在涌現(xiàn)。所以就有了新時代的編程語言,像水果的Swift,Groovy,Scala,以及Java陣營的Kotlin。Kotlin是新一代的編程語言,與Java完美融合,簡潔,方便,可以大大提高程序可讀性,特別是對于Android開發(fā)者來說。水果推出了Swift以解放水果平臺的開發(fā)者,而Kotlin就是來解放Android開發(fā)者的。
雖然說Kotlin可以用在任何可以用Java的地方,但目前主要就是兩大領域服務端,以及Android應用開發(fā),特別是有了Google官方的支持,所以Kotlin對于Android開發(fā)者的意義更為重大,身為一個Android猿,是一定要學習一下這門現(xiàn)代的編程語言的,因為當你學過了之后 ,你會發(fā)現(xiàn),之前寫的代碼都是在浪費生命。
Development environment setup
有三種方式
命令行
其實,這是最好的方式,因為配置起來非常的方便。到官網(wǎng)去下載編譯器,解壓,然后把kotlinc/bin/放到PATH環(huán)境變量里面,就可以了。如果要配置Vim,還需要安裝一下插件,大神們早就把插件準備好了,只需要下載,然后按照官方方法安裝即可,其實就是把解壓后的東西拷貝到相應的目錄里面就好了。
Idea IntellJ
這個看官方文檔就可以了,孤未親測,如遇困難請自行Google。
Android Studio
因為Kotlin官已支持了Android Studio,而Google也支持了,總而言之就是在Android Studio中可以直接使用Kotlin。所以, Android Stuido 3.0以后的版本無需特殊配置,就可以用例Kotlin了。
對于剛開始學習Kotlin而言呢,孤推薦使用命令行的方式,而不要使用Android Studio,特別是直接創(chuàng)建一個基于Kotlin的Android項目,因為此時對語言還不夠熟悉,直接上項目,會迷失在項目配置,frameworks以及語言基礎之中。剛學習一門語言的時候要先學習基本的語法以及語言本身的特性,這最好先繞開框架和項目,會更容易上手一些。
Hello world
這是所有編程語言的入門必學課程,目的是讓學習者快速的體驗一下一門語言,我們也不用多想,照著一個字母,一個字母的把示例敲進去就好了:
- 選擇喜歡的文本編輯器,如Vim hello.kt,Kotlin的文件擴展名是*.kt,我們遵循就好。
- 一字不差的敲進去:
package hello
fun main(args: Array<String>) {
println("Hello, world")
}
然后,保存文件
- 回到命令行,編譯源碼,如果一切順利會得到一個叫hello.jar的文件,這就是kotlin的最終輸出,也就是它的目標文件.
kotlinc hello.kt -include-runtime -d hello.jar
- 運行,這里跟Kotlin其實已經(jīng)沒啥關系了,因為經(jīng)過編譯得到的是一個標準的Jar文件,像運行其他jar一樣運行就好了:
java -jar hello.jar
就會得到輸出Hello, world到此,第一個Kotlin程序已經(jīng)完成,是不是很酷,已經(jīng)迫不及待的想深入學習了!往下看吧。
The basics
語句結構
一行一個語句(先不糾結語句與表達式的區(qū)別),不用加分號,不用打分號,光這個就可以節(jié)省多少時間呢?是不是感覺人生都浪費在了分號上面。如果想在一行寫多個語句,前面的要加上分號。
縮進規(guī)則與Java一致,用四個空格,也可以用tab,或者不加縮進,只要沒人打你。
語句塊需要加上花括號{}。總之,語句結構與Java很類似。
變量
用var來聲明變量,用val來聲明常量,因為Kotlin是靜態(tài)強類型語言(也就是說每個變量在編譯的時候必須知道類型)聲明時需要帶上類型,方法是在變量名的后面加冒號,空格跟上類型名字,與Pascal差不多。如果聲明時直接定義,則可以不用指定類型,編譯器會根據(jù)定義表達式來推測它的類型。示例:
var str: String
val i: Int
var str = "Hello, world"
語句和表達式
主要想說一下語句和表達式的區(qū)別,簡單來說就是表達式是有值的,可以放在變量賦值的右邊,而語句是沒有值的,不能放在賦值的右邊
基本運算
不多說了,跟Java一樣
注釋
這個跟Java也一樣:
// 單行注釋
/* / 多行注釋
/* */ documentation
函數(shù)
以fun關鍵字來定義一個函數(shù)格式為:fun 函數(shù)名(參數(shù)): 返回類型 {函數(shù)體},如:
fun foo(name: String): Int {
return name.length()
}
命名參數(shù)和默認值,調(diào)用函數(shù)時可以把參數(shù)的名字帶上,以增加可讀性。聲明函數(shù)時可以用默認值 ,以更好的支持函數(shù)的重載。如:
fun foo(name: String, number: Int = 42, toUpper: Boolean = false): String {}
使用時,可以指定參數(shù)的名字:
foo("a)
foo("b", number = 1)
foo("c", toUpper = true)
foo(name = "d", number = 2, toUpper = false)
表達式體如果一個函數(shù)體內(nèi)只有一個表達式,且有返回值時,那么,可以直接把返回值放在函數(shù) 的后面,如:
fun foo(name: String): String = name.toUpperCase()
甚至還可以把返回類型的聲明給省略掉,如:
fun foo(name: String) = name.toUpperCase()
跟Java不一樣的是,Kotlin的函數(shù)可以聲明為toplevel也就是跟class一個級別,也就是說不必非放在類里面,也就是說跟C和C++是類似的。此外,還可以函數(shù)賦值給一個變量,這個變量就像其他變量一樣。
類與對象
類的聲明與對象創(chuàng)建
用class來聲明一個類型,用:來繼承父類或者實現(xiàn)接口,不需要使用new來創(chuàng)建對象:
class Person {
var name: String
var age: Int
}
假如,一個類,是空的,沒有內(nèi)容,那么花括號{}是可以省略的:
class Person
創(chuàng)建對象:
var someone = Person()
Primary constructor
構造方法,有所謂的primary constructor,可以直接寫在類名的后面:
class Person constructor(name: String)
一般情況下,constructor 可以省略掉:
class Person(name: String)
初始化塊因為primary constructor不能包含代碼,所以,想要做些初始化工作就可以放在初始化塊里面(initializer block),也可以在定義屬性時直接使用:
class Person(name: String) {
var firstName: String = name
init {
println("First initializer block that prints ${name}")
}
}
一般情況下,如果聲明的屬性變量在primary constructor中都有賦值(通過initializer block)的話,可以有更簡潔的表達方式:
class Person(var name: String, var age: Int)
這相當于:
class Person(theName: String, theAge: Int) {
var name: String = theName var age: Int = theAge
}
如果primary construct前面要聲明屬性,或者有annotation的話,關鍵字constructor不能省略:
class Person public @Inect constructor(var name: String)
Secondary constructor
如果primary constructor不能滿足需求怎么辦呢?還可以聲明其他constructor,所謂的secondary constructor:
class Person {
var name: String constructor(name: String){
this.name = name
}
}
是不是看起來舒服一些,因為跟Java一樣了,可以把primary constfuctor和second constructor聯(lián)合起來一起用:
class Person(var name: String) {
constructor(name: String, parrent: Person) : this(name) {
parrent.addChild(this)
}
}
這里要把secondary construct盡可能delegate到primary constructor,這里的delegate的意思就是primary constructor會在second constructor之前 執(zhí)行,還有就是initiailzer block都是在primary construct中執(zhí)行的,這就能保證initiliazer block在second constructor之前執(zhí)行。即使沒有顯示的聲明primary constructor,編譯器還是會生成一個默認的primary constructor以及把secondary constructor默認的delegate到primary constrcutor上面。也就是說,會保證primary constructor以及initializer block執(zhí)行在second constructor前面:
class Constructors {
init {
println("Initializer block")
}
constructor(i: Int) {
println("second constructor")
}
}
fun main(args: Array<String>) {
val c = Constructors(3)
}
輸出:
Initializer block
second constructor
屬性和訪問方法
Kotlin會為聲明的屬性生成默認的setter和getter:
class Person(var name: Strring, var age: Int)
val p = Person("Kevin", 24)
p.getName() // 返回"Kevin"
p.setAge(32) // age變成了32
如果想自定義setter和getter,也是可以的:
class Person {
var name: String
set(n: String) {
if (n == null || n == "") {
name = "Unkown"
} else {
name = n
}
}
get() {
if (name == "Unkwon") {
return "Nobody"
}
return name
}
}
定義類的方法
跟聲明普通函數(shù)一樣,只不過是放在了類里面:
class Person(val name: String, val age: Int) {
fun report() = "My name is $name, and I'm $age"
}
如果,要覆寫父類的方法,需要使用在方法聲明時加上override關鍵字。
class Doggy(val name: String) : Animal {
override fun yell() = "Barking from $name"
}
訪問權限
訪問權限也跟Java類似分為public,protected,private以及internal,前三個意義也都一樣,只不過默認值不一樣,在Java里,如果對成員沒有指明,則是package scope,也就是同一個package可以訪問,但是Kotlin默認是public的。
internal是module內(nèi)部可見,有點類似于Java中的package,但是module定義跟package不一樣,module是一組編譯在一起的Kotlin文件,它跟編譯打包有關系,簡單的理解它的范圍要比package要大。
還有就是類,默認是不可被繼承的,相當于final class。如果想要允許繼承就要在聲明類的時候加上open。
字串
概念就不說了,大部分與Java一模一樣的,像支持的方法等。唯一需要說的就是字串模板,就是說把其他類型轉化為字串時,有較Java更為方便的方式:直接用$來把變量嵌入到字串之中,如:
val msg = "Error 1"
val count = 32
print("We got message $msg") //等同于"We got message " + msg
print("Total is $count") // Total is 32
lambda表達式
首先要介紹一個概念,高階函數(shù),其實就是把另外函數(shù)當作參數(shù)的函數(shù),或者說產(chǎn)生一個函數(shù),也即把函數(shù)作為返回值 的函數(shù)。前面說過,函數(shù)是一級對象,可以像常規(guī)變量一樣來使用,所以,就能把函數(shù)作為參數(shù)或者返回值來使用高階函數(shù)。lambda表達式就是為高階函數(shù)更方便使用而生的。
lambda 表達式
作為新時代的編程語言,都會支持函數(shù)式編程,而lambda表達 式又是函數(shù)式編程里面必不可少的一份子。其實啥是lambda表達式呢?說的簡單點就是沒有名字的函數(shù),非常簡短的,通常都是一兩句話的沒有名字的函數(shù)。就是長這個樣子{A, B -> C},這里面A,B是參數(shù),C是表達式,如:
val sum = { x: Int, y: Int -> x + y }
其中,參數(shù)的類型是可以省略的,因為編譯器能從上下文中推測出來:
max(strings, { a, b -> a.length < b.length }
表達式部分,可以不止一個,最后一個表達式作為返回值。
當把一個lambda表達作為最后一參數(shù),傳給某個函數(shù)時,可以直接把lambda表達式寫在參數(shù)的外面,比如:
val product = items.fold(1) { acc, e -> acc * e }
而當lambda是唯一的參數(shù)時,也可以把參數(shù)的括號省略掉:
run { println("Hello, world") }
還有就是,如果lambda表達中只有一個參數(shù),那么參數(shù)也可以省略,直接寫表達式:
eval{ x * x }
函數(shù)類型
前面提到了函數(shù)是可以像普通變量一樣使用的一級類,也就是說它是一個類型。它的具體形式是: (A, B)->C,其中括號內(nèi)的是參數(shù),C是返回類型,如:
val sum: (Int, Int)->Int = { x, y -> x + y }
val square: (Int)->Int = { x -> x * x }
為啥要提一下函數(shù)類型呢,因為有時需要聲明高階函數(shù):
fun walk(f: (Int)->Int)
fun run(f: ()->Unit)
Unit是一個特殊的返回值,相當于void,意思就是此函數(shù)沒有返回值。
集合
其實大部分跟Java是一樣的。只不過有一些函數(shù)式的操作,要多注意使用,從而讓代碼更簡潔,如:
- 遍歷
- 過濾
- 映射
- 排序
- 折疊
- 分組
- 歸類
這些操作,對于大家應該都不難理解,就不一一解釋了,來斷代碼就知道了:
fun collectionTests() {
val list = listOf("Apple", "Google", "Microsoft", "Facebook", "Twitter", "Intel", "QualComm", "Tesla")
// 遍歷,以進行某種操作
list.forEach{ println(it) }
//按條件進行過濾,返回條件為true的
val short = list.filter { it.length < 6 }
println(short) // [Apple, Intel, Tesla]
// 把列表元素映射成為另外一種元素
val lenList = list.map{ it.length }
println("Length of each item $lenList") //Length of each item [5, 6, 9, 8, 7, 5, 8, 5]
// 按某種條件進行排序
val ordered = list.sortedBy { it.length }
println("Sorted by length $ordered") // Sorted by length [Apple, Intel, Tesla, Google, Twitter, Facebook, QualComm, Microsoft]
// 折疊,用累積的結果繼續(xù)遍歷
val joint = list.fold("", {partial, item -> if (partial != "") "$partial, $item" else item })
println("Joint list with comma $joint") // Joint list with comma Apple, Google, Microsoft, Facebook, Twitter, Intel, QualComm, Tesla
//分組,用某種條件 把列表分成兩組
val (first, second) = list.partition { it.length < 6 }
println("Length shorter than 6 $first") // Length shorter than 6 [Apple, Intel, Tesla]
println("Longer than 6 $second") // Longer than 6 [Google, Microsoft, Facebook, Twitter, QualComm]
// 歸類,按某種方法把元素歸類,之后變成了一個Map
val bucket = list.groupBy { it.length }
println("$bucket is a map now") //{5=[Apple, Intel, Tesla], 6=[Google], 9=[Microsoft], 8=[Facebook, QualComm], 7=[Twitter]} is a map now
}
null處理
為了有效的減少空指針異常,Kotlin加入了Nullable類型,核心的原理是這樣的:聲明類型的時候要明確的告訴編譯器,這個變量是否可能為null,如果可能為null,那么可以賦null給這個變量,并且在使用此變量時必須檢查是否為null;假如這個變量不可能為null,那么是不可以賦null給此變量的。也就是說,編譯器會幫忙做一些檢查,以減少NullPointerException的發(fā)生。
Nullable變量
默認的變量聲明都是不可為null的,如:
var safe: String
safe = null // 會有compile error
要想允許變量為null,要在類型后面加一個問號,以告訴編譯器這是一個nullable類型:
var danger: String?
danger = null // OKay
使用時,nullable不能直接使用,必須檢查是否為null:
safe.length // okay
danger.length // compile error, danger could be null
檢查Nullable的真?zhèn)?/h4>
可以用傳統(tǒng)方式:
val len = if (danger != null) danger.length else -1
Safe call
既然有Nullable類型,自然就有配套的方式來更方便的使用它:
val len = danger?.length
如果danger是null就返回null,否則返回長度,注意它的返回值是一個Int?(又是一個Nullable類型)。這個還能鏈起來:
bob?.department?.head?.name
如果任何一環(huán)為null,則直接返回null。是不是感覺省了好多if (a == null)判斷。
Elvis operator
假如不能接受safe call返回的null,咋辦呢?想提供默認值的呢?也有方式:
val len = danger?.length
println(len ?: -1)
稍有點繞哈,首先,danger?.length返回一個Int?吧,那么?:的作用就是如果len是null,那么就返回-1,否則返回它的值。
強制取值符!!
它的作用是如果Nullable變量為null就拋出NullPointerException,如果正常的話就取其值,返回的類型是一個non-null類型:
val len = danger!!.length // get length or NullPointerException
盡管,編譯器可以幫助我們做一些事情,但是現(xiàn)實的項目中的大量的NPE并不是直接來源于,可以方便追蹤的賦值為null,而多是發(fā)生在多線程環(huán)境中,以及非常復雜的邏輯之中,編譯器能否追蹤到并警示,還有待考察。另外,就是雖有利器,但是要運用恰當,何時用允許null,何時不允許,還是要靠工程師的設計能力,比如盡可能返回空列表,空Map,或者空字串,而不是直接簡單的返回null,這就能減少一定的NPE。
Exercises
光是看書或者看教程是比較乏味的,學習編程最重要的是要上手去練習,這樣能加深印象,更好的理解書中或者教程中所講的概念和知識點。官方也準備了一個非常好的練習項目叫Kotlin-koans,非常適配初學習者來練手。
下面說一下如何使用這個練習項目:
- 到官網(wǎng)去下載后,解壓
- 用Android Studio打開此項目,一切提示都回答yes
- 要想運行測試前需要先編譯一下項目,否則會提示找不到基礎的測試類,找到Gradle窗口,一般在右側,點開找到kotlin-koans->Tasks->build->build,運行它
- 現(xiàn)在就可以用先進的TDD方式來學習Kotlin了,在Project視圖下面,可以看到kotlin-koans項目,里面有兩個,一個是java,一個是tests,這兩個目錄里面的子目錄都是一一對應的,先運行tests下面的,會失敗,然后編輯java/下面的對應的代碼,直到測試通過。
Essence of Kotlin
致此,我們可以看出Kotlin這門語言的設計的核心理念:簡潔,這是Kotlin的核心理念,所以我們看到,一些機械的,重復的,可以從上下文中推測 出來的都 可以省略,以增加可讀性。我們在使用Kotlin的時候要踐行此理念,把語言的特性發(fā)揮到最大。
當然,簡潔,不是犧牲可讀性的方式來縮短代碼,而是要使用語言中的標準的簡潔的表達方式,比如lambda表達式,省略參數(shù)等。
要注意參考Kotlin conventions以及Android Kotlin conventions以寫出更加簡潔和容易理解的代碼。
Android dev setup
我們來新建一個項目,用純Kotlin實現(xiàn)一個Hello, world Android應用,來展示一下如何在Android中使用Kotlin:
注意: 這里使用的是Android Studio 3.1.2版本,默認就支持Kotlin,如果使用小于3.0的版本需要安裝Kotlin插件,可自行Google,孤還是建議先升級AS吧。
-
新建一個項目,其實流程跟新建一個普通Android Studio項目是一樣一樣的,從Android Studio3.0起,新建項目時就會有一個Checkbox,問你要不要添加Kotlin。這里把它選上。
Step 1 -
就直接下一步就好
Step 2 -
Next,創(chuàng)建一個empty activity
Step 3 - Finish
Step 4 -
布局跟其他新建的Android項目無差別
Step 5 -
代碼已經(jīng)是Kotlin的了
Step 6 - 直接顯示"Hello, world"略顯無聊,所以加一下點擊事件:
class HelloActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hello)
val colorTable = listOf("#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff")
val label = findViewById<TextView>(R.id.label)
label.setOnClickListener { view ->
val randomIndex = (Math.random() * colorTable.size).toInt()
view.setBackgroundColor(Color.parseColor(colorTable[randomIndex]))
}
}
}
其實,整體來看,布局和項目的結構還是按照Android的方式來,唯一的不同是代碼可以用Kotlin來寫了。
Good to go
至此,Kotlin就算入門了,可以使用Kotlin來構建應用程序了,或者在你的項目中應用Kotlin了。
參考資料和有用的資料分享
- 官方文檔
- Awesome Kotlin Resources
- Kotlin and Android
- Resources to Learn Kotlin
- Learn Kotlin in Y minutes
原文鏈接:http://toughcoder.net/blog/2018/05/17/introduction-to-kotlin-programming-language/