Kotlin簡介
Kotlin來源于一個島嶼的名字,全稱是Kotlin Island,是英語【科特林島】之意,這個小島位于俄羅斯的圣彼得堡附近。之所以要命名為Kotlin,是因為Kotlin的主要開發(fā)工作就是由位于圣彼得堡分公司團(tuán)隊完成的。Kotlin是由JetBrains公司開發(fā)與設(shè)計的。
早在2011年,JetBrains就公布了Kotlin的第一個版本,并在2012年將其開源。
2016年,Kotlin發(fā)布了1.0的正式版本,并在自家IDE開發(fā)工具中加入了對Kotlin的支持。
2017年,Google宣布Kotlin正式成為Android一級開發(fā)語言,Android Studio也加入了對Kotlin的支持。
兩年之后,Google又在2019年的I/O大會上宣布,Kotlin已經(jīng)成為Android的第一開發(fā)語言。
在開發(fā)Kotlin之前,JetBrains團(tuán)隊一直使用Java來創(chuàng)建他們的IDE以及使用Java進(jìn)行業(yè)務(wù)邏輯開發(fā)。之所以開發(fā)Kotlin,是因為JetBrains 的工程師們在使用Java開發(fā)應(yīng)用程序的過程中,發(fā)現(xiàn)了大量的問題。為了提升開發(fā)效率以及解決使用Java開發(fā)帶來的問題,在借鑒了其他語言后,他們決定開發(fā)一款致力于解決Java問題的編程語言Kotlin。JetBrains深知開發(fā)者的需求和痛處,在孜孜不倦地為開發(fā)者提供最實用、最高效的IDE的同時,也為開發(fā)者提供全新的編程語言以解決目前的技術(shù)問題。
或許你會產(chǎn)生一些疑惑:Android操作系統(tǒng)明明是由Google開發(fā)的,為什么JetBrains作為一個第三方公司,卻能夠自己設(shè)計出一門編程語言來開發(fā)Android應(yīng)用程序呢?
先大概熟悉一下Java語言的運行機(jī)制。編程語言大致可以分為兩類:編譯型語言和解釋型語言。編譯型語言的特點是編譯器會將我們編寫的源代碼一次性地編譯成計算機(jī)可識別的二進(jìn)制文件,然后計算機(jī)直接執(zhí)行,像C和C++都屬于編譯型語言。
解釋型語言則完全不一樣,它有一個解釋器,在程序運行時,解釋器會一行行地讀取我們編寫的源代碼,然后實時地將這些源代碼解釋成計算機(jī)可識別的二進(jìn)制數(shù)據(jù)后再執(zhí)行,因此解釋型語言通常效率會差一些,像Python和JavaScript都屬于解釋型語言。
那Java是屬于編譯型語言還是解釋型語言呢?
雖然Java代碼確實是要先編譯再運行的,但是Java代碼編譯之后生成的并不是計算機(jī)可識別的二進(jìn)制文件,而是一種特殊的class文件,這種class文件只有Java虛擬機(jī)才能識別,而這個Java虛擬機(jī)擔(dān)當(dāng)?shù)钠鋵嵕褪墙忉屍鞯慕巧?,它會在程序運行時將編譯后的class文件解釋成計算機(jī)可識別的二進(jìn)制數(shù)據(jù)
后再執(zhí)行,因此,準(zhǔn)確來講,Java屬于解釋型語言。了解了Java語言的運行機(jī)制之后,可以看出,其實Java虛擬機(jī)并不直接和你編寫的Java代碼打交道,而是和編譯之后生成的class文件打交道。那么如果開發(fā)了一門新的編程語言,然后自己做了個編譯器,讓它將這門新語言的代碼編譯成同樣規(guī)格的
class文件,Java虛擬機(jī)能不能識別呢?沒錯,這其實就是Kotlin的工作原理了。Java虛擬機(jī)不關(guān)心class文件是從Java編譯來的,還是從Kotlin編譯來的,只要是符合規(guī)格的class文件,它都能識別。也正是這個原因,JetBrains才能以一個第三方公司的身份設(shè)計出一門用來開發(fā)Android應(yīng)用程序的編程語言。
下面數(shù)據(jù)是來對比kotlin和C++在不同的長度運算過程的耗時情況:
| 算式 | kotlin耗時 | C++耗時 |
|---|---|---|
| 2組 【9個9】相乘 | 6ms | 1ms |
| 10組【9個9】相乘 | 17ms | 10ms |
| 100組 【9個9】相乘 | 288ms | 25ms |
| 1000組【9個9】相乘 | 95171ms | 237ms |
在此應(yīng)用場景下,C++算法明顯優(yōu)于Kotlin,效率提升明顯。因為C++是編譯型的,直接將源碼編譯成機(jī)器代碼,可以直接運行;而Kotlin是解釋型,源碼被編譯成二進(jìn)制字節(jié)碼,但不是機(jī)器可以識別的語言,由虛擬機(jī)解釋才能執(zhí)行,效率會慢一些。
Kotlin的優(yōu)勢
Kotlin的語法簡潔,同樣的功能,Kotlin開發(fā)的代碼量可能會比Java少50%,可能更多。
Kotlin增加了很多高級語言的很特性,大大的提高了開發(fā)效率。
Kotlin和Java100%兼容,Kotlin可直接調(diào)用Java編寫的代碼,也可以無縫使用Java第三方的開源庫,使得Kotlin在加入新特性的同時,繼承了Java的全部財富。
Kotlin基礎(chǔ)語法
變量
val(value):不可變變量,它的值在初始化以后就無法再次修改,相當(dāng)于java中final變量
var(variable):可變變量,對應(yīng)java中的普通變量
基礎(chǔ)類型
| Type | Bit width | 備注 |
|---|---|---|
| Double | 64 | Kotlin沒有double |
| Float | 32 | Kotlin沒有float |
| Long | 64 | Kotlin沒有l(wèi)ong |
| Int | 32 | kotlin沒有int/Integer |
| Short | 16 | kotlin沒有short |
| Char | 16 | Kotlin沒有char |
| Byte | 8 | Kotlin沒有byte |
| Boolean | 8 | Kotlin沒有boolean |
基礎(chǔ)類型,包含我們常見的數(shù)字類型,布爾類型,字符類型,及這些類型組成的數(shù)組,這些常遇到的概念,統(tǒng)一歸納為基礎(chǔ)類型
從上圖也可以看出,在Kotlin語言體系中,是沒有原始類型這個概念的,這就意味著,在Kotlin中,一切都是對象。
對比后,從某種程度上講,Java的類型系統(tǒng)并不是完全面向?qū)ο蟮模驗樗嬖谠碱愋?,而原始類型并不屬于對象,而Kotlin則不一樣,它從語言設(shè)計的層面就規(guī)避了這個問題,類型系統(tǒng)是完全面向?qū)ο蟮摹?/p>
空安全
Kotlin強制要求開發(fā)者在定義變量的時候,就指定這個變量是否可能為null,對于可能為null的變量,我們需要在聲明的時候,在變量類型后面加一個問號“?”
val i: Double = null // 編譯器報錯
val j: Double? = null // 編譯通過
數(shù)字類型
val i = 1
val l = 1234567L
val d = 13.14
val f = 13.14F
val hex = 0xAF
val b = 0b01010101
整數(shù)默認(rèn)會被推導(dǎo)為“Int”類型;
Long 類型,我們則需要使用“L”后綴;小
數(shù)默認(rèn)會被推導(dǎo)為“Double”,我們不需要使用“D”后綴;
Float 類型,我們需要使用“F”后綴;
使用“0x”,來代表十六進(jìn)制字面量;
使用“0b”,來代表二進(jìn)制字面量。
對于數(shù)字類型的轉(zhuǎn)換,Kotlin與Java的轉(zhuǎn)換行為是不一樣的,Java可以隱式轉(zhuǎn)換數(shù)字類型,而Kotlin更推崇顯示轉(zhuǎn)換,在一些嚴(yán)謹(jǐn)?shù)倪壿嬇袛嘀校龅揭恍┻吔鐥l件問題,隱式轉(zhuǎn)換會因存在精度問題而導(dǎo)致的一些Bug不容易被排除出來。而在Kotlin中,提供了toLong(),toByte()、toShort()、toInt()、toLong()、toFloat()、toDouble()、toChar()等,使得代碼可讀性更強,也易維護(hù)。
布爾類型
兩種值,true和false,布爾類型是支持一些邏輯操作的,如下:
“&”代表“與運算”;
“|”代表“或運算”;
“!”代表“非運算”;
“&&”和“||”分別代表它們對應(yīng)的“短路邏輯運算”
val i = 1
val j = 2
val k = 3
val isTrue: Boolean = i < j && j < k
字符:Char
Char用于代表單個字符,‘A’、’B’、’C’、寫法和Java類似
字符串:String
val s = "Hello Kotlin!"
寫法和Java類似,但是Kotlin提供了非常簡潔的字符串模板:
val name = "Kotlin"
print("Hello $name!")
/* ↑
直接在字符串中訪問變量
*/
// 輸出結(jié)果:
Hello Kotlin!
當(dāng)然,在Java中也能實現(xiàn),但需要使用“+”進(jìn)行拼接,在字符串格式相對復(fù)雜的情況下啊,代碼就會顯得很臃腫。
如果需要在字符串中引用更加復(fù)雜的變量,則需要使用花括號括起來:
val array = arrayOf("Java", "Kotlin")
print("Hello ${array.get(1)}!")
/* ↑
復(fù)雜的變量,使用${}
*/
// 輸出結(jié)果:
Hello Kotlin!
此外,Kotlin還新增了原始字符串,是用三個引號來表示,可用來存放復(fù)雜發(fā)多行文本,打印格式和定義格式保持同步:
val s = """
原始字符串
所見即所得。 """
println(s)
數(shù)組
在Kotlin中,一般會使用arrayOf()來創(chuàng)建數(shù)組,括號當(dāng)中可以用于傳遞數(shù)組元素進(jìn)行初始化,同時,Kotlin編譯器也會根據(jù)傳入的參數(shù)進(jìn)行類型推導(dǎo):
val arrayInt = arrayOf(1, 2, 3)
val arrayString = arrayOf("apple", "pear")
如上,arrayInt的類型會被推導(dǎo)為整形數(shù)組,arrayString會被推導(dǎo)為字符串?dāng)?shù)組。
獲取數(shù)組長度方式和一些操作和集合類似:
val array = arrayOf("apple", "pear")
println("Size is ${array.size}")
println("First element is ${array[0]}")
// 輸出結(jié)果:
Size is 2
First element is apple
函數(shù)聲明
在Kotlin中,函數(shù)的聲明和Java不太一樣:
/*
關(guān)鍵字 函數(shù)名 參數(shù)類型 返回值類型
↓ ↓ ↓ ↓ */
fun helloFunction(name: String): String {
return "Hello $name !"
}
/* ↑
花括號內(nèi)為:函數(shù)體
*/
1)使用了fun關(guān)鍵字來定義函數(shù)
2)函數(shù)名稱,駝峰命名規(guī)則
3)是以 (name: String) 這樣的形式傳遞的,這代表了參數(shù)類型為 String 類型
4)返回值類型,跟在參數(shù)后面
5)函數(shù)體(業(yè)務(wù)邏輯)
如果函數(shù)體只有一行代碼,這種情況下,其實可以省略函數(shù)體的花括號,直接使用“=”來連接,將其變成一種類似變量賦值函數(shù)的形式:
fun helloFunction(name: String): String = "Hello $name !"
這種寫法,稱之為 單一表達(dá)式函數(shù),其中“retuen”是需要去掉的
繼續(xù)簡化,由于Kotlin是支持類型推導(dǎo)的,返回值的類型可省略:
fun helloFunction(name: String) = "Hello $name !"
代碼極其簡潔,Kotlin的優(yōu)勢不僅僅體現(xiàn)在函數(shù)聲明上,在函數(shù)調(diào)用的地方,它也有很多獨到之處。
函數(shù)調(diào)用
代碼風(fēng)格基本和Java一致
helloFunction("Kotlin")
但Kotlin提供了一些新的特性,命名參數(shù)??梢岳斫鉃椋壕褪撬试S我們在調(diào)用函數(shù)的時候傳入“形參的名字”
helloFunction(name = "Kotlin")
fun createUser(
name: String,
age: Int,
gender: Int,
friendCount: Int,
feedCount: Int,
likeCount: Long,
commentCount: Int
) {
//..
}
// Java調(diào)用方式:
createUser("Tom", 30, 1, 78, 2093, 10937, 3285)
// Kotlin調(diào)用方式:
createUser(
name = "Tom",
age = 30,
gender = 1,
friendCount = 78,
feedCount = 2093,
likeCount = 10937,
commentCount = 3285
)
可以看到,把函數(shù)的形參加了進(jìn)來,形參和實參通過“=”連接,建立了兩者的對應(yīng)關(guān)系,其實這樣代碼的可讀性會更強。
此外,Kotlin還支持參數(shù)默認(rèn)值:
fun createUser(
name: String,
age: Int,
gender: Int = 1,
friendCount: Int = 0,
feedCount: Int = 0,
likeCount: Long = 0L,
commentCount: Int = 0
) {
//..
}
createUser(
name = "Tom",
age = 30,
commentCount = 3285
)
以上方法調(diào)用時只傳了三個參數(shù),剩余的參數(shù)沒有傳,但是Kotlin編譯器會自動幫忙填充默認(rèn)值
但在Java中,需要定義三個參數(shù)的重載方法,才能實現(xiàn)。其實,在一些場景下還是提高了開發(fā)效率的。
流程控制
在Kotlin中,流程控制主要有if,when,for,white,這些語句可以控制代碼的執(zhí)行流程,具體如下:
if
if 語句,在程序中主要用于邏輯判斷,和Java基本一致,此外,還可以作為表達(dá)式來使用:
val i = 1
val message = if (i > 0) "Big" else "Small"
print(message)
//輸出結(jié)果:
Big
另外,在Kotlin中明確規(guī)定了類型分為可空類型和不可空類型,會遇到一些可空的變量,判斷是否為空:
fun getLength(text: String?): Int {
return if (text != null) text.length else 0
}
類似這種邏輯判斷出現(xiàn)的很頻繁,為此,Kotlin提供了一種簡寫,稱之為Elvis表達(dá)式
fun getLength(text: String?): Int {
return text?.length ?: 0
}
when
when 語句,在程序當(dāng)中也是主要用于邏輯判斷的,當(dāng)代碼邏輯只有兩個分支的時候,一般用if/else,而大于兩個邏輯分支的時候,一般使用when:
val i: Int = 1
when(i) {
1 -> print("一")
2 -> print("二")
else -> print("i 不是一也不是二")
}
// 輸出結(jié)果:
一
when語句和Java中的switch case語句很像,不過Kotlin的when更加強大,同時可以作為表達(dá)式,為變量賦值:
val i: Int = 1
val message = when(i) {
1 -> "一"
2 -> "二"
else -> "i 不是一也不是二" // 如果去掉這行,會報錯
}
print(message)
循環(huán)遍歷 while,for
while語法和Java類似,不做過多講解
Kotlin在for循環(huán)做了大幅度調(diào)修改,Java中最常用的for-i循環(huán)在kotlin中直接被舍棄了,而Java中另外一種for-each循環(huán)則被Kotlin進(jìn)行了加強,變成了for-in循環(huán)。
val array = arrayOf(1, 2, 3)
for (i in array) {
println(i)
}
此外,Kotlin還支持迭代一個區(qū)間。
在Kotlin中用關(guān)鍵字 .. 來創(chuàng)建一個兩端閉區(qū)間,兩邊指定區(qū)間的左右端點:
val range = 0..10 // 代表 [0, 10]
可通過for-in來遍歷這個區(qū)間:
for (i in range){
printIn(i)
}
在很多時候,需要創(chuàng)建單閉區(qū)間,可通過until關(guān)鍵字來創(chuàng)建一個左閉右開的區(qū)間:
val range = 0 until 10 // 代表 [0, 10)
默認(rèn)情況下,for-in循環(huán)每次執(zhí)行循環(huán)時會在區(qū)間范圍內(nèi)遞增1,即i++的效果,如果想跳過某一些元素,可以使用step關(guān)鍵字:
for(i in range step 2){
printIn(i)
}
// 打?。? 2 4 6 8
相當(dāng)于在遍歷range區(qū)間時,每次遞增2,即可i=i+2的效果。
不管是..,還是until關(guān)鍵字都要求區(qū)間的左端小于等于區(qū)間的右端,即這兩個關(guān)鍵字創(chuàng)建的都是一個升序區(qū)間,如果想創(chuàng)建一個降序區(qū)間,可使用downTo關(guān)鍵字:
for (i in 6 downTo 0 step 2) {
println(i)
}
// 輸出結(jié)果:
6 4 2 0
類與對象
在Kotlin中也是使用class關(guān)鍵字來聲明一個類的,這一點和Java是保持一致的。
class Person{
var name = “”
var age = 0
fun eat(){
printIn(name + “is eating. He is ”+age +” years old.”)
}
}
對這個類進(jìn)行實例化:
val p = Person()
Kotlin實例化一個類的方式和Java基本類似,只是去掉了new關(guān)鍵字。
繼承
我們先定義一個Student類:
class Student{
var sno =””
var grade = 0
}
怎么讓Student繼承Person呢?
在Kotlin中任何一個非抽象類默認(rèn)是不可以被繼承的。相當(dāng)于Java中給類聲明了final。之所以這么設(shè)計,其實和val關(guān)鍵字的原因是差不多的,類和變量一樣,最好是不可變的。
如果需要被繼承,在Person類前面加上open關(guān)鍵字即可:
open class Person{
}
加上open關(guān)鍵字之后,就是在主動告知Kotlin編譯器,Person這個類時專門為繼承而設(shè)計的,它就會被允許繼承。
在Java中繼承的關(guān)鍵字是extends,而在Kotlin中變成了一個冒號:
class Student : Person(){
var sno = “”
var grade = 0
}
接口
一般會在接口中定義一系列的抽象行為,然后由具體的類去實現(xiàn)。
Java中實現(xiàn)接口使用的是關(guān)鍵字implements,而Kotlin中統(tǒng)一使用冒號,中間用逗號進(jìn)行分隔。
class Student(name:String,age int):Person(name,age),Study{
override fun doHomework(){
}
}
修飾符
Kotlin中也有四種,public,private,protected 和internal
區(qū)別在于:在Java中默認(rèn)的是default,而Kotlin中public是默認(rèn)修飾符。
權(quán)限跟Java稍微有出入:
protected: Java中表示對當(dāng)前類,子類和同一個包路徑下的類可見。在Kotlin表示只對當(dāng)前類和子類可見。
internal:只對同一個模塊的類可見,。如:開發(fā)了一個模塊給別人使用,但一些函數(shù)只允許在模塊內(nèi)部調(diào)用,不想暴露在外部,可以用internal來聲明。
| 修飾 | Java | Kotlin |
|---|---|---|
| public | 所有類可見 | 所有類可見(默認(rèn)) |
| private | 當(dāng)前類可見 | 當(dāng)前類可見 |
| protected | 當(dāng)前類,子類和同一個包路徑下的類可見 | 只對當(dāng)前類和子類可見 |
| default | 同一個包路徑下面的類可見(默認(rèn)) | 無) |
| internal | 無 | 同一個模塊中的類可見 |
數(shù)據(jù)類和單列類
數(shù)據(jù)類通常需要重寫equals(),hashCode().toStirng()這幾個方法,其中equals()用于判斷兩個數(shù)據(jù)類是否相等,這些方法會讓代碼顯得比較臃腫,有些時候卻又不得不寫。但在Kotlin中,這種情況就變得極其簡單,可通過data關(guān)鍵字來聲明
data class CellPhone(val brand:String,val price:Double)
當(dāng)在一個類的前面聲明了data關(guān)鍵字時,就表明它是一個數(shù)據(jù)類,Kotlin會根據(jù)主構(gòu)造函數(shù)中的參數(shù)自動生成equals(),hashCode(),toString()等固定方法,一定程度上減少了工作量。
最后再講一個Kotlin中的單例類
在java中寫單例的常規(guī)寫法,首先為了進(jìn)制外部創(chuàng)建Singleton的實例,需要用private關(guān)鍵字將Singleton的構(gòu)造函數(shù)私有化,然后給外部提供一個getInstance()靜態(tài)方法用于獲取Singleton的實例。在getInstance()方法中判斷當(dāng)前緩存的Singleton實例為null,就創(chuàng)建一個新的實例,否則直接返回緩存的實例。
然而,在Kotlin中,會將一些固定的,重復(fù)的邏輯實現(xiàn)了隱藏,只暴露一個最簡單的用法。在Kotlin中創(chuàng)建單例,只需要將class關(guān)鍵字改為object關(guān)鍵字即可。Kotlin會自動一個Singleton實例,且會保證全局只會存在一個Singleton實例。
小結(jié)
Kotlin 和 Java 的語法很像,但在一些細(xì)節(jié)之處,Kotlin 總會有一些新的東西。熟悉完基礎(chǔ)語法之后,我們可以來看看 Kotlin 在這方面都做了以下改進(jìn):
支持類型推導(dǎo);
代碼末尾不需要分號;
字符串模板;
原始字符串,支持復(fù)雜文本格式;
單一表達(dá)式函數(shù),簡潔且符合直覺;
函數(shù)參數(shù)支持默認(rèn)值,替代 Builder 模式的同時,可讀性還很強;
if 和 when 可以作為表達(dá)式。
同時,JetBrains 也非常清楚開發(fā)者在什么情況下容易出錯,所以,它在語言層面也做了很多改進(jìn):
強制區(qū)分“可為空變量類型”和“不可為空變量類型”,規(guī)避空指針異常;
推崇不可變性(val),對于沒有修改需求的變量,IDE 會智能提示開發(fā)者將“var”改為“val”;
基礎(chǔ)類型不支持隱式類型轉(zhuǎn)換,這能避免很多隱藏的問題;
數(shù)組訪問行為與集合統(tǒng)一,不會出現(xiàn) array.length、list.size 不統(tǒng)一的情況;
函數(shù)調(diào)用支持命名參數(shù),提高可讀性,在后續(xù)維護(hù)代碼的時候不易出錯;
when 表達(dá)式,強制要求邏輯分支完整,盡可能的減少了邏輯上的漏洞