今天給大家分享的是 Kotlin 這門語言有別的于 Java 的一些新特性,在 Kotlin 沒有發(fā)布的時候,我們使用 Java 進行 Android 開發(fā),現(xiàn)在呢?Google 已經(jīng)向宣布了,Kotlin 也可以進行 Android 開發(fā)(畢竟如果可以不用 Java 的話,也就不用和甲骨文有版權(quán)糾紛了),而且學習成本也很低,因為簡單的語法可以使得開發(fā)人員快速上手,且和 Java 百分百兼容,你的項目可以不用從頭開始再寫一遍,而是可以兩種語言共存,從而慢慢的過渡到 Kotlin。
我們可以在哪里學習 Kotlin 呢?
- Android developer https://developer.android.com/kotlin/index.html
- Kotlin 官網(wǎng) http://kotlinlang.org
- Kotlin 中文 https://www.kotlincn.net
- Kotlin 論壇 https://discuss.kotlinlang.org
- Kotlin 博客 https://blog.jetbrains.com/kotlin
當我們點進 Kotlin 的官網(wǎng)時,可以看到官網(wǎng)是如何介紹它的呢?

image.png
- 簡潔,大大減少樣板代碼的數(shù)量,如數(shù)據(jù)類,Lambda,單例創(chuàng)建等等。
- 安全,避免類的錯誤,如空指針異常,可空類型,類型檢測與自動轉(zhuǎn)換等等。
- 互操作,開發(fā)時可利用JVM,Android和瀏覽器的現(xiàn)有庫。
- 工具友好,任何 Java IDE 或者命令行都可以進行開發(fā)。
在介紹今天要講內(nèi)容之前,我想先來介紹 Kotlin 的一些基本使用,也就是一些基本語法,然后我再講新特性的時候也就更容易看得懂了。
1.如何定義變量
// In Java
// 變量
int index = 0;
// 常量
final PI = 3.14;
// In Kotlin
// 變量
var index: Int = 0
// 或者使用類型推導,編譯器會知道 index 是 Int 類型
var index = 0
// 常量
val PI = 3.14
2.如何定義方法
// In Java
public int sum(int a, int b) {
return a + b;
}
// In Kotlin
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
// 表達式函數(shù)體
// 這里使用了類型推導
fun sum(a: Int, b: Int) = a + b
3.如何定義類
In Java
interface AC {}
class Super {
public Super() {}
}
class Sub extends Super implements AC{
public Sub () {
super();
}
}
// In Kotlin
// 默認會自動生成一個無參構(gòu)造
open class B
// 這里必須顯示的調(diào)用父類的構(gòu)造方法
// 如果父類的的構(gòu)造帶參那么也必須調(diào)用帶參的構(gòu)造
class RB : B()
interface AC {
// 帶默認實現(xiàn)的方法
// Java 8 之后也支持接口方法的默認實現(xiàn),不過需要聲明 default 關鍵字
// 由于 Kotlin 是以 Java 6 為目標設計的,所以默認方法在轉(zhuǎn)成 Java 代碼后其實是
// 接口中有一個默認實現(xiàn)了接口的靜態(tài)類
fun showOff() = println("Clickable 默認實現(xiàn)")
}
// 沒有參數(shù)的話可以省略 ()
// 也可以省略大括號
// Kolin 的類默認是 public 和 final,想要被集成需要改為 open 的
// (p: Int) 這里被括號圍起來的語句就叫做主構(gòu)造方法
open class Super(p: Int)
// 這里需要寫 Super(10) 是因為需要調(diào)用父類的構(gòu)造函數(shù)
// 而接口 AC 并不存在構(gòu)造直接寫 AC 即可
// 無論是繼承還是現(xiàn)實都是通過 : 實現(xiàn)
// constructor 用來聲明一個構(gòu)造(主構(gòu)造),constructor 關鍵字可省略
class Sub constructor(s: Int): Super(s), AC {
// init 表示引入一個初始化語句塊,這種語句塊會在類被創(chuàng)建時執(zhí)行,并且與主構(gòu)造方法一起使用
init {
this.s = 0
}
}
// 實例化
// In Java
Sub sub = new Sub(2);
// In Kotlin
var sub = Sub(3)
接下來開始介紹今天的重點,Kotlin 的一些新特性~
1.擴展函數(shù)/成員(給別人的類添加方法)
// 在擴展函數(shù)中可以直接訪問被擴展的類的其他方法和屬性
// 但擴展函數(shù)并不允許打破它的封裝性,所以擴展函數(shù)不能訪問私有或是受保護的成員
// 對應 Java 代碼中 擴展函數(shù)是 static final 的,所以擴展函數(shù)是不能被子類重寫的
// 擴展函數(shù)并不是類的一部分,因為擴展函數(shù)是聲明在類外部的
// 如果擴展函數(shù)與成員函數(shù)簽名相同,往往成員函數(shù)會被優(yōu)先使用
// 書中解釋說擴展函數(shù)無非就是靜態(tài)函數(shù)的一個高效的語法糖
// 如:為 String 擴展一個 重復多次的字符串方法
// String.multiply 前面為為 String 擴展,后面為方法名
// int 為重復次數(shù) 返回一個 String
fun String.multiply(int: Int): String {
val stringBuild = StringBuffer()
for (i in 0 until int) {
// 這時候里面就有了一個 this,而這個 this 就指代調(diào)用的這方法的對象了
stringBuild.append(this)
}
return stringBuild.toString()
}
// Kotlin 中的調(diào)用
// 直接用字符串調(diào)用該方法即可
println("Jaaaelu ".multiply(8))
// Java 中的調(diào)用
// Java 中如何調(diào)用呢? 類名.方法名
// 這里其實相當于靜態(tài)方法
// 如 Expand.multiply("Jaaaelu ", 8)
// 給 String 擴展成員常量
// 并不能進行初始化,因為沒有合適的地方來存放值
val String.test: String
get() = "abc"
var StringBuilder.lastChar
// var 必須要有 getter 和 setter
get() = get(length - 1)
set(value) = this.setCharAt(length - 1, value)
2.默認參數(shù)
// Kotlin 可以給函數(shù)參數(shù)定義默認值,這樣大大降低了重載函數(shù)的必要性,
// 而且命名函數(shù)讓多參數(shù)函數(shù)的調(diào)用更加易讀。
// 我們現(xiàn)在為 Collection 寫一個集合拼接成字符串的一個擴展方法
// 拼接時我們指定前綴、后綴和分隔符
// 這里使用了默認參數(shù),為參數(shù)指定默認的值,這樣在調(diào)用方法的時候就
// 可以不指定參數(shù)或者只為某個參數(shù)指定值
fun <T> Collection<T>.collectionJoinToString(separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = ""): String {
val result = StringBuilder(prefix)
for ((index, value) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(value)
}
result.append(postfix)
return result.toString()
}
// 調(diào)用
val set = setOf(1, 2, 5)
// 全部使用默認值
println(set.collectionJoinToString())
// 為第一個參數(shù)賦值
println(set.collectionJoinToString("-"))
// 配合命名參數(shù),第一個參數(shù)值使用默認值,只為指定的參數(shù)賦值
println(set.collectionJoinToString(prefix = "[", postfix = "]"))
// 如果在方法上面添加 @JvmOverloads 那么在 Java 中調(diào)用也會生成該函數(shù)的各個重載版本
3.object 關鍵字
// object 關鍵字的核心用途就是定義一個類時同時創(chuàng)建一個實例。
// 一些不同的使用場景:
// 1.對象聲明是定義單例的一種方式。
// 2.伴生對象可以持有工廠方法和其他與這個類相關,但在調(diào)用時并不依賴類實例的方法。
// 它們的成員可以通過類名來訪問。
// 3.對象表達式用來替代 Java 的匿名內(nèi)部類。
// 1.創(chuàng)建單例
// 引入 object 后,與類一樣依然可以有屬性、方法、初始化語句等聲明,
// 但是唯一不允許的就是構(gòu)造方法(主構(gòu)造、從構(gòu)造)都沒有
object Patroll :Driver{
override fun drive() {
println("日常飆車")
}
private val allEmployees = arrayListOf<Person>()
fun calculateSalary() {
for (person in allEmployees) {
// ...
}
println("Jaaaelu")
}
}
// 調(diào)用
val p1:Driver = Patroll
val p2 = Patroll
// 2.伴生對象
// 關鍵字 companion,使用后可以通過類名訪問這個對象的方法屬性,看上去非常像是靜態(tài)調(diào)用
// 可以訪問私有
class ACompanion {
private fun privateFun() {
println("私有方法")
}
companion object {
val PI = 3.14
fun bar() {
println("測試方法")
}
}
}
// 調(diào)用
println(ACompanion.PI)
// 3.匿名內(nèi)部類的新寫法
// object 除了聲明單例外還能用來聲明匿名對象(也就是 Java 中的匿名內(nèi)部類)
// 于聲明對象不同,匿名對象不是單例,比如每次執(zhí)行 other()。都會創(chuàng)建新的對象實例
var count = 0
fun other() {
// 除了去掉名字外,語法與對象聲明相同
val a = object : Driver {
override fun drive() {
count++
// 也可以訪問外面的內(nèi)容
println("匿名內(nèi)部類 $count")
}
}
println(a)
a.drive()
}
4.Lambda 表達式
// Lambda 表達式本質(zhì)上就是可以傳遞給其他函數(shù)的一小段代碼(作為函數(shù)參數(shù)的代碼塊)
// 函數(shù)式表層提供了另一種解決方案:把函數(shù)作為值來看待
// 基礎語法
// { x: Int, y: Int -> x + y } 其中 x: Int, y: Int 為參數(shù), x + y 為函數(shù)體。
// Kotlin 中的 Lambda 始終用花括號包圍,只需箭頭就將參數(shù)列表與函數(shù)體分開。
// 可以將 Lambda 賦值給變量或者常量,然后調(diào)用
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))
// 正常 sum
// 相當于(Int, Int) -> Int
fun sumOld(arg1: Int, arg2: Int): Int {
return arg1 + arg2
}
// Lambda 表達式形式的 sum
val sumLambda = { arg1: Int, arg2: Int -> arg1 + arg2 }
// Lambda 表達式形式的 sum 且其中還能有其他語句
// 相當于(Int, Int) -> Int
val sumAndPrint = { arg1: Int, arg2: Int ->
println("$arg1 + $arg2 = ${arg1 + arg2}")
// 最后一行表示你的返回值
arg1 + arg2
}
// 沒有參數(shù)和返回值的時候,可以直接將其寫在大括號內(nèi)
val printHello = {
// 相當于函數(shù)體
println("Hello")
}
// 在作用域中訪問變量
// 捕捉的原理:當你捕捉 final 變量時,它的值和使用這個值的 Lambda 代碼一起存儲。
// 對于非 final 變量來說,它的值被封裝在一個特殊的包裝器中,這樣你就可以改變這個值,
// 這個包裝器的引用會和 Lambda
// 代碼一起被存儲。
fun printProblemCounts(responses: Collection<String>) {
// 默認情況下,局部變量的生命周期被限制在聲明這個變量的函數(shù)中
// 但如果被 Lambda 捕捉了,那么使用這個變量的代碼可以被存儲并稍后再執(zhí)行
var clientErrors = 0
var serverErrors = 0
responses.forEach {
if (it.startsWith("4")) {
// Kotlin 中允許在 Lambda 中訪問非 final 變量,甚至修改它們
// 從 Lambda 內(nèi)訪問外部變量,我們稱這些變量被 Lambda 捕捉
// 就像例子中的 clientErrors 以及 serverErrors
clientErrors++
} else if (it.startsWith("5")) {
serverErrors++
}
}
println("$clientErrors client errors, $serverErrors server errors")
}
// Lambda 的實現(xiàn)細節(jié):從 Kotlin 1.0 開始,每個 Lambda 表達式都會被編譯成一個匿名類,
// 除非它是一個內(nèi)聯(lián) Lambda
// 不過這里所說的匿名類只對期望函數(shù)式接口的 Java 方法有效
// 內(nèi)聯(lián)函數(shù):消除 Lambda 帶來的運行時開銷
// 例如 Lambda 表達式會被正常的編譯為匿名類,這樣每次調(diào)用 Lambda 就會創(chuàng)建一個類,
// 這樣會帶來額外的開銷。
// Kotlin 提供了 inline 修飾符標記一個函數(shù)
// 內(nèi)聯(lián)函數(shù)被編譯后,它的字節(jié)碼連同傳遞給它的 Lambda 的字節(jié)碼被插入到調(diào)用函數(shù)的代碼中,
// 這使得函數(shù)調(diào)用相比于直接編寫相同的代碼,不會產(chǎn)生額外的運行時開銷。
inline fun <T> synchronized(lock: Lock, action: () -> T): T {
lock.lock()
try {
return action()
} finally {
lock.unlock()
}
}
val l = Lock()
synchronized(l) {
println("J")
}
// 上面代碼會編譯為
l.lock()
try {
println("J")
} finally {
l.unlock()
}
// 內(nèi)聯(lián)函數(shù)的限制
// 由于不是所有的 Lambda 函數(shù)都可以被內(nèi)聯(lián),當函數(shù)內(nèi)斂的時候,
// 作為參數(shù)的 Lambda 表達式的函數(shù)會直接替換掉最終生成代碼中。
// 這也限制了函數(shù)體中對應的(Lambda)參數(shù)的使用,如果(Lambda)參數(shù)被調(diào)用,
// 這樣的代碼能被容易地內(nèi)聯(lián)。
// 但如果(Lambda)在某個地方被保存起來了,以便后面繼續(xù)使用,Lambda 表達式的代碼將不能被內(nèi)聯(lián)。
// 例如系統(tǒng)提供的這個函數(shù),它沒有直接調(diào)用作為 transform 參數(shù)傳遞進來的函數(shù),
// 而是將這個函數(shù)傳遞給了一個類的構(gòu)造,構(gòu)造方法將它保存在一個屬性中,
// 所以 transform 需要被編譯為標準的非內(nèi)聯(lián)的表示法,即一個實現(xiàn)了函數(shù)接口的匿名類。
// fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
// return TransformingSequence(this, transform)
// }
// 如果一個函數(shù)期望兩個或者更多 Lambda 參數(shù),可以選擇只內(nèi)聯(lián)一些參數(shù)。
inline fun foo(inlined: () -> Unit, noinline noinlined: () -> Unit) {
}
// 決定何時將函數(shù)聲明成內(nèi)聯(lián)
// 使用 inline 關鍵字只能提高帶有 Lambda 參數(shù)的函數(shù)的性能。
// 對于普通的函數(shù)調(diào)用,JVM 已經(jīng)提供了強大的內(nèi)聯(lián)支持。
// 在使用 inline 關鍵字時,應該注意代碼的長度,把一些和 Lambda 無關的方法抽取到一個
// 獨立非內(nèi)聯(lián)函數(shù)中,從而減少字節(jié)碼拷貝長度。
// 對于 Koltin 集合來說,提供了更多高階函數(shù)
// 高階函數(shù)就是以另一個函數(shù)作為參數(shù)或者返回值的函數(shù)
// 在 Kotlin 中函數(shù)可以用 Lambda 或者函數(shù)引用來表示
// 因此,任何以 Lambda 或者函數(shù)引用作為參數(shù)貨返回值的函數(shù)(或者兩者都是)都是高階函數(shù)
// 以集合為例:
// 檢查集合中所有元素是否都符合某個條件,可以通過 all 或 any 來實現(xiàn)
// all 判斷所有元素是否都滿足該條件
// any 判斷元素列表中是否存在至少一個匹配元素
// count 對滿足該表達式的元素計數(shù)
// find 找到一個滿足 Lambda 的第一個元素
// filter 會從集合中一處你不想要的元素
// map 函數(shù)對集合的每一個元素應用給定的函數(shù),并把結(jié)果收集到一個新集合
// flatMap 可將多列表平鋪
// groupBy 把列表轉(zhuǎn)為分組的 map
// 等等
// 不過對于某些高階函數(shù)來說,如做 map 或 filter 操作時,這些函數(shù)都會創(chuàng)建中間集合
// 如果元素數(shù)量過多,這種鏈式調(diào)用就會變得十分低效
// 所以這時候就引入序列,將上述操作變成使用序列,而不是直接返回集合
// 序列:惰性集合操作,由于序列中的元素求值是惰性的,因此,
// 可以使用序列更高效的對集合元素執(zhí)行鏈式操作,而不需要創(chuàng)建額外中間集合來保存中間過程中產(chǎn)生的結(jié)果
// 惰性操作是對元素逐個處理
// 先將集合轉(zhuǎn)為序列,然后進一步進行函數(shù)操作,最后再將序列轉(zhuǎn)為集合
// 這時就不會創(chuàng)建中間集合
// 下面的計算中.map(Person::name).filter { it.startsWith("A") } 就是中間操作
// 而 toList() 是末端操作
// 由于中間操作時惰性的,所以 map 和 filter 變換被延期了
// 末端操作才會觸發(fā)被延期的計算
// 序列的執(zhí)行順序是按照元素來的,所以打印出來的內(nèi)容是 map 第一個元素,filter 第一個元素,如此往下
// 而我們正常集合操作的話就是先全部 map,然后再全部 filter
println(people
.asSequence()
.map(Person::name)
.filter { it.startsWith("A") }
.toList())
5.可空性
// Kotlin 對可空類型的支持,可以幫助我們在編譯期,檢測出潛在的 NullPointerException 錯誤。
// Kotlin 提供了像安全調(diào)用(?.)、Elvis 運算符(?:)、
// 非空斷言(!!)及 let 函數(shù)這樣的工具來簡潔的處理可空類型。
// String = String,不能存儲 null 引用
// 所以默認情況下類型都是非空的
fun strLen(s: String) = s.length
// String? = String or null
// 這里是顯式的標記出使用可空的 String
// s?.length 表示如果 s 不為空會返回 s.length 否則返回 null
// 相當于 if (s != null) s.length else null
fun strLenCanNullNoException(s: String?) = s?.length
// 帶有默認值 ?. 后面跟的就是默認值
fun strLenCanNullNoExceptionAndWithDefault(s: String?) = s?.length ?: 0
// 雖然可以用作默認值,當然也可以用于其他功能,如拋出異常
fun strLenThrowException(s: String?) = s?.length ?: throw NullPointerException("字符串為空")
// s!!.length 表示如果 s 為 null 會直接拋出空指針異常,不為 null 則可以返回 s.length
// 一般情況下,只有我們知道 s 一定不為 null 的時候使用
// !! 也叫做非空斷言,盡量不在一行代碼中使用多個非空斷言,
// 因為你很難分清除是哪個 !! 讓你程序拋出空指針異常
fun strLenCanNullWillException(s: String?) = s!!.length
val p: Person? = Person("Gzw", 23)
// sendEmail 接收一個非空類型的 Person,所以需要先進行空判斷,判斷通過再繼續(xù)
if (p != null) sendEmail(p, "啦啦啦啦啦啦")
// 還可以使用另一種方式 let
// 如果 p 不為空,那么 it 就不為空,如果 p 為空什么都不會發(fā)生
// 這里必須是 ?. 的調(diào)用方式,否則會報錯
p?.let { sendEmail(it, ",,,,,") }
// Kotlin 中所以泛型類和泛型函數(shù)的類型參數(shù)默認都是可空的
fun <T> printlnHashCode(t: T) {
println(t?.hashCode())
}
// T 被推導為 Any?
printlnHashCode(null)
// 為類型參數(shù)添加非空上界后,現(xiàn)在的 T 就不是可空的了
fun <T: Any> printlnHashCodeNotNull(t: T) {
println(t.hashCode())
}
// Java 中的類型在 Kotlin 中被解釋成平臺類型,允許開發(fā)者把他們當做可空或非空來對待。
// Java 中 @Nullable + Type = Type? / @NotNull + Type = Type
// Java 中的 Type = Kotlin 中的 Type? or Type
// 如 Java 中的 ArrayList<String> 在 Kotlin 中被當做了 ArrayList<String?>?
// 表示基本數(shù)字的類型(如 Int)看起來用起來都像普通的類,但通常會被編譯成 Java 基本數(shù)據(jù)類型。
// 可空基本類型(如 Int?)對應著 Java 中的裝箱基本數(shù)據(jù)類型(如 Integer)。
// Any 類型是所有其他類型的超類型,類型于 Java 中的 Object。而 Unit 類比于 void。
6.集合
// 創(chuàng)建可空性的集合時,需要注意的時候,要小心決定什么是可空的,
// 是元素可空還是集合本身可空,還是兩者都可為空?
// List<Int?>、List<Int>?、List<Int?>?
// Kotlin 中把訪問集合數(shù)據(jù)的接口和修改集合數(shù)據(jù)的接口分開了
// (只讀集合 Collection 與可變集合 MutableCollection)
// kotlin.collections.Collection 只讀集合提供了 size、iterator、contains 等操作來查看讀取數(shù)據(jù)
// kotlin.collections.MutableCollection 可變的集合提供了 add、remove、clear 等修改集合的操作,
// 不過 MutableCollection 繼承自 Collection,所有也擁有那些讀取操作
// 如果函數(shù)接收 Collection 而不是 MutableCollection,
// 那么就很容易知道這個方法不會修改集合,只會讀取集合數(shù)據(jù)。
// 只讀集合并不一定是不可變的,當只讀集合和可變集合指向同一個集合對象的時候,可變可以進行操作,
// 然后只讀讀取操作后的集合。
// 只讀集合并不總是線程安全的。
// 即使在 Kotlin 中將集合聲明成只讀,Java 代碼也能夠修改這個集合,
// 因為 Java 中并不區(qū)分只讀集合和可變集合。
// List 創(chuàng)建集合函數(shù),只讀 -> listOf,可變 -> mutableListOf、arrayListOf
// Set 創(chuàng)建集合函數(shù),只讀 -> setOf,可變 -> mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
// Map 創(chuàng)建集合函數(shù),只讀 -> mapOf,可變 -> mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf
// 只讀集合并一定是不可變的
var mutable: MutableCollection<Int> = mutableListOf(1, 2, 3, 4, 5)
val c: Collection<Int> = mutable
var m: MutableCollection<Int> = mutable
// Array<Int> 將會是一個包含裝箱整型的數(shù)組
// IntArray 是基礎類型 int 的數(shù)組,也就是 Java 中的 int[],這樣效率會更高一些
// 通過 Java 方法操作了只讀集合
7.運算符
// Kotlin 允許使用對應名稱的函數(shù)來重載一些標準的數(shù)學運算,但是不能定義自己的運算符
// 可重載的二元算數(shù)運算符
// a * b -> times
// a / b -> div
// a % b -> mod
// a + b -> plus
// a - b -> minus
// 特殊運算符
// shl -> 帶符號左移
// shr -> 帶符號右移
// ushr -> 無符號右移
// and -> 按位與
// or -> 按位或
// xor -> 按位異或
// inv -> 按位取反
// 一元運算符
// +a -> unaryPlus
// -a -> unaryMinus
// !a -> not
// ++a, a++ -> inc
// --a, a-- -> dec
data class Point(val x: Int, val y: Int) {
// 重載 plus 運算符
// 且并不要求兩個運算數(shù)是相同類型,返回值也可以不同
// 不支持交換性
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
operator fun unaryMinus(): Point {
return Point(-x, -y)
}
}
// 也相當于 Point(10, 20).plus(Point(20, 10))
println(Point(10, 20) + Point(20, 10))
// 復合運算符
// += 可以被轉(zhuǎn)換為 plus 或者 plusAssign(但最好不要同時重載這兩個方法)
// point = point + Point(10, 20)
point += Point(10, 20)
println(point)
// 把運算符定義為擴展方法
operator fun Point.minus(other: Point): Point {
return Point(x - other.x, y - other.y)
}
// 等號運算符:equals,在 Kotlin 中使用 == 會被轉(zhuǎn)成 equals
// 而 != 也會被轉(zhuǎn)成 equals,只是返回的結(jié)果是相反的
// == 與 != 可用于空運算數(shù),因為這些運算符實際上會檢查運算數(shù)是否為 null
// 如 a == b,會先檢查 a 是否為空,如果不是,就會調(diào)用 a.equals(b),
// 否則兩個都是 null 的時候才會返回 true a == b -> a?.equals(b) ?: (b == bull)
// Comparable 便于比較值
class P(val x: Int, val y: Int) : Comparable<P> {
// 用于比較一個對象是否大于另一個對象
// 比較運算符 >, <, >=, <= 將被轉(zhuǎn)換為 compareTo
// 即 a > b -> a.compareTo(b) >= 0
override fun compareTo(other: P): Int {
return compareValuesBy(this, other, P::x, P::y)
}
// 重寫 equals 方法
// 這里我們雖然是重載了比較運算符 equals,但是方法前綴不是 operator 而是 override,
// 因為 equals 是 Any 中定義的方法,equals 不能實現(xiàn)擴展函數(shù),
// 因為集成自 Any 類的實現(xiàn)始終優(yōu)先于擴展方法
override fun equals(other: Any?): Boolean {
// 先檢查是否為同一個對象
// 這里 === 與 Java 中的 == 完全相同
// (檢查兩個參數(shù)是否是同一個對象的引用,基本類型的話會比較值)
// === 不能被重載
if (other === this) return true
// 檢查參數(shù)類型
if (other !is P) return false
// 檢查值
return other.x == x && other.y == y
}
}
// in 運算符會被轉(zhuǎn)為 contains
// a in b -> a.contains(b)
// rangeTo 是 Comparable 的擴展函數(shù)
// rangeTo 運算符的優(yōu)先級低于算數(shù)運算符
// start..end -> start.rangeTo(end)
// 日期區(qū)間
val now = LocalDate.now()
// 未來十天
val vacation = now..now.plusDays(10)
println(now.plusDays(1) in vacation)
(1..10).forEach(::println)
// for 循環(huán)中也可以使用 in 運算符,但這種情況下和上面的表示含義不同,它被用來執(zhí)行迭代
// for (x in list) {...} 實際上被轉(zhuǎn)為 list.iterator(),然后就像 Java 中一樣,
// 反復調(diào)用 hasNext 和 next
for (x in 1..10) {
print("$x ")
}
// 這種寫法叫字符串模板
// $ 后面跟著變量就可以直接打印出來變量的值
// 如果想要打印 $ 直接寫出來就行了
println("Hello $name !")
// In Java
System.out.println(“Hello ” + name + " !")
// 但如果 $ 后面還有其他字符,那么 $ 就需要使用轉(zhuǎn)義字符 \$
println("\$name")
// ${ 里面可以放各種表達式 }
println("Hello ${if (args.isNotEmpty()) args[0] else "Kotlin"} !")
// until 用來構(gòu)建一個開區(qū)間,然后用 in 判斷是否在區(qū)間內(nèi)
// 10..20 表示閉區(qū)間為 10 - 20, 10 until 20 位開區(qū)間表示 10 - 19
8.其他
// 1.內(nèi)部類和嵌套類
// 內(nèi)部類和嵌套類:默認是嵌套類
// Kotlin 中的嵌套類一般情況下不能訪問外部類的實例
class Button : View {
override fun getCurrentState(): State = ButtonState()
// 這個類與 Java 中的靜態(tài)嵌套類類似,而不是內(nèi)部類
// 所以 ButtonState 不會持有外部引用
class ButtonState : State {
override fun toString(): String {
return "ButtonState"
}
}
// inner class 與 Java 的內(nèi)部類類似
// 所以 OtherButtonState 會持有外部類的應用
inner class OtherButtonState : State {
// 拿到外部類的引用
fun getOutReference(): Button = this@Button
}
}
// 2.修飾符
// Kotlin 中的默認可見性是 public 的
// Java 中的默認可見性是包私有
// Kotlin 中有一個不同的修飾符 internal,表示模塊內(nèi)可見
// 修飾符 public 對于類成員與頂層聲明都表示所有地方可見
// 修飾符 internal 對于類成員與頂層聲明都表示模塊中可見
// 修飾符 protected 對于類成員表示子類可見
// (與 Java 中不同,Java 還允許同包內(nèi)的文件訪問 protected)
// 修飾符 private 對于類成員表示類中可見,對于頂層聲明表示文件中可見
// 3.局部函數(shù)
class User(val id: Int, val name: String, val address: String)
// 一般寫法
fun saveUser(user:User) {
if (user.name.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}: empty name")
}
if (user.address.isEmpty()) {
throw IllegalArgumentException("Can't save user ${user.id}: empty address")
}
// 然后存儲邏輯省略
}
// 通過提取局部函數(shù)和擴展來避免重復
fun saveUserWithOptimize(user:User) {
user.validateBeforeSave()
// 然后存儲邏輯省略
}
// 通過提取局部函數(shù)來避免重復
fun User.validateBeforeSave() {
// 聲明一個局部函數(shù)來進行字段校驗
fun validate(value:String, fieldName:String) {
if (value.isEmpty()) {
throw IllegalArgumentException("Can't save user $id: empty $fieldName")
}
}
validate(name, "name")
validate(address, "address")
}
// 4.類委托(幫助避免在代碼中出現(xiàn)許多相似的委托方法)
// 實現(xiàn)接口后可以通過 by 關鍵字將接口的實現(xiàn)委托到另一個對象
// 這樣看上去我們需要重寫的方法就都消失了,其實是會去執(zhí)行被委托的對象
class DelegateCollection<T>(private val innerList:ArrayList<T>) : Collection<T> by innerList {
// 雖然委托給了別人執(zhí)行,但是仍然可以重寫,最終會執(zhí)行你重寫的方法
override fun contains(element: T): Boolean {
return false
}
}
// 5.中綴表達式
// 中綴表達式(調(diào)用只有一個參數(shù)的函數(shù)時,使得代碼更簡練)
// 簡單的自定義函數(shù)
// 當然中綴
infix fun Any.toAny(other:Any) = Pair(this, other)
// 調(diào)用
println(2 toAny 3)
就到這里了...