Kotlin知識總結:變量和函數

前言

這幾天在學習kotlin,因爲是新語言,看的時候能看懂,看完容易忘記,所以想着寫一個博客,將一些知識點記錄下來,可以隨時查看

1.變量和函數

聲明變量關鍵字:
•val (來自 value ) 一一不可變引用。使用 val 聲明的變量不能在初始化之後再次賦值。它對應的是 Java 的 final 變量。
• var (來自 variable ) 一一可變引用。這種變量的值可以被改變。這種聲明對應的是普通(非 final )的 Java 變量。
應該儘可能地使用 val 關鍵字來聲明所有的 Kotlin 變量,僅在必要的時候換成 var。儘管 val 引用自身是不可變的,但是它指向的對象可能是可變的。下邊代碼是允許的

val languages = arrayListOf ("java")
languages.add ("kotlin")

Kotlin 的變量是沒有默認值的,所以必須初始化。

val answer: Int = 42

可空性

在java中經常因爲null產生NullPointerException異常, Kotiin 解決這類問題的方法是把運行時的錯誤轉變成編譯期的錯誤。對可空類型是顯示支持的,默認情況下如果傳入null,編譯器就會報錯。
如果需要傳入null,可以在變量後邊加上"?",所有常見類型默認都是非空的,除非顯式地把它標記爲可空。

class User {
    var name: String? = null
}
fun setLen(s: String?) = s?.length

非空斷言 “!!”,說明變量一定是非空的,編譯器不會再檢查

view!!.setBackgroundColor(Color.RED)

安全調用運算符:“?.” 它允許你把一次null檢查和一次方法調用合併成一個操作。例如,表達式 s?.toUpperCase()等同於下面這種煩瑣的寫法: if (s!=null) s.toUpperCase() else null 。

Elvis運算符:“ ?: " ,接收兩個運算數,如果第一個運算數不爲 null ,運算結果就是第一個運算數;如果第一個運算數爲 null ,運算結果就是第二個運算數

val t: String= s ?: ""

安全轉換:“as?” 運算符嘗試把值轉換成指定的類型, 如果值不是合適的類型就返回 null

“ let”函數 做的所有事情就是把一個調用它的對象變成 lambda 表達式的參數 。 如果結合安
全調用語法,它能有效地把調用 let 函數的可空對象,轉變成非空類型。
比如:foo?.let{ …it…} 在foo!=null的時候,在lambda方法裏,it就是非空的,如果foo==null則什麼事情都不會發生

email?.let { email -> sendEmailTo(email ) }

簡單點的寫法:

email?.let{ sendEmailTo(it)

方法裏有個it,如果當前上下文期望的是隻有一個參數的 lambda 且這個參數的類型可以推斷出來,就會生成這個名稱。

延遲初始化 lateinit告訴編譯器我沒法第一時間就初始化,但我肯定會在使用它之前完成初始化的。屬性必須是var

lateinit var view: View
override fun onCreate(...) {
    ...
    view = findViewById(R.id.tvContent)
}

String類中的isEmpty和isBlank方法,可以允許接收者是null,在函數中對null進行處理,不用確保變量不
爲 null 之後再調用它的方法。這個只有擴展函數才能做到,因爲成員方法需要實例調用,實例爲null,永遠不會調用成員方法

fun verifyUserInput(input:String?){
    if(input.isNullOrBlank()){
        println("輸入爲空")
    }
}

verifyUserInput(null)

Kotlin 中所有泛型類和泛型函數的類型參數默認都是可空的

類型推斷

如果在聲明變量時賦值,就可以不用寫類型,Kotlin會自動推斷,聲明以後,不可以再賦值另一類型

var name = "Mike"
name = 1
// 會報錯,The integer literal does not conform to the expected type String

數據類型

Kotlin 並不區分基本數據類型和它們的包裝類。在運行時,大多數情況下,對於變量、屬性、返回值和參數,Int等類型,會編譯成java的基本數據類型int,在泛型類,如集合中,會編譯成對應的java包裝類型,Integer。如果使用了基本類型的可空版本,就會被編譯成對應的包裝類型。
Kotlin 不會自動地把數字從一種類型轉換成另外一種,必須顯示轉換,每 一 種基本數據類型( Boolean 除外)都定義有轉換函數: toByte()、toShort() 、 toChar()等

val x = l
val list = listOf (lL, 2L, 3L)
x in list

這個是錯誤的,不會隱式轉換

val x = 1
println(x.toLong() in listOf(lL , 2L, 3L))

Kotlin Any和java的Object的區別是,Object 只是所有引用類型的超類型(引用類型的根),而基本數據類型並不是類層級結構的一部分。Any 是所有類型的超類型(所有類型的根),包括像 Int 這樣的基本數據類型 。 把基本數據類型的值賦給 Any 類型的變量時會自動裝箱
Kotlin Unit相當於Java的Void,也有區別,Unit 是一個完備的類型,可以作爲類型參數,而 void 卻不行
Noting類型,說明這個函數永遠不會返回,表示從來不會成功結束

fun fail(message: String): Nothing {
throw IllegalStateException (message)
}
>> fail (”Error occurred”)
java . lang.IllegalStateException: Error occurred

字符串模板:在字符串字面值中引用局部變量,只需要在變量名稱前面加上字符$,

 val result = operation(2, 3)
    println("result is $result")

原生字符串:有時候不想要轉移,可以用一對 “”" 將字符串括起來

val text = """
      Hi $name!
    My name is $myName.\n
"""

可以通過 trimMargin() 函數去除每行前面的空格

集合和數組

數組

val strs: Array<String> = arrayOf("a", "b", "c")

取值和修改:

println(strs[0])
strs[1] = "B"

Kotlin數組不支持協變,就是子類數組對象不能賦值給父類的數組變量

val strs: Array<String> = arrayOf("a", "b", "c")
val anys: Array<Any> = strs // compile-error: Type mismatch

在一些性能需求比較苛刻的場景,並且元素類型是基本類型時,用數組好一點。Kotlin 中要用專門的基本類型數組類 (IntArray FloatArray LongArray) 纔可以免於裝箱,會被編譯成int []、 float[], long[].
Array< Int >,它將會是一個包含裝箱整型的數組

var arrays = intArrayOf(1,2,3)

集合
List 以固定順序存儲一組元素,元素可以重複。
Set 存儲一組互不相等的元素,通常沒有固定順序。
Map 存儲 鍵-值 對的數據集合,鍵互不相等,但不同的鍵可以對應相同的值。

val strList = listOf("a", "b", "c")

val strSet = setOf("a", "b", "c")

val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 3)

to 表示將「鍵」和「值」關聯,這個叫做「中綴表達式」

List支持 covariant(協變),可以把子類的 List 賦值給父類的 List 變量

val strs: List<String> = listOf("a", "b", "c")
val anys: List<Any> = strs // success

取值

val value1 = map.get("key1")
val value2 = map["key2"]

賦值

val map = mutableMapOf("key1" to 1, "key2" to 2)
map.put("key1", 2)
map["key1"] = 2 

繼承自Collection的集合,可以遍歷集合中的元素、獲取集合大小、判斷集合中是否包含某個元素,以及執行其他從該集合中讀取數據的操作。但這個接口沒有任何添加或移除元素的方法 。如果需要修改,需要繼承MutableCollection接口。有 mutable 前綴的函數創建的是可變的集合,沒有 mutbale 前綴的創建的是不可變的集合,不過不可變的可以通過 toMutable*() 系函數轉換成可變的集合

val strList = listOf("a", "b", "c")
strList.toMutableList()
val strSet = setOf("a", "b", "c")
strSet.toMutableSet()
val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 3)
map.toMutableMap()

只讀集合不一定是不可變的,兩個不同的引用,一個只讀,另一個可變 , 指向同一個集合對象。使用集合的時候它被其他代碼修改了,這會導致 concurrentModificationException 錯誤和其他一些 問題。因此,必須瞭解只讀集合並不總是線程安全的

lambda的一些說明
處理集合類,經常使用lambda,這裏有個約定

val persons = listOf<Person>(Person("張三"), Person("李四", 20))

    val oldest = persons.maxBy { it.age ?: 0 }

persons.maxBy完整寫法:

persons. maxBy ({ p: Person-> p.age })

Kotlin 有這樣一種語法約定,如果lambda 表達式是函數調用的最後一個實參,它可以放到括號的外邊。

persons. maxBy () { p: Person-> p.age }

當 lambda 是函數唯一的實參時,還可以去掉調用代碼中的空括號對

persons.maxBy { p: Person -> p.age }

lambda 如果有多個語句,最後一個表達式就是lambda的結果

val sum = { x: Int, y· Int ->
println (” Computing the sum of $x and $y...”)
x + y
>> println(sum(l, 2))
Computing the sum of 1 and 2 ...
3

Kotlin 允許在 lambda 內部訪問非 final 變量甚至修改它們

fun printProblems(responses:Collection<String>){
    var clientErrors = 0//
    responses.forEach{
        if(it.startsWith("4")){
            clientErrors++
        }
    }
}

如果把函數轉換成一個值,你就可以傳遞它 。 使用::運算符來轉換

data class Person(val name: String, val age: Int = 60) {
    val isOld: Boolean
        get() = age > 50

}
 println(persons.map(Person::name))

還可以引用頂層函數(不是類的成員)

fun salute() = println ("Salute!")
>> run (::salute)
Salute!

操作符:

data class Person( val name: String , val age: Int)

forEach:遍歷每一個元素

val intArray = intArrayOf(1, 2, 3)
intArray.forEach { i ->
    print(i + " ")
}

filter:對每個元素進行過濾操作,如果 lambda 表達式中的條件成立則留下該元素,否則剔除,最終生成新的集合

>> val list= listOf(l, 2, 3, 4)
>> println(list.filter { it%2==0}[2,4]
>> val people= listOf(Person(”Alice”, 29) , Person (”Bob”, 31))
>> println(people.filter { it.age > 30 })
[Person (name=Bob, age=31)]

map:對集合中的每一個元素應用給定的函數並把結果收集到一個新集合

>> val list= listOf(l, 2, 3, 4)
>> println(list.map { it * it })
[1,4,9,16]

>> val people = listOf(Person("Alice", 29), Person ("Bob", 31))
>> println(people.map { it.name })
[Alice, Bob]
>> people.filter { it .age > 30 }.map(Person::name)
[Bob]

一個案例:找出人羣分組中所有年齡最大的人的名字

people. filter {it.age== people.maxBy(Person::age) .age}

這個問題就在於:每個人都會重複尋找最大年齡的過程,假設集合中有
100 個人,尋找最大年齡的過程就會執行 100 遍!
改進版:

val maxAge = people.maxBy(Person::age).age
people. filter {it .age == maxAge }

如果沒有必要就不要重複計算

groupBy:把列表轉換成分組的 map

>> val people= listOf(Person("Alice", 31)'
Person ("Bob", 29), Person ("Carol" , 31))
>> println(people.groupBy { it age })

>>{29=[Person(name=Bob , age=29)],
3l=[Person(name=Alice, age=31), Person(name=Carol, age=31ll}
>> val list = listOf("a","ab","b")
>> println (list .groupBy(String:: first))
{a=[a, ab], b=[bJ}

flatMap:遍歷每個元素,併爲每個元素創建新的集合,最後合併到一個集合中

>> val strings= listOf ("abc","def")
> > println(strings.flatMap { it.toList() } )
[a, b, c, d, e, fl

當多個元素集合的集合不得不合併成一個的時候,使用flatten

 val list2 = listOf(listOf(1, 2, 3), listOf(4, 5, 6, 1, 2, 3), listOf(7, 8, 9))
    println(list2.flatten())
[1, 2, 3, 4, 5, 6, 1, 2, 3, 7, 8, 9]

通過map和filter,每次都會創建中間集合,創建臨時列表,

people.map(Person: :name) .filter { it.startsWith ("A")}

如果想避免創建中間集合,可以使用序列

people.asSequence()
. map (Person: : name)
.filter { it.startsWith ("A")}
. toList ()

沒有創建任何用於存儲元素的中間集合。序列操作分爲兩類 :中間的和末端的。 一次 中間操作返回的是另 一個序列,這個新序列知道如何變換原始序列中 的元素 。 而一次末端操作返回的是一個結果,這
個結果可能是集合、元素、數字,或者其他從初始集合的變換序列中獲取的任意對象
在這裏插入圖片描述
中間操作始終都是惰性的,末端操作觸發執行了所有的延期計算。

map和filter調用先後順序的區別

>> val people: listOf(Person("Alice", 29), Person("Bob", 31) '
Person ("Charles", 31), Person ("Dan", 21 ))
>> println(people.asSequence() .map(Person::name).filter { it. length < 4 }. toList ())
[Bob, Dan]
>> println(people.asSequence().filter { it.name.length< 4 }
.map(Person: :name) .toList())
[Bob, Dan)

如果 map 在前,每個元素都被變換。而如果 filter 在前,不合適的元素會被儘早地過濾掉且不會發生變換.
除了在集合上調用asSequence ()創建序列,還可以通過generateSequence創建

  val naturalNumbers = generateSequence(0) { it + 1 }
    val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
    println(numbersTo100.sum())

當獲取結果“sum”時,所 有被推遲的操作都被執行
java中的

public class Button {
public void setOnClickListener (OnClickListener 1 ) { ... }
}

這種只有一個抽象方法的接口,被稱爲函數式接 口,或者 SAM 接口, SAM 代表單抽象方法。可以用lambda表示成

button .setOnClickListener { view -> ... }

如果可以用語句對同一個對象執行多次操作,而不需要反覆把對象的名稱 寫出來。可以使用with

fun alphabet(): String {
    var stringBuffer = StringBuffer()
    return with(stringBuffer) {
        for (letter in 'A'..'Z') {
            append(letter)
        }
        append("end")
        this.toString()
    }
}

或者

fun alphabet1()= with(StringBuilder()){
        for(letter in 'A'..'Z'){
            append(letter)
        }
        append("end")
        toString()
    }
}

with 是接收兩個參數的函數:這個例子中兩個參數分別是 stringBuilder 和一個 lambda 。 這裏利用
了把 lambda 放在括號外的約定 ,這樣整個調用看起來就像是內建的語言功能 。可以等同於with (StringBuilder , { . . . })
with 返回的值是執行 lambda 代碼的結果,該結果就是 lambda 中的最後 一
個表達式(的值)。如果想返回接收者對象,而不是執行 lambda 的結果 。可以用apply。 apply 始終會返回作爲實參傳遞給它的對象(換句話說,接收者對象)

fun alphabet () = StringBuilder () . apply {
	for (letter in  'A' ..'Z') {
append (letter)
append ("\nNow I know the alphabet !")
}. toString()

apply的接收者是StringBuilder,所以這個返回的是StringBuilder,可以調用toString()
看另一個例子

fun createCustomTextView(context:Context){
      TextView(context).apply {
          text = "Sample TextView"
          textSize= 20.0F
          setPadding(10,0,0,0)
      }
  }

在傳給 apply 的 lambda 中, TextView 實例變成了( lambda的)接收者,你就可以調用它的方法並設置它的屬性。
Range
Range 表示區間的意思,也就是範圍

val range: IntRange = 0..1000

表示就表示從 0 到 1000 的範圍,包括 1000

val range: IntRange = 0 until 1000 

表示從 0 到 1000,但不包括 1000

val range = 0..1000
//      默認步長爲 1,輸出:0, 1, 2, 3, 4, 5, 6, 7....1000,
for (i in range) {
    print("$i, ")
}
val range = 0..1000
//    步長爲 2,輸出:0, 2, 4, 6, 8, 10,....1000,
for (i in range step 2) {
    print("$i, ")
}
//     輸出:4, 3, 2, 1, 
for (i in 4 downTo 1) {
    print("$i, ")
}

其中 4 downTo 1 就表示遞減的閉區間 [4, 1]。這裏的 downTo 以及上面的 step 都叫做「中綴表達式」

條件控制

for ,while,if等基本上和java類似
if/else

val max = if (a > b) a else b

代碼塊的最後一行會作爲結果返回

val max = if (a > b) {
    println("max:a")
    a //  返回 a
} else {
    println("max:b")
    b //  返回 b
}

when
相當於java的switch,

when (x) {
    1 -> { println("1") }
    2 -> { println("2") }
    else -> { println("else") }

區別在於:省略了 case 和 break,Java 中的默認分支使用的是 default 關鍵字,Kotlin 中使用的是 else,when 允許使用任何對象
定義一個枚舉

enum class Color(val r: Int, val g: Int, val b: Int) {
    BLUE(0, 0, 255),
    YELLOW(255, 255, 0);

    fun rgb() = (r * 256 + g) * 256 + b
}

fun mix(c1: Color, c2: Color) =
    when (setOf(c1, c2)) {
        setOf(Color.BLUE, Color.YELLOW) -> Color.BLUE
        else -> ""
    }

還可以不帶參數

when {
    str1.contains("a") -> print("字符串 str1 包含 a")
    str2.length == 3 -> print("字符串 str2 的長度爲 3")
}

for

val array = intArrayOf(1, 2, 3, 4)
for (item in array) {
    ...
}

for (i in 0..10) {
    println(i)
}

try-catch

try {
    ...
}
catch (e: Exception) {
    ...
}
finally {
    ...
}

java中處理資源文件,可以這樣寫

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
            new BufferedReader(new FileleReader (path)))
        return br.readLine ()
    }
}

在kotlin中可以這樣寫

//資源管理
fun readFirstLineFromFile(path:String):String{
    BufferedReader(FileReader(path)).use {
         br-> return br.readLine()
    }
}

調用use 就不用手動調用close方法

“==“和”= ==”
== :可以對基本數據類型以及 String 等類型進行內容比較,相當於 Java 中的 equals
=== :對引用的內存地址進行比較,相當於 Java 中的 ==

函數

函數定義:fun開頭,函數名,參數名,返回值,函數體

fun max(a: Int, b: Int) :Int{
 return if (a > b) a else b
 }

如果函數體是由單個表達式構成的,可以用這個表達式作爲完整的函數體,井去掉花括號和 return 語句。直接返回了 一個表達式,它就有表達式體。

fun max(a: Int, b: Int) = if (a > b) a else b

沒有返回值,返回Unit,可以省略

fun main(): Unit {}
// Unit 返回類型可以省略
fun main() {}

參數可以設置默認值,

fun joinToString(separator:String=",",
    prefix:String="",
    postfix:String="")

調用的時候,可以指定參數名來調用

joinToString(
            separator = ";",
            prefix = "(",
            postfix = ")")

使用vararg表示的是可變參數,讓函數支持任意數量的參數。listOf的源碼就是用的這個修飾符
在這裏插入圖片描述
top-level property / function
把屬性和函數的聲明不寫在 class 裏面,這些函數直接放到代碼文件的頂層,不用從屬於任何的類。這些放在文件頂層的函數依然是包內的成員,如果你需要從包外訪問它,則需要 import, 但不再需要額外包一層。

package drag.mandala.com.kotlindemo.strings
val String.lastChar:Char
    get() = get(length-1)

調用:

import drag.mandala.com.kotlindemo.strings.lastChar
 println("hello".lastChar)

和頂層函數一樣,可以定義頂層屬性

var count = 1
fun main(){
 println("count : ${++count}")
 }

如果你想要把一個常量以 public static final 的屬性暴露給 Java ,可以用 const 來修飾它(這個適用於所有的基本數據類型的屬性,以及 String 類型) 。const 必須修飾val,const 只允許在top-level級別和object中聲明
擴展函數
上個例子有String.lastChar(),這個是擴展函數,就是一個類的成員函數,不過定義在類的外面,把你要擴展的類或者接口的名稱,放到即將添加的函數前面 。 這個類的名稱被稱爲接收者類型;用來調用這個擴展函數的那個對象,叫作接收者對象。擴展函數不能重寫
在這裏插入圖片描述

可以像調用類的普通成員函數一樣去調用這個函數。可以用as修改導入的類或者函數名稱:

import strings.lastChar as last
val c ="Kotlin".last()
fun <T> Collection<T>.joinToString(
    separator:String=",",
    prefix:String="",
    postfix:String="",
    transform:((T)->String)?=null
):String{
    val result = StringBuilder(prefix)
    for((index,element) in this.withIndex()){
        if(index>0) result.append(separator)
        val str = transform?.invoke(element) ?: element.toString()

        result.append(str)
    }
    result.append(postfix)
    return  result.toString()
}

 println(list.joinToString(";", "(", ")"))
 

其中,(index,element) in this.withIndex()是解構聲明,一般用在數據類中

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // 輸出 "Jane, 35 years of age"

屬性的 getter/setter 函數

class User {
    var name = "Mike"
  
        get() {
            return field + " nb"
        }
    
        set(value) {
            field = "Cute " + value
        }
}

field 是幕後字段

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章