本文已收錄至學習筆記大全:JavaKotlinAndroidGuidejava
做者:leavesCgit
[TOC]github
按照國際慣例,學習一門新的語言一般都是從 Hello World 開始的,在這裏也不例外編程
package main
fun main(args: Array<String>) {
println("Hello World")
}
複製代碼
從這個簡單的函數就能夠列出 kotlin 和 Java 的幾點不一樣數組
println
代替了 System.out.println
,這是 kotlin 標準庫提供的對 Java 標準庫函數的封裝此外,kotlin 的最新版本已經能夠省略 main
方法的參數了安全
kotlin 文件都以一條 package 語句開頭,文件中定義的全部聲明(類、函數和屬性)都會被放到這個包中。若是其餘文件中定義的聲明也有相同的包,這個文件能夠直接使用它們,若是包不相同則須要導入它們數據結構
包的聲明應處於源文件頂部,import 語句緊隨其後多線程
package base
import java.text.SimpleDateFormat
import java.util.*
複製代碼
kotlin 不區分導入的是類仍是函數,容許使用 import
關鍵字導入任何種類的聲明。此外,也能夠在包名稱後加上 .* 來導入特定包中定義的全部聲明,這不只會讓包中定義的類可見,也會讓頂層函數和屬性可見併發
package learn.package2
val index = 10
fun Test(status: Boolean) = println(status)
class Point(val x: Int, val y: Int) {
val isEquals1: Boolean
get() {
return x == y
}
val isEquals2
get() = x == y
var isEquals3 = false
get() = x > y
set(value) {
field = !value
}
}
複製代碼
package learn.package1
import learn.package2.Point
import learn.package2.Test
import learn.package2.index
fun main() {
val point = Point(10, index)
Test(true)
}
複製代碼
Java 語言規定類要放到和包結構匹配的文件夾目錄結構中,而 kotlin 容許把多個類放到同一個文件中,文件名也能夠任意選擇。kotlin 也沒有對磁盤上源文件的佈局強加任何限制,包層級結構不須要遵循目錄層級結構 ,但最好仍是遵循 Java 的目錄佈局並根據包結構把源碼文件放到相應的目錄中框架
若是包名出現命名衝突,可使用 as 關鍵字在本地重命名衝突項來消歧義
import learn.package1.Point
import learn.package2.Point as PointTemp //PointTemp 能夠用來表示 learn.package2.Point 了
複製代碼
kotlin 中也有一個相似的概念能夠用來重命名現有類型,叫作類型別名。類型別名用於爲現有類型提供一個替代名稱,若是類型名稱比較長,就能夠經過引入一個較短或者更爲簡略的名稱來方便記憶
類型別名不會引入新的類型,依然相應其底層類型,因此在下述代碼中輸出的 class 類型是一致的
class Base {
class InnerClass
}
typealias BaseInner = Base.InnerClass
fun main() {
val baseInner1 = Base.InnerClass()
val baseInner2 = BaseInner()
println(baseInner1.javaClass) //class test2.Base$InnerClass
println(baseInner2.javaClass) //class test2.Base$InnerClass
}
複製代碼
在 Java 中,大部分的變量是可變的(非 final 的),意味着任何能夠訪問到這個變量的代碼均可以去修改它。而在 kotlin 中,變量能夠分爲 可變變量(var) 和 不可變變量(val) 兩類
聲明變量的關鍵字有兩個:
不可變變量在賦值以後就不能再去改變它的狀態了,所以不可變變量能夠說是線程安全的,由於它們沒法改變,全部線程訪問到的對象都是同一個,所以也不須要去作訪問控制。開發者應當儘量地使用不可變變量,這樣可讓代碼更加接近函數式編程風格
編程領域中也推崇一種開發原則:儘量使用 val,不可變對象及純函數來設計程序。這樣能夠儘可能避免反作用帶來的影響
fun main() {
//只讀變量即賦值後不能夠改變值的變量,用 val 聲明
//聲明一個整數類型的不可變變量
val intValue: Int = 100
//聲明一個雙精度類型的可變變量
var doubleValue: Double = 100.0
}
複製代碼
在聲明變量時咱們一般不須要顯式指明變量的類型,這能夠由編譯器根據上下文自動推導出來。若是隻讀變量在聲明時沒有初始值,則必須指明變量類型,且在使用前必須確保在各個分支條件下變量能夠被初始化,不然編譯期會報異常
fun main() {
val intValue = 1002222222222
val doubleValue = 100.0
val longValue = 100L
//若是隻讀變量在聲明時沒有初始值,則必須指明變量類型
val intValue2: Int
if (false) {
intValue2 = 10
}
println(intValue2) //error, Variable 'intValue2' must be initialized
}
複製代碼
與 Java 不一樣,kotlin 並不區分基本數據類型和它的包裝類,在 kotlin 中一切都是對象,能夠在任何變量上調用其成員函數和屬性。kotlin 沒有像 Java 中那樣的原始基本類型,但 byte、char、integer、float 或者 boolean 等類型仍然有保留,可是所有都做爲對象存在
對於基本類型,kotlin 相比 Java 有幾點特殊的地方
//在 kotlin 中,int、long、float 等類型仍然存在,可是是做爲對象存在的
val intIndex: Int = 100
//等價於,編譯器會自動進行類型推導
val intIndex = 100
//數字類型不會自動轉型,必需要進行明確的類型轉換
val doubleIndex: Double = intIndex.toDouble()
//如下代碼會提示錯誤,須要進行明確的類型轉換
//val doubleIndex: Double = intIndex
val intValue: Int = 1
val longValue: Long = 1
//如下代碼會提示錯誤,由於二者的數據類型不一致,須要轉換爲同一類型後才能進行比較
//println(intValue == longValue)
//Char 不能直接做爲數字來處理,須要主動轉換
val ch: Char = 'c'
val charValue: Int = ch.toInt()
//如下代碼會提示錯誤
//val charValue: Int = ch
//二進制
val value1 = 0b00101
//十六進制
val value2 = 0x123
複製代碼
此外,kotlin 的可空類型不能用 Java 的基本數據類型表示,由於 null 只能被存儲在 Java 的引用類型的變量中,這意味着只要使用了基本數據類型的可空版本,它就會被編譯成對應的包裝類型
//基本數據類型
val intValue_1: Int = 200
//包裝類
val intValue_2: Int? = intValue_1
val intValue_3: Int? = intValue_1
//== 比較的是數值相等性,所以結果是 true
println(intValue_2 == intValue_3)
//=== 比較的是引用是否相等,所以結果是 false
println(intValue_2 === intValue_3)
複製代碼
若是 intValue_1 的值是100,就會發現 intValue_2 === intValue_3 的比較結果是 true,這就涉及到 Java 對包裝類對象的重複使用問題了
kotlin 與 Java 同樣用 String 類型來表示字符串,字符串是不可變的,可使用索引運算符訪問[]
來訪問包含的單個字符,也能夠用 for 循環來迭代字符串,此外也能夠用 + 來鏈接字符串
val str = "leavesC"
println(str[1])
for (c in str) {
println(c)
}
val str1 = str + " hello"
複製代碼
kotlin 支持在字符串字面值中引用局部變量,只須要在變量名前加上字符 $ 便可,此外還能夠包含用花括號括起來的表達式,此時會自動求值並把結果合併到字符串中
val intValue = 100
//能夠直接包含變量
println("intValue value is $intValue") //intValue value is 100
//也能夠包含表達式
println("(intValue + 100) value is ${intValue + 100}") //(intValue + 100) value is 200
複製代碼
若是你須要在原始字符串中表示字面值($)字符(它不支持反斜槓轉義),能夠用下列語法:
val price = "${'$'}100.99"
println(price) //$100.99
複製代碼
kotlin 中的數組是帶有類型參數的類,其元素類型被指定爲相應的類型參數,使用 Array 類來表示, Array 類定義了 get 與 set 函數(按照運算符重載約定這會轉變爲 [ ] )以及 size 屬性等
建立數組的方法有如下幾種:
//包含給定元素的字符串數組
val array1 = arrayOf("leavesC", "葉", "https://github.com/leavesC")
array1[0] = "leavesC"
println(array1[1])
println(array1.size)
//初始元素均爲 null ,大小爲 10 的字符數組
val array2 = arrayOfNulls<String>(10)
//建立從 「a」 到 「z」 的字符串數組
val array3 = Array(26) { i -> ('a' + i).toString() }
複製代碼
須要注意的是,數組類型的類型參數始終會變成對象類型,所以聲明 Array< Int > 將是一個包含裝箱類型(java.lang.Integer)的數組。若是想要建立沒有裝箱的基本數據類型的數組,必須使用一個基本數據類型數組的特殊類
爲了表示基本數據類型的數組,kotlin 爲每一種基本數據類型都提供了若干相應的類並作了特殊的優化。例如,有 IntArray、ByteArray、BooleanArray 等類型,這些類型都會被編譯成普通的 Java 基本數據類型數組,好比 int[]、byte[]、boolean[] 等,這些數組中的值存儲時沒有進行裝箱,而是使用了可能的最高效的方式。須要注意,IntArray 等並非 Array 的子類
要建立一個基本數據類型的數組,有如下幾種方式:
//指定數組大小,包含的元素將是對應基本數據類型的默認值(int 的默認值是 0)
val intArray = IntArray(5)
//指定數組大小以及用於初始化每一個元素的 lambda
val doubleArray = DoubleArray(5) { Random().nextDouble() }
//接收變長參數的值來建立存儲這些值的數組
val charArray = charArrayOf('H', 'e', 'l', 'l', 'o')
複製代碼
Any 類型是 kotlin 全部非空類型的超類型,包括像 Int 這樣的基本數據類型
若是把基本數據類型的值賦給 Any 類型的變量,則會自動裝箱
val any: Any = 100
println(any.javaClass) //class java.lang.Integer
複製代碼
若是想要使變量能夠存儲包括 null 在內的全部可能的值,則須要使用 Any?
val any: Any? = null
複製代碼
kotlin 中的 Unit 類型相似於 Java 中的 void,能夠用於函數沒有返回值時的狀況
fun check(): Unit {
}
//若是返回值爲 Unit,則能夠省略該聲明
fun check() {
}
複製代碼
Unit 是一個完備的類型,能夠做爲類型參數,但 void 不行
interface Test<T> {
fun test(): T
}
class NoResultClass : Test<Unit> {
//返回 Unit,但能夠省略類型說明,函數也不須要顯式地 return
override fun test() {
}
}
複製代碼
Nothing 類型沒有任何值,只有被當作函數返回值使用,或者被當作泛型函數返回值的類型參數使用時纔會有意義,能夠用 Nothing 來表示一個函數不會被正常終止,從而幫助編譯器對代碼進行診斷
編譯器知道返回值爲 Nothing 類型的函數從不正常終止,因此編譯器會把 name1 的類型推斷爲非空,由於 name1 在爲 null 時的分支處理會始終拋出異常
data class User(val name: String?)
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
fun main() {
val user = User("leavesC")
val name = user.name ?: fail("no name")
println(name) //leavesC
val user1 = User(null)
val name1 = user1.name ?: fail("no name")
println(name1.length) //IllegalStateException
}
複製代碼
kotlin 中的函數以關鍵字 fun 做爲開頭,函數名稱緊隨其後,再以後是用括號包裹起來的參數列表,若是函數有返回值,則再加上返回值類型,用一個冒號與參數列表隔開
//fun 用於表示聲明一個函數,getNameLastChar 是函數名
//空括號表示該函數無傳入參數,Char 表示函數的返回值類型是字符
fun getNameLastChar(): Char {
return name.get(name.length - 1)
}
複製代碼
//帶有兩個不一樣類型的參數,一個是 String 類型,一個是 Int 類型
//返回值爲 Int 類型
fun test1(str: String, int: Int): Int {
return str.length + int
}
複製代碼
此外,表達式函數體的返回值類型能夠省略,返回值類型能夠自動推斷,這種用單行表達式與等號定義的函數叫作表達式函數體。但對於通常狀況下的有返回值的代碼塊函數體,必須顯式地寫出返回類型和 return 語句
//getNameLastChar 函數的返回值類型以及 return 關鍵字是能夠省略的
//返回值類型能夠由編譯器根據上下文進行推導
//所以,函數能夠簡寫爲如下形式
fun getNameLastChar() = name.get(name.length - 1)
複製代碼
若是函數沒有有意義的返回值,則能夠聲明爲 Unit ,也能夠省略 Unit
如下三種寫法都是等價的
fun test(str: String, int: Int): Unit {
println(str.length + int)
}
fun test(str: String, int: Int) {
println(str.length + int)
}
fun test(str: String, int: Int) = println(str.length + int)
複製代碼
爲了加強代碼的可讀性,kotlin 容許咱們使用命名參數,即在調用某函數的時候,能夠將函數參數名一塊兒標明,從而明確地表達該參數的含義與做用,可是在指定了一個參數的名稱後,以後的全部參數都須要標明名稱
fun main() {
//錯誤,在指定了一個參數的名稱後,以後的全部參數都須要標明名稱
//compute(index = 110, "leavesC")
compute(index = 120, value = "leavesC")
compute(130, value = "leavesC")
}
fun compute(index: Int, value: String) {
}
複製代碼
能夠在聲明函數的時候指定參數的默認值,從而避免建立重載的函數
fun main() {
compute(24)
compute(24, "leavesC")
}
fun compute(age: Int, name: String = "leavesC") {
}
複製代碼
對於以上這個例子,若是按照常規的調用語法時,必須按照函數聲明定義的參數順序來給定參數,能夠省略的只有排在末尾的參數
fun main() {
//錯誤,不能省略參數 name
// compute(24)
// compute(24,100)
//能夠省略參數 value
compute("leavesC", 24)
}
fun compute(name: String = "leavesC", age: Int, value: Int = 100) {}
複製代碼
若是使用命名參數,能夠省略任何有默認值的參數,並且也能夠按照任意順序傳入須要的參數
fun main() {
compute(age = 24)
compute(age = 24, name = "leavesC")
compute(age = 24, value = 90, name = "leavesC")
compute(value = 90, age = 24, name = "leavesC")
}
fun compute(name: String = "leavesC", age: Int, value: Int = 100) {
}
複製代碼
可變參數可讓咱們把任意個數的參數打包到數組中傳給函數,kotlin 的語法相比 Java 有所不一樣,改成經過使用 varage 關鍵字聲明可變參數
例如,如下的幾種函數調用方式都是正確的
fun main() {
compute()
compute("leavesC")
compute("leavesC", "葉應是葉")
compute("leavesC", "葉應是葉", "葉")
}
fun compute(vararg name: String) {
name.forEach { println(it) }
}
複製代碼
在 Java 中,能夠直接將數組傳遞給可變參數,而 kotlin 要求顯式地解包數組,以便每一個數組元素在函數中可以做爲單獨的參數來調用,這個功能被稱爲展開運算符,使用方式就是在數組參數前加一個 *
fun main() {
val names = arrayOf("leavesC", "葉應是葉", "葉")
compute(* names)
}
fun compute(vararg name: String) {
name.forEach { println(it) }
}
複製代碼
kotlin 支持在函數中嵌套函數,被嵌套的函數稱爲局部函數
fun main() {
compute("leavesC", "country")
}
fun compute(name: String, country: String) {
fun check(string: String) {
if (string.isEmpty()) {
throw IllegalArgumentException("參數錯誤")
}
}
check(name)
check(country)
}
複製代碼
這裏須要先區分「語句」和「表達式」這兩個概念。語句是能夠單獨執行,可以產生實際效果的代碼,表現爲賦值邏輯、打印操做、流程控制等形式,Java 中的流程控制(if,while,for)等都是語句。表達式能夠是一個值、變量、常量、操做符、或它們之間的組合,表達式能夠看作是包含返回值的語句
例如,如下的賦值操做、流程控制、打印輸出都是語句,其是做爲一個總體存在的,且不包含返回值
val a = 10
for (i in 0..a step 2) {
println(i)
}
複製代碼
再看幾個表達式的例子
1 //字面表達式,返回 1
++1 //自增,返回 2
//與 Java 不一樣,kotlin 中的 if 是做爲表達式存在的,其能夠擁有返回值
fun getLength(str: String?): Int {
return if (str.isNullOrBlank()) 0 else str.length
}
複製代碼
if 的分支能夠是代碼塊,最後的表達式做爲該塊的返回值
val maxValue = if (20 > 10) {
println("maxValue is 20")
20
} else {
println("maxValue is 10")
10
}
println(maxValue) //20
複製代碼
如下代碼能夠顯示地看出 if 的返回值,徹底能夠用來替代 Java 中的三元運算符,所以 kotlin 並無三元運算符
val list = listOf(1, 4, 10, 4, 10, 30)
val value = if (list.size > 0) list.size else null
println(value) //6
複製代碼
若是 if 表達式分支是用於執行某個命令,那麼此時的返回值類型就是 Unit ,此時的 if 語句就看起來和 Java 的同樣了
val value1 = if (list.size > 0) println("1") else println("2")
println(value1.javaClass) //class kotlin.Unit
複製代碼
若是將 if 做爲表達式而不是語句(例如:返回它的值或者把它賦給變量),該表達式須要有 else 分支
when 表達式與 Java 中的 switch/case 相似,可是要強大得多。when 既能夠被當作表達式使用也能夠被當作語句使用,when 將參數和全部的分支條件順序比較直到某個分支知足條件,而後它會運行右邊的表達式。若是 when 被當作表達式來使用,符合條件的分支的值就是整個表達式的值,若是當作語句使用, 則忽略個別分支的值。與 Java 的 switch/case 不一樣之處是 When 表達式的參數能夠是任何類型,而且分支也能夠是一個條件
和 if 同樣,when 表達式每個分支能夠是一個代碼塊,它的值是代碼塊中最後的表達式的值,若是其它分支都不知足條件將會求值於 else 分支
若是 when 做爲一個表達式使用,則必須有 else 分支, 除非編譯器可以檢測出全部的可能狀況都已經覆蓋了。若是不少分支須要用相同的方式處理,則能夠把多個分支條件放在一塊兒,用逗號分隔
val value = 2
when (value) {
in 4..9 -> println("in 4..9") //區間判斷
3 -> println("value is 3") //相等性判斷
2, 6 -> println("value is 2 or 6") //多值相等性判斷
is Int -> println("is Int") //類型判斷
else -> println("else") //若是以上條件都不知足,則執行 else
}
複製代碼
fun main() {
//返回 when 表達式
fun parser(obj: Any): String =
when (obj) {
1 -> "value is 1"
"4" -> "value is string 4"
is Long -> "value type is long"
else -> "unknown"
}
println(parser(1))
println(parser(1L))
println(parser("4"))
println(parser(100L))
println(parser(100.0))
}
value is 1
value type is long
value is string 4
value type is long
unknown
複製代碼
此外,When 循環也能夠不帶參數
when {
1 > 5 -> println("1 > 5")
3 > 1 -> println("3 > 1")
}
複製代碼
和 Java 中的 for 循環最爲相似的形式是
val list = listOf(1, 4, 10, 34, 10)
for (value in list) {
println(value)
}
複製代碼
經過索引來遍歷
val items = listOf("H", "e", "l", "l", "o")
//經過索引來遍歷List
for (index in items.indices) {
println("${index}對應的值是:${items[index]}")
}
複製代碼
也能夠在每次循環中獲取當前索引和相應的值
val list = listOf(1, 4, 10, 34, 10)
for ((index, value) in list.withIndex()) {
println("index : $index , value :$value")
}
複製代碼
也能夠自定義循環區間
for (index in 2..10) {
println(index)
}
複製代碼
while 和 do/while 與 Java 中的區別不大
val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
while (index < list.size) {
println(list[index])
index++
}
複製代碼
val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
var index = 0
do {
println(list[index])
index++
} while (index < list.size)
複製代碼
kotlin 有三種結構化跳轉表達式:
在 kotlin 中任何表達式均可以用標籤(label)來標記,標籤的格式爲標識符後跟 @ 符號,例如:abc@ 、fooBar@ 都是有效的標籤
fun main() {
fun1()
}
fun fun1() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
loop@ for (it in list) {
if (it == 8) {
continue
}
if (it == 23) {
break@loop
}
println("value is $it")
}
println("function end")
}
複製代碼
value is 1
value is 4
value is 6
value is 12
function end
複製代碼
kotlin 有函數字面量、局部函數和對象表達式。所以 kotlin 的函數能夠被嵌套
標籤限制的 return 容許咱們從外層函數返回,最重要的一個用途就是從 lambda 表達式中返回。一般狀況下使用隱式標籤更方便,該標籤與接受該 lambda 的函數同名
fun main() {
fun1()
fun2()
fun3()
fun4()
fun5()
}
fun fun1() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
return
}
println("value is $it")
}
println("function end")
// value is 1
// value is 4
// value is 6
}
fun fun2() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
return@fun2
}
println("value is $it")
}
println("function end")
// value is 1
// value is 4
// value is 6
}
//fun3() 和 fun4() 中使用的局部返回相似於在常規循環中使用 continue
fun fun3() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach {
if (it == 8) {
return@forEach
}
println("value is $it")
}
println("function end")
// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}
fun fun4() {
val list = listOf(1, 4, 6, 8, 12, 23, 40)
list.forEach loop@{
if (it == 8) {
return@loop
}
println("value is $it")
}
println("function end")
// value is 1
// value is 4
// value is 6
// value is 12
// value is 23
// value is 40
// function end
}
fun fun5() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) {
//局部返回到匿名函數的調用者,即 forEach 循環
return
}
println("value is $value")
})
println("function end")
}
複製代碼
Ranges 表達式使用一個 .. 操做符來聲明一個閉區間,它被用於定義實現了一個 RangTo 方法
如下三種聲明形式是等價的
var index = 5
if (index >= 0 && index <= 10) {
}
if (index in 0..10) {
}
if (index in 0.rangeTo(10)) {
}
複製代碼
數字類型的 ranges 在被迭代時,編譯器會將它們轉換爲與 Java 中使用 index 的 for 循環的相同字節碼的方式來進行優化
Ranges 默認會自增加,因此若是像如下的代碼就不會被執行
for (index in 10..0) {
println(index)
}
複製代碼
能夠改用 downTo 函數來將之改成遞減
for (index in 10 downTo 0) {
println(index)
}
複製代碼
能夠在 ranges 中使用 step 來定義每次循環遞增或遞增的長度:
for (index in 1..8 step 2){
println(index)
}
for (index in 8 downTo 1 step 2) {
println(index)
}
複製代碼
以上聲明的都是閉區間,若是想聲明的是開區間,可使用 until 函數:
for (index in 0 until 4){
println(index)
}
複製代碼
擴展函數 reversed()
可用於返回將區間反轉後的序列
val rangeTo = 1.rangeTo(3)
for (i in rangeTo) {
println(i) //1 2 3
}
for (i in rangeTo.reversed()) {
println(i) //3 2 1
}
複製代碼
kotlin 中的類和方法默認都是 final 的,即不可繼承的,若是想容許建立一個類的子類,須要使用 open 修飾符來標識這個類,此外,也須要爲每個但願被重寫的屬性和方法添加 open 修飾符
open class View {
open fun click() {
}
//不能在子類中被重寫
fun longClick() {
}
}
class Button : View() {
override fun click() {
super.click()
}
}
複製代碼
若是重寫了一個基類或者接口的成員,重寫了的成員一樣默認是 open 的。例如,若是 Button 類是 open 的,則其子類也能夠重寫其 click() 方法
open class Button : View() {
override fun click() {
super.click()
}
}
class CompatButton : Button() {
override fun click() {
super.click()
}
}
複製代碼
若是想要類的子類重寫該方法的實現,能夠顯式地將重寫的成員標記爲 final
open class Button : View() {
override final fun click() {
super.click()
}
}
複製代碼
public 修飾符是限制級最低的修飾符,是默認的修飾符。若是一個定義爲 public 的成員被包含在一個 private 修飾的類中,那麼這個成員在這個類之外也是不可見的
protected 修飾符只能被用在類或者接口中的成員上。在 Java 中,能夠從同一個包中訪問一個 protected 的成員,但對於 kotlin 來講,protected 成員只在該類和它的子類中可見。此外,protected 不適用於頂層聲明
一個定義爲 internal 的包成員,對其所在的整個 module 可見。若是它是一個其它領域的成員,它就須要依賴那個領域的可見性了。好比,若是咱們寫了一個 private 類,那麼它的 internal 修飾的函數的可見性就會限制於它所在的這個類的可見性
咱們能夠訪問同一個 module 中的 internal 修飾的類,可是其它 module 是訪問不到該 internal 類的,該修飾符可用於對外發布的開源庫,將開源庫中不但願被外部引用的代碼設爲 internal 權限,可避免對外部引用庫形成混淆
根據 Jetbrains 的定義,一個 module 應該是一個單獨的功能性的單位,能夠看作是一塊兒編譯的 kotlin 文件的集合,它應該是能夠被單獨編譯、運行、測試、debug 的。至關於在 Android Studio 中主工程引用的 module,Eclipse 中在一個 workspace 中的不一樣的 project
private 修飾符是限制級最高的修飾符,kotlin 容許在頂層聲明中使用 private 可見性,包括類、函數和屬性,這表示只在本身所在的文件中可見,因此若是將一個類聲明爲 private,就不能在定義這個類以外的地方中使用它。此外,若是在一個類裏面使用了 private 修飾符,那訪問權限就被限制在這個類裏面,繼承這個類的子類也不能使用它。因此若是類、對象、接口等被定義爲 private,那麼它們只對被定義所在的文件可見。若是被定義在了類或者接口中,那它們只對這個類或者接口可見
修飾符 | 類成員 | 頂層聲明 |
---|---|---|
public(默認) | 全部地方可見 | 全部地方可見 |
internal | 模塊中可見 | 模塊中可見 |
protected | 子類中可見 | |
private | 類中可見 | 文件中可見 |
在 kotlin 中,類型系統將一個引用分爲能夠容納 null (可空引用)或者不能容納 null(非空引用)兩種類型。 例如,String 類型的常規變量不能指向 null
var name: String = "leavesC"
//編譯錯誤
//name = null
複製代碼
若是但願一個變量能夠儲存 null 引用,須要顯式地在類型名稱後面加上問號
var name: String? = "leavesC"
name = null
複製代碼
問號能夠加在任何類型的後面來表示這個類型的變量能夠存儲 null 引用:Int?、Doubld? 、Long?
等
kotlin 對可空類型的顯式支持有助於防止 NullPointerException 致使的異常問題,編譯器不容許不對可空變量作 null 檢查就直接調用其屬性。這個強制規定使得開發者必須在編碼初期就考慮好變量的可賦值範圍併爲其各個狀況作好分支處理
fun check(name: String?): Boolean {
//error,編譯器不容許不對 name 作 null 檢查就直接調用其屬性
return name.isNotEmpty()
}
複製代碼
正確的作法事顯式地進行 null 檢查
fun check(name: String?): Boolean {
if (name != null) {
return name.isNotEmpty()
}
return false
}
複製代碼
安全調用運算符:?.
容許把一次 null 檢查和一次方法調用合併爲一個操做,若是變量值非空,則方法或屬性會被調用,不然直接返回 null
例如,如下兩種寫法是徹底等同的:
fun check(name: String?) {
if (name != null) {
println(name.toUpperCase())
} else {
println(null)
}
}
fun check(name: String?) {
println(name?.toUpperCase())
}
複製代碼
Elvis 運算符:?:
用於替代 ?.
直接返回默認值 null 的狀況,Elvis 運算符接收兩個運算數,若是第一個運算數不爲 null ,運算結果就是其運算結果值,若是第一個運算數爲 null ,運算結果就是第二個運算數
例如,如下兩種寫法是徹底等同的:
fun check(name: String?) {
if (name != null) {
println(name)
} else {
println("default")
}
}
fun check(name: String?) {
println(name ?: "default")
}
複製代碼
安全轉換運算符:as?
用於把值轉換爲指定的類型,若是值不適合該類型則返回 null
fun check(any: Any?) {
val result = any as? String
println(result ?: println("is not String"))
}
複製代碼
非空斷言用於把任何值轉換爲非空類型,若是對 null 值作非空斷言,則會拋出異常
fun main() {
var name: String? = "leavesC"
check(name) //7
name = null
check(name) //kotlinNullPointerException
}
fun check(name: String?) {
println(name!!.length)
}
複製代碼
爲可空類型定義擴展函數是一種更強大的處理 null 值的方式,能夠容許接收者爲 null 的調用,並在該函數中處理 null ,而不是在確保變量不爲 null 以後再調用它的方法
例如,以下方法能夠被正常調用而不會發生空指針異常
val name: String? = null
println(name.isNullOrEmpty()) //true
複製代碼
isNullOrEmpty()
的方法簽名以下所示,能夠看到這是爲可空類型 CharSequence? 定義的擴展函數,方法中已經處理了方法調用者爲 null 的狀況
@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}
return this == null || this.length == 0
}
複製代碼
平臺類型是 kotlin 對 java 所做的一種平衡性設計。kotlin 將對象的類型分爲了可空類型和不可空類型兩種,但 java 平臺的一切對象類型均爲可空的,當在 kotlin 中引用 java 變量時,若是將全部變量均歸爲可空類型,最終將多出許多 null 檢查;若是均當作不可空類型,那麼就很容易就寫出忽略了NPE 風險的代碼。爲了平衡二者,kotlin 引入了平臺類型,即當在 kotlin 中引用 java 變量值時,既能夠將之當作可空類型,也能夠將之當作不可空類型,由開發者本身來決定是否進行 null 檢查
is 與 !is 操做符用於在運行時檢查對象是否符合給定類型:
fun main() {
val strValue = "leavesC"
parserType(strValue) //value is String , length : 7
val intValue = 100
parserType(intValue) //value is Int , toLong : 100
val doubleValue = 100.22
parserType(doubleValue) //value !is Long
val longValue = 200L
parserType(longValue) //unknown
}
fun parserType(value: Any) {
when (value) {
is String -> println("value is String , length : ${value.length}")
is Int -> println("value is Int , toLong : ${value.toLong()}")
!is Long -> println("value !is Long")
else -> println("unknown")
}
}
複製代碼
在許多狀況下,不須要在 kotlin 中使用顯式轉換操做符,由於編譯器跟蹤不可變值的 is 檢查以及顯式轉換,並在須要時自動插入安全的轉換
例如,對於如下例子來講,當判斷 value 爲 String 類型經過時,就能夠直接將 value 當作 String 類型變量並調用其內部屬性
fun main() {
val strValue = "leavesC"
parserType(strValue) //value is String , length : 7
val intValue = 100
parserType(intValue) //value is Int , toLong : 100
val doubleValue = 100.22
parserType(doubleValue) //value !is Long
val longValue = 200L
parserType(longValue) //unknown
}
fun parserType(value: Any) {
when (value) {
is String -> println("value is String , length : ${value.length}")
is Int -> println("value is Int , toLong : ${value.toLong()}")
!is Long -> println("value !is Long")
else -> println("unknown")
}
}
複製代碼
編譯器會指定根據上下文環境,將變量智能轉換爲合適的類型
if (value !is String) return
//若是 value 非 String 類型時直接被 return 了,因此此處能夠直接訪問其 length 屬性
println(value.length)
// || 右側的 value 被自動隱式轉換爲字符串,因此能夠直接訪問其 length 屬性
if (value !is String || value.length > 0) {
}
// && 右側的 value 被自動隱式轉換爲字符串,因此能夠直接訪問其 length 屬性
if (value is String && value.length > 0) {
}
複製代碼
若是轉換是不可能的,轉換操做符 as
會拋出一個異常。所以,咱們稱之爲不安全的轉換操做符
fun main() {
parserType("leavesC") //value is String , length is 7
parserType(10) //會拋出異常 ClassCastException
}
fun parserType(value: Any) {
val tempValue = value as String
println("value is String , length is ${tempValue.length}")
}
複製代碼
須要注意的是,null 不能轉換爲 String 變量,由於該類型不是可空的
所以以下轉換會拋出異常
val x = null
val y: String = x as String //會拋出異常 TypeCastException
複製代碼
爲了匹配安全,能夠轉換的類型聲明爲可空類型
val x = null
val y: String? = x as String?
複製代碼
可使用安全轉換操做符 as? 來避免在轉換時拋出異常,它在失敗時返回 null
val x = null
val y: String? = x as? String
複製代碼
儘管以上例子 as? 的右邊是一個非空類型的 String,可是其轉換的結果是可空的
類的概念就是把數據和處理數據的代碼封裝成一個單一的實體。在 Java 中,數據存儲在一個私有字段中,經過提供訪問器方法:getter 和 setter 來訪問或者修改數據
在 Java 中如下的示例代碼是很常見的,Point 類包含不少重複的代碼:經過構造函數把參數賦值給有着相同名稱的字段,經過 getter 來獲取屬性值
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
}
複製代碼
使用 kotlin 來聲明 Point 類則只須要一行代碼,二者徹底等同
class Point(val x: Int, val y: Int)
複製代碼
kotlin 也使用關鍵字 class 來聲明類,類聲明由類名、類頭(指定其類型參數、主構造函數等)以及由花括號包圍的類體構成,類頭與類體都是可選的,若是一個類沒有類體,能夠省略花括號。此外,kotlin 中類默認是 publish(公有的) 且 final (不可繼承)的
kotlin 區分了主構造方法(在類體外部聲明)和次構造方法(在類體內部聲明),一個類能夠有一個主構造函數和多個次構造函數,此外也容許在初始化代碼塊中 init
添加額外的初始化邏輯
主構造函數是類頭的一部分,跟在類名(和可選的類型參數)後,主構造函數的參數能夠是可變的(var)或只讀的(val)
class Point constructor(val x: Int, val y: Int) {
}
複製代碼
若是主構造函數沒有任何註解或者可見性修飾符,能夠省略 constructor 關鍵字
class Point(val x: Int, val y: Int) {
}
//若是不包含類體,則能夠省略花括號
class Point(val x: Int, val y: Int)
複製代碼
若是構造函數有註解或可見性修飾符,則 constructor 關鍵字是必需的,而且這些修飾符在它前面
class Point public @Inject constructor(val x: Int, val y: Int) {
}
複製代碼
主構造函數不能包含任何的代碼,初始化的代碼能夠放到以 init 關鍵字做爲前綴的初始化塊(initializer blocks)中,初始化塊包含了在類被建立時執行的代碼,主構造函數的參數能夠在初始化塊中使用。若是須要的話,也能夠在一個類中聲明多個初始化語句塊。須要注意的是,構造函數的參數若是用 val/var 進行修飾,則至關於在類內部聲明瞭一個同名的全局屬性。若是不加 val/var 進行修飾,則構造函數只能在 init 函數塊和全局屬性初始化時進行引用
此外,要建立一個類的實例不須要使用 Java 中的 new 關鍵字,像普通函數同樣調用構造函數便可
class Point(val x: Int, val y: Int) {
init {
println("initializer blocks , x value is: $x , y value is: $y")
}
}
fun main() {
Point(1, 2) // initializer blocks , x value is: 1 , y value is: 2
}
複製代碼
主構造函數的參數也能夠在類體內聲明的屬性初始化器中使用
class Point(val x: Int, val y: Int) {
private val localX = x + 1
private val localY = y + 1
init {
println("initializer blocks , x value is: $x , y value is: $y")
println("initializer blocks , localX value is: $localX , localY value is: $localY")
}
}
fun main() {
Point(1, 2)
//initializer blocks , x value is: 1 , y value is: 2
//initializer blocks , localX value is: 2 , localY value is: 3
}
複製代碼
類也能夠聲明包含前綴 constructor 的次構造函數。若是類有一個主構造函數,每一個次構造函數都須要直接委託給主構造函數或者委託給另外一個次構造函數以此進行間接委託,用 this 關鍵字來進行指定便可
class Point(val x: Int, val y: Int) {
private val localX = x + 1
private val localY = y + 1
init {
println("initializer blocks , x value is: $x , y value is: $y")
println("initializer blocks , localX value is: $localX , localY value is: $localY")
}
constructor(base: Int) : this(base + 1, base + 1) {
println("constructor(base: Int)")
}
constructor(base: Long) : this(base.toInt()) {
println("constructor(base: Long)")
}
}
fun main() {
Point(100)
//initializer blocks , x value is: 101 , y value is: 101
//initializer blocks , localX value is: 102 , localY value is: 102
//constructor(base: Int)
Point(100L)
//initializer blocks , x value is: 101 , y value is: 101
//initializer blocks , localX value is: 102 , localY value is: 102
//constructor(base: Int)
//constructor(base: Long)
}
複製代碼
初始化塊中的代碼實際上會成爲主構造函數的一部分,委託給主構造函數會做爲次構造函數的第一條語句,所以全部初始化塊中的代碼都會在次構造函數體以前執行。即便該類沒有主構造函數,這種委託仍會隱式發生,而且仍會執行初始化塊。若是一個非抽象類沒有聲明任何(主或次)構造函數,會默認生成一個不帶參數的公有主構造函數
在 Java 中,字段和其訪問器的組合被稱做屬性。在 kotlin 中,屬性是頭等的語言特性,徹底替代了字段和訪問器方法。在類中聲明一個屬性和聲明一個變量同樣是使用 val 和 var 關鍵字。val 變量只有一個 getter ,var 變量既有 getter 也有 setter
fun main() {
val user = User()
println(user.name)
user.age = 200
}
class User() {
val name: String = "leavesC"
var age: Int = 25
}
複製代碼
訪問器的默認實現邏輯很簡單:建立一個存儲值的字段,以及返回屬性值的 getter 和更新屬性值的 setter。若是須要的話,也能夠自定義訪問器
例如,如下就聲明瞭三個帶自定義訪問器的屬性
class Point(val x: Int, val y: Int) {
val isEquals1: Boolean
get() {
return x == y
}
val isEquals2
get() = x == y
var isEquals3 = false
get() = x > y
set(value) {
field = !value
}
}
複製代碼
若是僅須要改變一個訪問器的可見性或者爲其添加註解,那麼能夠定義訪問器而不定義其實現
fun main() {
val point = Point(10, 10)
println(point.isEquals1)
//如下代碼會報錯
//point.isEquals1 = true
}
class Point(val x: Int, val y: Int) {
var isEquals1: Boolean = false
get() {
return x == y
}
private set
}
複製代碼
通常地,非空類型的屬性必須在構造函數中初始化,但像使用了 Dagger2 這種依賴注入框架的項目來講就十分的不方便了,爲了應對這種狀況,能夠用 lateinit 修飾符來標記該屬性,用於告訴編譯器該屬性會在稍後的時間被初始化
用 lateinit 修飾的屬性或變量必須爲非空類型,而且不能是原生類型
class Point(val x: Int, val y: Int)
class Example {
lateinit var point: Point
var point2: Point
constructor() {
point2 = Point(10, 20)
}
}
複製代碼
若是訪問了一個未通過初始化的 lateinit 變量,則會拋出一個包含具體緣由(該變量未初始化)的異常信息
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property point has not been initialized
複製代碼
聲明爲 abstract 的類內部能夠包含沒有實現體的成員方法,且該成員方法也用 abstract 標記,這種類稱爲抽象類,包含的沒有實現體的方法稱爲抽象方法
此外,咱們並不須要用 open 標註一個抽象類或者抽象方法,由於這是默認聲明的
abstract class BaseClass {
abstract fun fun1()
}
class Derived : BaseClass() {
override fun fun1() {
}
}
複製代碼
數據類是一種很是強大的類,能夠避免重複建立 Java 中的用於保存狀態但又操做很是簡單的 POJO 的模版代碼,它們一般只提供了用於訪問它們屬性的簡單的 getter 和 setter
定義一個新的數據類很是簡單,例如
data class Point(val x: Int, val y: Int)
複製代碼
數據類默認地爲主構造函數中聲明的全部屬性生成了以下幾個方法
爲了確保生成的代碼的一致性以及有意義的行爲,數據類必須知足如下要求:
能夠利用 IDEA 來反編譯查看 Point 類的 Java 實現,瞭解其內部實現
public final class Point {
private final int x;
private final int y;
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public final int component1() {
return this.x;
}
public final int component2() {
return this.y;
}
@NotNull
public final Point copy(int x, int y) {
return new Point(x, y);
}
// $FF: synthetic method
// $FF: bridge method
@NotNull
public static Point copy$default(Point var0, int var1, int var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = var0.x;
}
if ((var3 & 2) != 0) {
var2 = var0.y;
}
return var0.copy(var1, var2);
}
public String toString() {
return "Point(x=" + this.x + ", y=" + this.y + ")";
}
public int hashCode() {
return this.x * 31 + this.y;
}
public boolean equals(Object var1) {
if (this != var1) {
if (var1 instanceof Point) {
Point var2 = (Point)var1;
if (this.x == var2.x && this.y == var2.y) {
return true;
}
}
return false;
} else {
return true;
}
}
}
複製代碼
經過數據類能夠簡化不少的通用操做,能夠很方便地進行:格式化輸出變量值、映射對象到變量、對比變量之間的相等性、複製變量等操做
fun main() {
val point1 = Point(10, 20)
val point2 = Point(10, 20)
println("point1 toString() : $point1") //point1 toString() : Point(x=10, y=20)
println("point2 toString() : $point2") //point2 toString() : Point(x=10, y=20)
val (x, y) = point1
println("point1 x is $x,point1 y is $y") //point1 x is 10,point1 y is 20
//在 kotlin 中,「 == 」 至關於 Java 的 equals 方法
//而 「 === 」 至關於 Java 的 「 == 」 方法
println("point1 == point2 : ${point1 == point2}") //point1 == point2 : true
println("point1 === point2 : ${point1 === point2}") //point1 === point2 : false
val point3 = point1.copy(y = 30)
println("point3 toString() : $point3") //point3 toString() : Point(x=10, y=30)
}
複製代碼
須要注意的是,數據類的 toString()、equals()、hashCode()、copy()
等方法只考慮主構造函數中聲明的屬性,所以在比較兩個數據類對象的時候可能會有一些意想不到的結果
data class Point(val x: Int) {
var y: Int = 0
}
fun main() {
val point1 = Point(10)
point1.y = 10
val point2 = Point(10)
point2.y = 20
println("point1 == point2 : ${point1 == point2}") //point1 == point2 : true
println("point1 === point2 : ${point1 === point2}") //point1 === point2 : false
}
複製代碼
Sealed 類(密封類)用於對類可能建立的子類進行限制,用 Sealed 修飾的類的直接子類只容許被定義在 Sealed 類所在的文件中(密封類的間接繼承者能夠定義在其餘文件中),這有助於幫助開發者掌握父類與子類之間的變更關係,避免因爲代碼更迭致使的潛在 bug,且密封類的構造函數只能是 private 的
例如,對於 View 類,其子類只能定義在與之同一個文件裏,Sealed 修飾符修飾的類也隱含表示該類爲 open 類,所以無需再顯式地添加 open 修飾符
sealed class View {
fun click() {
}
}
class Button : View() {
}
class TextView : View() {
}
複製代碼
由於 Sealed 類的子類對於編譯器來講是可控的,因此若是在 when 表達式中處理了全部 Sealed 類的子類,那就不須要再提供 else 默認分支。即便之後因爲業務變更又新增了 View 子類,編譯器也會檢測到 check 方法缺乏分支檢查後報錯,因此說 check 方法是類型安全的
fun check(view: View): Boolean {
when (view) {
is Button -> {
println("is Button")
return true
}
is TextView -> {
println("is TextView")
return true
}
}
}
複製代碼
kotlin 也提供了枚舉的實現,相比 Java 須要多使用 class 關鍵字來聲明枚舉
enum class Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
複製代碼
枚舉能夠聲明一些參數
enum class Day(val index: Int) {
SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6)
}
複製代碼
此外,枚舉也能夠實現接口
interface OnChangedListener {
fun onChanged()
}
enum class Day(val index: Int) : OnChangedListener {
SUNDAY(0) {
override fun onChanged() {
}
},
MONDAY(1) {
override fun onChanged() {
}
}
}
複製代碼
枚舉也包含有一些共有函數
fun main() {
val day = Day.FRIDAY
//獲取值
val value = day.index //5
//經過 String 獲取相應的枚舉值
val value1 = Day.valueOf("SUNDAY") //SUNDAY
//獲取包含全部枚舉值的數組
val value2 = Day.values()
//獲取枚舉名
val value3 = Day.SUNDAY.name //SUNDAY
//獲取枚舉聲明的位置
val value4 = Day.TUESDAY.ordinal //2
}
複製代碼
使用對象表達式來建立匿名內部類實例
interface OnClickListener {
fun onClick()
}
class View {
fun setClickListener(clickListener: OnClickListener) {
}
}
fun main() {
val view = View()
view.setClickListener(object : OnClickListener {
override fun onClick() {
}
})
}
複製代碼
在 kotlin 中在類裏面再定義的類默認是嵌套類,此時嵌套類不會包含對外部類的隱式引用
class Outer {
private val bar = 1
class Nested {
fun foo1() = 2
//錯誤
//fun foo2() = bar
}
}
fun main() {
val demo = Outer.Nested().foo1()
}
複製代碼
以上代碼經過 IDEA 反編譯後能夠看到其內部的 Java 實現方式
能夠看到 Nested 其實就是一個靜態類,所以 foo2() 不能訪問外部類的非靜態成員,也不用先聲明 Outer 變量再指向 Nested 類,而是直接經過 Outer 類指向 Nested 類
public final class Outer {
private final int bar = 1;
public static final class Nested {
public final int foo1() {
return 2;
}
}
}
public final class MainkotlinKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
int demo = (new Outer.Nested()).foo1();
}
}
複製代碼
若是須要去訪問外部類的成員,須要用 inner 修飾符來標註被嵌套的類,這稱爲內部類。內部類會隱式持有對外部類的引用
class Outer {
private val bar = 1
inner class Nested {
fun foo1() = 2
fun foo2() = bar
}
}
fun main() {
val demo = Outer().Nested().foo2()
}
複製代碼
再來看其內部的 Java 實現方式
使用 inner 來聲明 Nested 類後,就至關於將之聲明爲非靜態內部類,所以 foo2() 能訪問其外部類的非靜態成員,在聲明 Nested 變量前也須要經過 Outer 變量來指向其內部的 Nested 類
public final class Outer {
private final int bar = 1;
public final class Nested {
public final int foo1() {
return 2;
}
public final int foo2() {
return Outer.this.bar;
}
}
}
public final class MainkotlinKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
int demo = (new Outer().new Nested()).foo2();
}
}
複製代碼
類A在類B中聲明 | 在Java中 | 在kotlin中 |
---|---|---|
嵌套類(不存儲外部類的引用) | static class A | class A |
內部類(存儲外部類的引用) | class A | inner class A |
kotlin 中的接口與 Java 8 中的相似,能夠包含抽象方法的定義以及非抽象方法的實現,不須要使用 default 關鍵字來標註有默認實現的非抽象方法,但在實現接口的抽象方法時須要使用 override 進行標註
fun main() {
val view = View()
view.click()
view.longClick()
}
class View : Clickable {
override fun click() {
println("clicked")
}
}
interface Clickable {
fun click()
fun longClick() = println("longClicked")
}
複製代碼
若是一個類實現了多個接口,而接口包含帶有默認實現且簽名相同的方法,此時編譯器就會要求開發者必須顯式地實現該方法,能夠選擇在該方法中調用不一樣接口的相應實現
class View : Clickable, Clickable2 {
override fun click() {
println("clicked")
}
override fun longClick() {
super<Clickable>.longClick()
super<Clickable2>.longClick()
}
}
interface Clickable {
fun click()
fun longClick() = println("longClicked")
}
interface Clickable2 {
fun click()
fun longClick() = println("longClicked2")
}
複製代碼
接口中能夠包含抽象屬性聲明,接口不定義該抽象屬性是應該存儲到一個支持字段仍是經過 getter 來獲取,接口自己並不包含任何狀態,所以只有實現這個接口的類在須要的狀況下會存儲這個值
看如下例子,Button 類和 TextView 類都實現了 Clickable 接口,並都提供了取得 statusValue 值的方式
Button 類提供了一個自定義的 getter 用於在每次訪問時從新獲取 statusValue 值,所以在屢次獲取屬性值時其值可能都會不一致,由於每次 getRandom() 方法都會被調用
TextView 類中的 statusValue 屬性有一個支持字段來存儲在類初始化時獲得的數據,所以其值在初始化後是不會再次獲取值,即 TextView 類中的 getRandom() 只會被調用一次
fun main() {
val button = Button()
println(button.statusValue)
val textView = TextView()
println(textView.statusValue)
}
class Button : Clickable {
override val statusValue: Int
get() = getRandom()
private fun getRandom() = Random().nextInt(10)
}
class TextView : Clickable {
override val statusValue: Int = getRandom()
private fun getRandom() = Random().nextInt(10)
}
interface Clickable {
val statusValue: Int
}
複製代碼
除了能夠聲明抽象屬性外,接口還能夠包含具備 getter 和 setter 的屬性,只要它們沒有引用一個支持字段(支持字段須要在接口中存儲狀態,而這是不容許的)
interface Clickable {
val statusValue: Int
val check: Boolean
get() = statusValue > 10
}
複製代碼
在 kotlin 中全部類都有一個共同的超類 Any ,對於沒有超類聲明的類來講它就是默認超類。須要注意的是, Any 並非 java.lang.Object ,它除了 equals() 、 hashCode() 與 toString() 外沒有其餘屬性或者函數
要聲明一個顯式的超類,須要把父類名放到類頭的冒號以後
open class Base()
class SubClass() : Base()
複製代碼
當中,類上的 open 標註與 Java 中的 final 含義相反,用於容許其它類從這個類繼承。默認狀況下,kotlin 中全部的類都是 final
若是派生類有一個主構造函數,其基類型必須直接或間接調用基類的主構造函數
open class Base(val str: String)
class SubClass(val strValue: String) : Base(strValue)
class SubClass2 : Base {
constructor(strValue: String) : super(strValue)
constructor(intValue: Int) : super(intValue.toString())
constructor(doubValue: Double) : this(doubValue.toString())
}
複製代碼
與 Java 不一樣,kotlin 須要顯式標註可覆蓋的成員和覆蓋後的成員:
open class Base() {
open fun fun1() {
}
fun fun2() {
}
}
class SubClass() : Base() {
override fun fun1() {
super.fun1()
}
}
複製代碼
用 open 標註的函數才能夠被子類重載,子類用 override 表示該函數是要對父類的同簽名函數進行覆蓋。標記爲 override 的成員自己也是開放的,也就是說,它能夠被子類覆蓋。若是想禁止再次覆蓋,可使用 final 關鍵字標記 若是父類沒有使用 open 對函數進行標註,則子類不容許定義相同簽名的函數。對於一個 final 類(沒有用 open 標註的類)來講,使用 open 標記屬性和方法是無心義的
屬性覆蓋與方法覆蓋相似。在超類中聲明爲 open 的屬性,若是要進行覆蓋則必須在派生類中從新聲明且以 override 開頭,而且它們必須具備兼容的類型
每一個聲明的屬性能夠由具備初始化器的屬性或者具備 getter 方法的屬性覆蓋
open class Base {
open val x = 10
open val y: Int
get() {
return 100
}
}
class SubClass : Base() {
override val x = 100
override var y = 200
}
fun main() {
val base = Base()
println(base.x) //10
println(base.y) //100
val base1: Base = SubClass()
println(base1.x) //100
println(base1.y) //200
val subClass = SubClass()
println(subClass.x) //100
println(subClass.y) //200
}
複製代碼
此外,也能夠用一個 var 屬性覆蓋一個 val 屬性,但反之則不行。由於一個 val 屬性本質上聲明瞭一個 getter 方法,而將其覆蓋爲 var 只是在子類中額外聲明一個 setter 方法
能夠在主構造函數中使用 override 關鍵字做爲屬性聲明的一部分
open class Base {
open val str: String = "Base"
}
class SubClass(override val str: String) : Base()
fun main() {
val base = Base()
println(base.str) //Base
val subClass = SubClass("leavesC")
println(subClass.str) //leavesC
}
複製代碼
派生類能夠經過 super 關鍵字調用其超類的函數與屬性訪問器的實現
open class BaseClass {
open fun fun1() {
println("BaseClass fun1")
}
}
class SubClass : BaseClass() {
override fun fun1() {
super.fun1()
}
}
複製代碼
對於內部類來講,其自己就能夠直接調用調用外部類的函數
open class BaseClass2 {
private fun fun1() {
println("BaseClass fun1")
}
inner class InnerClass {
fun fun2() {
fun1()
}
}
}
複製代碼
但若是想要在一個內部類中訪問外部類的超類,則須要經過由外部類名限定的 super 關鍵字來實現
open class BaseClass {
open fun fun1() {
println("BaseClass fun1")
}
}
class SubClass : BaseClass() {
override fun fun1() {
println("SubClass fun1")
}
inner class InnerClass {
fun fun2() {
super@SubClass.fun1()
}
}
}
fun main() {
val subClass = SubClass()
val innerClass = subClass.InnerClass()
//BaseClass fun1
innerClass.fun2()
}
複製代碼
若是一個類從它的直接超類和實現的接口中繼承了相同成員的多個實現, 則必須覆蓋這個成員並提供其本身的實現來消除歧義
爲了表示採用從哪一個超類型繼承的實現,使用由尖括號中超類型名限定的 super 來指定,如 super< BaseClass >
open class BaseClass {
open fun fun1() {
println("BaseClass fun1")
}
}
interface BaseInterface {
//接口成員默認就是 open 的
fun fun1() {
println("BaseInterface fun1")
}
}
class SubClass() : BaseClass(), BaseInterface {
override fun fun1() {
//調用 SubClass 的 fun1() 函數
super<BaseClass>.fun1()
//調用 BaseInterface 的 fun1() 函數
super<BaseInterface>.fun1()
}
}
複製代碼
kotlin 的集合設計和 Java 不一樣的另外一項特性是:kotlin 把訪問數據的接口和修改集合數據的接口分開了,kotlin.collections.Collection
接口提供了遍歷集合元素、獲取集合大小、判斷集合是否包含某元素等操做,但這個接口沒有提供添加和移除元素的方法。kotlin.collections.MutableCollection
接口繼承於 kotlin.collections.Collection
接口,擴展出了用於添加、移除、清空元素的方法
就像 kotlin 對 val
和 var
的區分同樣,只讀集合接口與可變集合接口的分離能提升對代碼的可控性,若是函數接收 Collection
做爲形參,那麼就能夠知道該函數不會修改集合,而只是對數據進行讀取
如下是用來建立不一樣類型集合的函數
集合元素 | 只讀 | 可變 |
---|---|---|
List | listOf | mutableListOf、arrayListOf |
Set | setOf | mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf |
Map | mapOf | mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf |
val list = listOf(10, 20, 30, 40)
//不包含 add 方法
//list.add(100)
println(list.size)
println(list.contains(20))
val mutableList = mutableListOf("leavesC", "葉應是葉", "葉")
mutableList.add("Ye")
println(mutableList.size)
println(mutableList.contains("leavesC"))
複製代碼
由於 Java 並不會區分只讀集合與可變集合,即便 kotlin 中把集合聲明爲只讀的, Java 代碼也能夠修改這個集合,而 Java 代碼中的集合對 kotlin 來講也是可變性未知的,kotlin 代碼能夠將之視爲只讀的或者可變的,包含的元素也是能夠爲 null 或者不爲 null 的
例如,在 Java 代碼中 names 這麼一個 List< String > 類型的變量
public class JavaMain {
public static List<String> names = new ArrayList<>();
static {
names.add("leavesC");
names.add("Ye");
}
}
複製代碼
在 kotlin 中能夠用如下四種方式來引用變量 names
val list1: List<String?> = JavaMain.names
val list2: List<String> = JavaMain.names
val list3: MutableList<String> = JavaMain.names
val list4: MutableList<String?> = JavaMain.names
複製代碼
只讀集合不必定就是不可變的。例如,假設存在一個擁有隻讀類型接口的對象,該對象存在兩個不一樣的引用,一個只讀,一個可變,當可變引用修改了該對象後,這對只讀引用來講就至關於「只讀集合被修改了」,所以只讀集合並不老是線程安全的。若是須要在多線程環境下處理數據,須要保證正確地同步了對數據的訪問,或者使用支持併發訪問的數據結構
例如,list1 和 list1 引用到同一個集合對象, list3 對集合的修改同時會影響到 list1
val list1: List<String> = JavaMain.names
val list3: MutableList<String> = JavaMain.names
list1.forEach { it -> println(it) } //leavesC Ye
list3.forEach { it -> println(it) } //leavesC Ye
for (index in list3.indices) {
list3[index] = list3[index].toUpperCase()
}
list1.forEach { it -> println(it) } //LEAVESC YE
複製代碼
集合的可空性能夠分爲三種:
例如,intList1 能夠包含爲 null 的集合元素,但集合自己不能指向 null;intList2 不能夠包含爲 null 的集合元素,但集合自己能夠指向 null;intList3 能夠包含爲 null 的集合元素,且集合自己能指向 null
//List<Int?> 是能持有 Int? 類型值的列表
val intList1: List<Int?> = listOf(10, 20, 30, 40, null)
//List<Int>? 是能夠爲 null 的列表
var intList2: List<Int>? = listOf(10, 20, 30, 40)
intList2 = null
//List<Int?>? 是能夠爲 null 的列表,且能持有 Int? 類型值
var intList3: List<Int?>? = listOf(10, 20, 30, 40, null)
intList3 = null
複製代碼