三篇文章帶你快速入門Kotlin(上)
文章目錄
三篇文章帶你快速入門 Kotlin(上)
Kotlin的發展歷程
2011年,JetBrains釋出了Kotlin的第一個版本,並在2012年將其開源。
2016年Kotlin釋出了1.0正式版,代表著Kotlin語言已經足夠成熟和穩定了,並且JetBrains也在自家的旗艦IDE開發工具IntelliJ IDEA中加入了Kotlin的支援。
2017年Google宣佈Kotlin正式成為Android開發一級語言,並且Android Studio也加入了對Kotlin的支援。
2019年Google正式宣佈了Kotlin First,未來提供的官方API也將會以Kotlin版本為主。
Kotlin相比於Java的優勢
語法更加簡潔,對於同樣的功能,使用Kotlin開發的程式碼量可能會比使用Java開發的減少50%甚至更多。
語法更加高階,相比於Java比較老舊的語法,Kotlin增加了很多現代高階語言的語法特性,使得開發效率大大提升。
語言更加安全,Kotlin幾乎杜絕了空指標這個全球崩潰率最高的異常。
和Java是100%相容的,Kotlin可以直接呼叫使用Java編寫的程式碼,也可以無縫使用Java第三方的開源庫。這使得Kotlin在加入了諸多新特性的同時,還繼承了Java的全部財富。
Kotlin的工作原理
Kotlin可以做到和Java 100%相容,這主要是得益於Java虛擬機器的工作機制。
其實Java虛擬機器並不會直接和你編寫的Java程式碼打交道,而是和編譯之後生成的class檔案打交道。
而Kotlin也有一個自己的編譯器,它可以將Kotlin程式碼也編譯成同樣規格的class檔案。
Java虛擬機器不會關心class檔案是從Java編譯來的,還是從Kotlin編譯來的,只要是符合規格的class檔案,它都能識別。
也正是這個原因,JetBrains才能以一個第三方公司的身份設計出一門用來開發Android應用程式的程式語言。
函式和變數
變數
Kotlin中定義一個變數,只允許在變數前宣告兩種關鍵字:val和var。
-
val(value的簡寫的簡寫)用來宣告一個不可變的變數,這種變數在初始賦值之後就再也不能重新賦值,對應Java中的final變數。
-
var(variable的簡寫的簡寫)用來宣告一個可變的變數,這種變數在初始賦值之後仍然可以再被重新賦值複製,對應Java中的非final變數。
fun main() { val a = 10 var b = 5 b = b + 3 println("a = " + a) println("b = " + b) }
內嵌表示式
在Kotlin中,我們可以直接將表示式寫在字串裡面,即使是構建非常複雜的字串,也會變得輕而易舉。
Kotlin中字串內嵌表示式的語法規則如下:
"hello, ${obj.name}. nice to meet you!"
當表示式中僅有一個變數的時候,還可以將兩邊的大括號省略:
"hello, $name. nice to meet you!"
函式
定義一個函式的語法規則如下:
fun methodName(param1: Int, param2: Int): Int {
return 0
}
當一個函式的函式體中只有一行程式碼時,可以使用單行程式碼函式的語法糖:
fun methodName(param1: Int, param2: Int) = 0
使用這種寫法,可以直接將唯一的一行程式碼寫在函式定義的尾部,中間用等號連線即可
return關鍵字也可以省略,等號足以表達返回值的意思。
Kotlin還擁有出色的型別推導機制,可以自動推匯出返回值的型別。
邏輯控制
if條件語句
fun largerNumber4(num1: Int, num2: Int): Int {
val value = if (num1 > num2) {
num1
} else {
num2
}
return value
}
Kotlin中的if語句相比於Java有一個額外的功能:它是可以有返回值的,返回值就是if語句每一個條件中最後一行程式碼的返回值。
fun largerNumber(num1: Int, num2: Int): Int {
val value = if (num1 > num2) {
num1
} else {
num2
}
return value
}
仔細觀察上述程式碼,你會發現value其實是一個多餘的變數,我們可以直接將if語句返回,這樣程式碼將會變得更加精簡,如下所示:
fun largerNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
當一個函式只有一行程式碼時,可以省略函式體部分,直接將這一行程式碼使用等號串連在函式定義的尾部。雖然largerNumber()函式不止只有一行程式碼,但是它和只有一行程式碼的作用是相同的,只是return了一下if語句的返回值而已,符合該語法糖的使用條件。那麼我們就可以將程式碼進一步精簡:
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {
num1
} else {
num2
}
最後,還可以將上述程式碼再精簡一下,直接壓縮成一行程式碼:
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
when條件語句
當需要判斷的條件非常多的時候,可以考慮使用when語句來替代if語句。
類似與switch語句又遠比switch語句強大的多
分支可以時表示式,也可以時具體數值。
fun getScore1(name: String) = when (name) {
"Tom" -> 86
"Jack" -> 77
"Jim" -> 33
"Lily" -> 44
else -> 0
}
高階用法:when不帶引數寫法,不常用,但是擴充套件性很強
fun getScore2(name: String) = when {
name == "Tom" -> 86
name.equals("Jack") -> 77
name.endsWith("Jim") -> 33
name.startsWith("Lily") -> 44
else -> 0
}
when不帶引數,多引數
fun getScore3(name: String, name1: String) = when {
name == "Tom" -> 86
name.equals("Jack") -> 77
name1.endsWith("Jim") -> 33
name1.startsWith("Lily") -> 44
else -> 0
}
for-in迴圈語句
我們可以使用如下Kotlin程式碼來表示一個區間:
val range = 0..10
上述程式碼表示建立了一個0到10的區間,並且兩端都是閉區間,這意味著0到10這兩個端點都是包含在區間中的,用數學的方式表達出來就是[0, 10]。
也可以使用until關鍵字來建立一個左閉右開的區間:
val range = 0 until 10
如果你想跳過其中的一些元素,可以使用step關鍵字:
fun main() {
for (i in 0 until 10 step 2) {
println(i)
}
}
如果你想建立一個降序的區間,可以使用downTo關鍵字:
fun main() {
for (i in 10 downTo 1) {
println(i)
}
}
控制迴圈語句Break
退出當前迴圈
@ 標籤可以指定退出外層迴圈
fun for(){
out@ for (i in 0..5) {
println("i$i")
for (j in 0..3) {
println("j$j")
if (j == 1) {
break@out
}
}
}
}
控制迴圈語句Continue
退出本次迴圈
fun for(){
out@ for (i in 0..5) {
println("i$i")
for (j in 0..3) {
println("j$j")
if (j == 1) {
continue@out
}
}
}
}
物件導向程式設計
類與物件
可以使用如下程式碼定義一個類,以及宣告它所擁有的欄位和函式:
class Person {
var name = ""
var age = 0
fun eat() {
println("name:" + name + " age:" + age)
}
}
然後使用如下程式碼建立物件,並對物件進行操作:
fun main() {
val p = Person()
p.name = "yx"
p.age = 66
p.eat()
}
繼承
Kotlin中一個類預設是不可以被繼承的,如果想要讓一個類可以被繼承,需要主動宣告open關鍵字:
open class Person {
…
}
要讓另一個類去繼承Person類,則需要使用冒號關鍵字:
class Student : Person() {
var sno = ""
var grade = 0
}
現在Student類中就自動擁有了Person類中的欄位和函式,還可以定義自己獨有的欄位和函式。
構造器
- 主構造器
constructor 關鍵字如果沒有任何註解或修飾可以省略
如果沒有為非抽象類定義任何主次構造器,系統會提供一個無引數的主構造器。預設修飾為public
一旦程式設計師為一個類提供了構造器,系統將不再為該類提供構造器
- 次構造器
任何一個類只能有一個主建構函式,但可以有多個次建構函式
如果定義了主構造器,那麼次構造器必須委託主構造器
如果沒有定義柱構造器則不用委託主構造器
class Student(val sno: String, val grade: Int) : Person() {
init {
println("sno:$sno")
println("grade:$grade")
}
}
init 關鍵字為初始化時回撥的邏輯塊
介面
Kotlin中定義介面的關鍵字和Java中是相同的,都是使用的interface:
interface Study {
fun readBooks()
fun doHomework()
}
而Kotlin中實現介面的關鍵字變數了冒號,和繼承使用的是同樣的關鍵字:
class Artist(var name: String) : Study {
override fun readBook() {
println("name:$name readBook")
}
override fun doHomework() {
println("name:$name doHomework")
}
}
資料類
Kotlin中使用data關鍵字可以定義一個資料類:
data class Cellphone(val brand: String, val price: Double)
Kotlin會根據資料類的主建構函式中的引數將equals()、hashCode()、toString()等固定且無實際邏輯意義的方法自動生成,從而大大簡少了開發的工作量。
data class CellPhone(val brand: String, val price: Double)
class Cellphone1(val brand: String, val price: Double)
fun main() {
val str = "{'userCode':'demo','userName':'Demo Group'}"
val a = EncryptUtils.decryptBase64AES(
str.toByteArray(),
"PHA".toByteArray(),
"CBC",
"".toByteArray()
)
println(a)
val cellPhone1 = CellPhone("apple", 1000.0)
val cellPhone2 = CellPhone("apple", 1000.0)
println(cellPhone1)
println(cellPhone1 == cellPhone2)
val cellPhone3 = Cellphone1("apple", 1000.0)
val cellPhone4 = Cellphone1("apple", 1000.0)
println(cellPhone3)
println(cellPhone3 == cellPhone4)
}
單例類
Kotlin中使用object關鍵字可以定義一個單例類:
object Singleton {
fun singletonTest() {
println("singletonTest is called.")
}
}
而呼叫單例類中的函式比較類似於Java中靜態方法的呼叫方式:
Singleton.singletonTest()
這種寫法雖然看上去像是靜態方法的呼叫,但其實Kotlin在背後自動幫我們建立了一個Singleton類的例項,並且保證全域性只會存在一個Singleton例項。
集合
集合的建立
使用如下程式碼可以初始化一個List集合:
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
使用如下程式碼可以初始化一個Set集合:
val set = setOf("Apple", "Banana", "Orange", "Pear", "Grape")
使用如下程式碼可以初始化一個Map集合
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
集合的取值
fun mutableList() {
val list = mutableListOf("apple", "banana", "orange", "pear", "grape")
list.add("other")
for (fruit in list) {
println(fruit)
}
}
SetOf集合,去重的集合,底層是map 所以可以去重
fun setList() {
val list = setOf("apple", "banana", "orange", "pear", "grape")
for (fruit in list) {
println(fruit)
}
}
map 集合 java寫法:鍵值對一對一
fun mapList() {
val map = HashMap<String, Int>()
map.put("apple", 1)
map.put("banana", 2)
}
kotlin 可以用[]
fun mapListKotlin() {
val map = HashMap<String, Int>()
map["apple"] = 2
map["banana"] = 1
val number = map["apple"]
}
最簡潔的寫法 mapOf不可變,mutableMapOf可變
fun mapListKotlin1() {
val map = mapOf("apple" to 1, "banana" to 2, "orange" to 3)
val map1 = mutableMapOf("apple" to 1, "banana" to 2, "orange" to 3)
map1["watermelon"] = 6
for ((fruit, number) in map1) {
println("mutable" + fruit + number)
}
}
Lambda程式設計
Lambda就是一小段可以作為引數傳遞的程式碼。正常情況下,我們向某個函式傳參時只能傳入變數,而藉助Lambda卻允許傳入一小段程式碼。
我們來看一下Lambda表示式的語法結構:
{引數名1: 引數型別, 引數名2: 引數型別 -> 函式體}
首先最外層是一對大括號,如果有引數傳入到Lambda表示式中的話,我們還需要宣告引數列表,引數列表的結尾使用一個->符號,表示引數列表的結束以及函式體的開始,函式體中可以編寫任意行程式碼,並且最後一行程式碼會自動作為Lambda表示式的返回值。
集合的函式式API
集合中的map函式是最常用的一種函式式API,它用於將集合中的每個元素都對映成一個另外的值,對映的規則在Lambda表示式中指定,最終生成一個新的集合。比如,這裡我們希望讓所有的水果名都變成大寫模式,就可以這樣寫:
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map({ fruit: String -> fruit.toUpperCase() })
for (fruit in newList) {
println(fruit)
}
}
- 當Lambda引數是函式的最後一個引數時,可以將Lambda表示式移到函式括號的外面。
- 如果Lambda引數是函式的唯一一個引數的話,還可以將函式的括號省略。
- 由於Kotlin擁有出色的型別推導機制,Lambda表示式中的引數列表其實在大多數情況下也不必宣告引數型別。
- 當Lambda表示式的引數列表中只有一個引數時,也不必宣告引數名,而是可以使用it關鍵字來代替。
因此,Lambda表示式的寫法可以進一步簡化成如下方式:
val newList = list.map { it.toUpperCase() }
推導過程
fun lambda1() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
val max = list.maxByOrNull { it.length }
println(max)
}
maxByOrNul實際上它一個普通函式,只不過它接受的是一個Lambda型別引數,每次集合遍歷時把遍歷的值作為引數傳遞給lambda表示式,工作原理就是找到最長值並返回
fun lambda2() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
val lambda = { fruit: String -> fruit.length }
val max = list.maxByOrNull(lambda)
println(max)
}
可直接將lambda表示式傳入maxByOrNull函式中
fun lambda3() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
val max = list.maxByOrNull({ fruit: String -> fruit.length })
println(max)
}
Kotlin規定,當lambda引數是函式的最後一個引數時,可以將Lambda表示式移到函式括號外邊
fun lambda4() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
val max = list.maxByOrNull() { fruit: String -> fruit.length }
println(max)
}
Kotlin規定,如果Lambda引數是函式的唯一一個引數的話,還可以講函式的括號省略
fun lambda5() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
val max = list.maxByOrNull { fruit: String -> fruit.length }
println(max)
}
Kotlin有出色的類推導機制,大多數的情況下不必宣告引數的型別
fun lambda6() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
val max = list.maxByOrNull { fruit -> fruit.length }
println(max)
}
Kotlin規定,當lambda表示式的引數列表中只有一個引數時,也不必宣告引數名,而是可以使用it關鍵字來代替
fun lambda7() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
val max = list.maxByOrNull { it.length }
println(max)
}
其他集合函式API:
使用map函式轉換為大寫
fun lamdabMap() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
var newList = list.map { it.toUpperCase() }
}
filter函式過濾集合中的元素,這裡注意呼叫順序 先map 在filter 效率會低
fun lamdabMapFilter() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
var newList = list.filter { it.length <= 5 }.map { it.toUpperCase() }
}
any函式用於判斷集合中至少存在一個元素滿足條件
all函式判斷集合中所有元素滿足條件
這樣是不是比每次迴圈方便多了
fun lamdabAnyAll() {
val list = listOf("apple", "banana", "orange", "pear", "grape", "other")
val any = list.any { it.length <= 5 }
val all = list.all { it.length <= 5 }
}
Java函式式API
如果我們在Kotlin程式碼中呼叫了一個Java方法,並且該方法接收一個Java單抽象方法介面引數,就可以使用函式式API。Java單抽象方法介面指的是介面中只有一個待實現方法,如果介面中有多個待實現方法,則無法使用函式式API。
舉個例子,Android中有一個極為常用的點選事件介面OnClickListener,其定義如下:
public interface OnClickListener {
void onClick(View v);
}
可以看到,這是一個單抽象方法介面。假設現在我們擁有一個按鈕button的例項,就可以使用函式式API的寫法來註冊這個按鈕的點選事件:
button.setOnClickListener { v ->
}
推導過程
fun lmClass() {
Thread(object : Runnable {
override fun run() {
println("Thead is run")
}
}).start()
}
Runnable只有一個待實現方法,這裡沒有顯示地重寫run方法kotlin自動明白runnable後面的lambda表示式就是在run中的實現內容
fun lmClass1() {
Thread(Runnable {
println("Thead is run")
}).start()
}
java方法的引數列表不存在一個以上java單抽象方法介面引數,還可以將介面名省略
fun lmClass2() {
Thread({
println("Thead is run")
}).start()
}
當lambda表示式是方法的最後一個引數時,可以將lambda表示式移到方法括號外面
fun lmClass3() {
Thread() {
println("Thead is run")
}.start()
}
同時lambda表大會還是方法的唯一一個引數,還可以方法的括號省略
fun lmClass4() {
Thread {
println("Thead is run")
}.start()
}
空指標檢查
空指標是一種不受程式語言檢查的執行時異常,只能由程式設計師主動通過邏輯判斷來避免,但即使是最出色的程式設計師,也不可能將所有潛在的空指標異常全部考慮到。
public void doStudy(Study study) {
study.readBooks();
study.doHomework();
}
這段Java程式碼安全嗎?不一定,因為這要取決於呼叫方傳入的引數是什麼,如果我們向doStudy()方法傳入了一個null引數,那麼毫無疑問這裡就會發生空指標異常。因此,更加穩妥的做法是在呼叫引數的方法之前先進行一個判空處理,如下所示:
public void doStudy(Study study) {
if (study != null) {
study.readBooks();
study.doHomework();
}
}
可空型別系統
Kotlin中引入了一個可空型別系統的概念,它利用編譯時判空檢查的機制幾乎杜絕了空指標異常。
fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}
這段程式碼看上去和剛才的Java版本並沒有什麼區別,但實際上它是沒有空指標風險的,因為Kotlin預設所有的引數和變數都不可為空,所以這裡傳入的Study引數也一定不會為空,可以放心地呼叫它的任何函式。
Kotlin提供了另外一套可為空的型別系統,就是在類名的後面加上一個問號。比如,Int表示不可為空的整型,而Int?就表示可為空的整型;String表示不可為空的字串,而String?就表示可為空的字串。
使用可為空的型別系統時,需要在編譯時期就把所有的空指標異常都處理掉才行。
判空輔助工具
Kotlin提供了一系列的輔助工具,使開發者能夠更輕鬆地進行判空處理。
?. 操作符表示當物件不為空時正常呼叫相應的方法,當物件為空時則什麼都不做。比如:
if (a != null) {
a.doSomething()
}
這段程式碼使用?.操作符就可以簡化成:
a?.doSomething()
?: 操作符表示如果左邊表示式的結果不為空就返回左邊表示式的結果,否則就返回右邊表示式的結果。比如:
val c = if (a ! = null) {
a
} else {
b
}
這段程式碼的邏輯使用?:操作符就可以簡化成:
val c = a ?: b
kotlin並非總是那麼智慧如下
我們發現一個可空的全域性變數,在contextNull已經判斷為空卻報錯kotlin識別不了
!!:我們使用強制判斷不為空!!非空斷言工具
var content: String? = "hello"
fun contextNull() {
if (content != null) {
upCase()
}
}
fun upCase() {
val upcase = content!!.toUpperCase()
println(upcase)
}
結合使用?.操作符和let函式也可以對多次重複呼叫的某個變數統一進行判空處理
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}
取文字長度的函式
fun getTextLength(text: String?): Int {
if (text != null) {
return text.length
}
return 0
}
簡寫為:由於text可為空?.判斷返回null藉助?:返回0
fun getTextLength1(text: String?) = text?.length ?: 0
函式預設值
Kotlin允許在定義函式的時候給任意引數設定一個預設值,這樣當呼叫此函式時就不會強制要求呼叫方為此引數傳值,在沒有傳值的情況下會自動使用引數的預設值。語法格式如下:
fun printParams(num: Int, str: String = "hello") {
println("num is $num , str is $str")
}
這裡給printParams()函式的第二個引數設定了一個預設值,這樣當呼叫printParams()函式時,可以選擇給第二個引數傳值,也可以選擇不傳,在不傳的情況下就會自動使用預設值。
示例:
fun params(num: Int, str: String = "Hello") {
println("num $num str $str")
}
fun params1(num: Int = 100, str: String) {
println("num $num str $str")
}
fun params2(num: Int = 100, str: String = "Hello") {
println("num $num str $str")
}
給建構函式傳入預設入參,可以預設不傳入引數
class Student(val sno: String = "", val grade: Int = 0, name: String = "", age: Int = 0) :
Person3(name, age) {
init {
println("sno:$sno")
println("grade:$grade")
}
}
fun main() {
params(123)
//params1(123)
params1(str = "wordla")
params2(str = "11", num = 55)
val s = Student()
}
相關文章
- 一篇文章帶你快速入門createjsJS
- Cris 帶你快速入門 Flink
- 一篇文章帶你入門Zookeeper
- Kotlin協程快速入門Kotlin
- # 一篇文章帶你入門軟體測試
- 一篇文章帶你瞭解——Kotlin協程Kotlin
- Android跨平臺入門:手把手帶你快速入門Flutter!AndroidFlutter
- 一篇文章帶你入門SQL程式設計GIFUSQL程式設計
- 十五張圖帶你快速入門 shardingsphere-proxy
- 一篇帶你快速入門ansible和使用
- Android Studio上Kotlin的入門AndroidKotlin
- 5分鐘帶你快速入門和了解 OAM Kubernetes
- CSS快速入門(三)CSS
- 一篇文章快速入門React框架React框架
- 你真的瞭解python嗎?這篇文章帶你快速瞭解!Python
- CSS快速入門基礎篇,讓你快速上手(附帶程式碼案例)CSS
- 前端快速入門(三)--CSS前端CSS
- 帶你高效入門 FlutterFlutter
- 帶你ECharts入個門Echarts
- Kotlin學習快速入門(3)——類 繼承 介面Kotlin繼承
- 手摸手帶你入門ionic3(三):新建專案
- ES6快速入門(三)
- Kotlin 入門(一)Kotlin
- Kotlin學習快速入門(7)——擴充套件的妙用Kotlin套件
- Python語言如何入門?這篇文章讓你5分鐘入門Python!Python
- gRPC(三)基礎:gRPC快速入門RPC
- Flutter基礎(三)Dart快速入門FlutterDart
- Flutter 基礎(三)Dart 快速入門FlutterDart
- webservice快速入門-SOAP和WSDL(三)Web
- Android版kotlin協程入門(三):kotlin協程的異常處理AndroidKotlin
- python入門:進來吧,給自己10分鐘,這篇文章帶你直接學會pythonPython
- vue 快速入門 系列 —— vue loader 上Vue
- 一文帶你入門TransformerORM
- 一文帶你入門LinuxLinux
- 12個例子帶你入門Electron
- 帶你入門RPC之反向代理RPC
- 一文帶你入門 GolangGolang
- Android入門教程 | Kotlin協程入門AndroidKotlin