前言
上一節(jié)簡(jiǎn)單了解了 Kotlin 的語(yǔ)法使用错忱,對(duì)一些語(yǔ)法有一些了解后,在面對(duì) Java 轉(zhuǎn) Kotlin 時(shí)或想了解 Kotlin 到底轉(zhuǎn)成了什么樣的 Java?那我們需要知道這兩個(gè)技能良蛮,不僅能偷一些懶也能了解一些簡(jiǎn)單原理掖鱼;
-
AndroidStudio 中
Code → Convert Java File to Kotlin File
或雙擊 Shift 輸入 Convert Java File to Kotlin File
一鍵 Java 轉(zhuǎn) Kotlin然走,無(wú)法從 Kotlin 再轉(zhuǎn)回 Java 哦。雖然能偷懶戏挡,但不能過(guò)分依賴芍瑞,前提你要了解 Kotlin,轉(zhuǎn)的時(shí)候有一些實(shí)現(xiàn)與性能會(huì)有出入褐墅。
-
雙擊 Shift 輸入 Show Kotlin Bytecode
Kotlin 有挺多 Java 中沒(méi)有的特性拆檬,如果你想看下 Kotlin 這樣實(shí)現(xiàn)在 Java 中到底是怎樣體現(xiàn)的,那你可以去瞧一瞧掌栅。
構(gòu)造器
上一節(jié)簡(jiǎn)單上手接觸了 Kotlin 的一些語(yǔ)法秩仆,我們用上一節(jié)的知識(shí)知道如何實(shí)現(xiàn)一個(gè)類的構(gòu)造器。
class KotlinView : View {
private var paint = Paint() // 初始化畫筆
// 次級(jí)構(gòu)造器 → 調(diào)用 this(主構(gòu)造器)
constructor(context: Context) : this(context, null)
// 主構(gòu)造器 → 調(diào)用 super
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
// 畫筆樣式設(shè)置
paint.style = Paint.Style.STROKE
}
}
Kotlin 在構(gòu)造器還有更簡(jiǎn)潔的寫法猾封,主要有主構(gòu)造器澄耍、**次級(jí)構(gòu)造 **以及 init代碼塊 與 構(gòu)造屬性。
主構(gòu)造器
主構(gòu)造器是什么呢晌缘?
我認(rèn)為在一個(gè)類里面最終消費(fèi)的構(gòu)造器就是主構(gòu)造器齐莲;比如上面的代碼里面 constructor(context: Context)
調(diào)用的是this
,最終流向的還是constructor(context: Context, attrs: AttributeSet?)
磷箕,所以它就是主構(gòu)造器选酗。
那主構(gòu)造器如何更簡(jiǎn)潔呢?
可以將主構(gòu)造器的函數(shù)直接放置類聲明的后面就行岳枷∶⑻睿看下簡(jiǎn)約后的代碼
class KotlinView constructor(context: Context, attrs: AttributeSet?) : View(context, attrs) {
// 可以訪問(wèn)到主構(gòu)造函數(shù) context 參數(shù)
val color = context.getColor(R.color.white)
private var paint = Paint()
constructor(context: Context) : this(context, null)
// paint.style = Paint.Style.STROKE
fun testParams(){
context.resources// 不報(bào)紅,但實(shí)際 context 并不是主構(gòu)造函數(shù)里面的參數(shù)
attrs// 會(huì)報(bào)紅空繁,意味著訪問(wèn)不到
}
}
倒是簡(jiǎn)潔了蠻多殿衰,可是我們主構(gòu)造器里面的邏輯咋辦?別慌盛泡,往下看闷祥!
另外:
- 如果主構(gòu)造器沒(méi)有被可見(jiàn)性修飾符或注解標(biāo)記,那么
constructor
是可以省略的傲诵; - 還有成員變量初始化時(shí)是可以直接訪問(wèn)主構(gòu)造參數(shù)凯砍,例如
color
變量。注意點(diǎn)看代碼!
次級(jí)構(gòu)造
這個(gè)就不多講了,除了主構(gòu)造器,剩下的不就是次級(jí)構(gòu)造了嗎
init 代碼塊
在上面我們把主構(gòu)造器簡(jiǎn)化了,可是里面的邏輯沒(méi)地方放了摘完,現(xiàn)在就改init
上場(chǎng)了
class KotlinView (context: Context, attrs: AttributeSet?) : View(context, attrs) {
private var paint = Paint()
constructor(context: Context) : this(context, null)
init {
paint.style = Paint.Style.STROKE
}
}
這樣就把主構(gòu)造器里面的邏輯安置好了,值得注意的一點(diǎn):在初始化的時(shí)候初始化塊會(huì)按照它們?cè)?strong>文件中出現(xiàn)的順序執(zhí)行枉侧。如果paint
變量在init
的下面聲明汞幢,那paint
在init
里面就會(huì)報(bào)紅钳榨,在編譯的時(shí)候,init
代碼會(huì)按照文件出現(xiàn)的順序插入到實(shí)際的構(gòu)造函數(shù)中纽门,聲明放在init
下面的話薛耻,那就會(huì)被插入在paint.style
的下面。
PS:注意到主構(gòu)造器的`constructor沒(méi)赏陵?沒(méi)見(jiàn)了是吧饼齿,因?yàn)樗葲](méi)有被可見(jiàn)性修飾也沒(méi)有被注解,所以是可以省略的蝙搔。
構(gòu)造屬性
構(gòu)造屬性是什么呢缕溉,我給他翻譯過(guò)來(lái)就是:構(gòu)造函數(shù)的參數(shù)屬性化。
常規(guī)保守寫法是這樣的
class KotlinInfo {
var name: String? = null
var age: Int? = null
constructor(name: String?, age: Int?) {
this.name = name
this.age = age
}
}
可是代碼太多吃型,不是 Kotlin 的風(fēng)格证鸥,那我們這樣來(lái)
// 簡(jiǎn)化方式一:直接使用主構(gòu)造函數(shù)參數(shù)初始化。
class KotlinInfo constructor(name: String?, age: Int?) {
var name: String? = name
var age: Int? = age
}
// 簡(jiǎn)化方式二:emmm~還是不夠簡(jiǎn)潔勤晚,再來(lái)
class KotlinInfo constructor(var name: String?, var age: Int?) {
}
是吧枉层,這樣才對(duì),主構(gòu)造函數(shù)的入?yún)⒓词菂?shù)又變量赐写,所以這就是構(gòu)造屬性鸟蜡。
data class
在 Java 中重寫hashCode()
、toString()
等類時(shí)挺邀,需要我們一個(gè)個(gè)去重寫出來(lái)揉忘,在 Kotlin 中我們只需要在class
前聲明為data
「數(shù)據(jù)類」就會(huì)自動(dòng)為我們生成hashCode()
、toString()
端铛、equals()
泣矛、copy()
、componentN()
方法沦补。
// 聲明類為 data 類型
data class KotlinInfo constructor(var name: String?, var age: Int?) {}
fun main() {
val kotlinInfo = KotlinInfo("Kotlin", 25)
kotlinInfo.hashCode()
kotlinInfo.toString()
kotlinInfo.equals(null)
kotlinInfo.copy()// 淺拷貝
kotlinInfo.component1()// 對(duì)應(yīng)第一個(gè)變量 name
kotlinInfo.component2()// 對(duì)應(yīng)第二個(gè)變量 age
}
但是生成數(shù)據(jù)類還是有前提的
- 主構(gòu)造函數(shù)需要至少有一個(gè)參數(shù)
- 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為
val
或var
乳蓄;相當(dāng)于必須是構(gòu)造屬性 - 數(shù)據(jù)類不能是抽象、開(kāi)放夕膀、密封或者內(nèi)部的
component
屬性的順序?qū)?yīng)主構(gòu)造參數(shù)的聲明順序
相等性
-
==
結(jié)構(gòu)相等虚倒,調(diào)用了equals()
-
===
引用相等
注意:與 Java 不同
解構(gòu)
Kotlin 的一大特點(diǎn)解構(gòu)聲明,表示一個(gè)對(duì)象一次賦值給多個(gè)變量产舞。
data class KotlinInfo constructor(var name: String?, var age: Int?) {}
fun main() {
val (name, age) = KotlinInfo("Kotlin", 25)
}
這個(gè)就有一個(gè)前提魂奥,必須是數(shù)據(jù)類才能做解構(gòu)聲明,當(dāng)然不是數(shù)據(jù)類我們也可以自定義解構(gòu)聲明易猫,比如:
class KotlinInfo constructor(var name: String?, var age: Int?) {
operator fun component1(): String? {
return name
}
operator fun component2(): Int? {
return age
}
}
fun main() {
val (name, age) = KotlinInfo("Kotlin", 25)
}
Elvis 操作符
通過(guò)?:
操作簡(jiǎn)化if null
的操作
// kotlinInfo.name 為空返回"沒(méi)有姓名"
val name = kotlinInfo.name ?: "沒(méi)有姓名"
// kotlinInfo.age 為空提前返回
val age = kotlinInfo.age ?: return
// kotlinInfo.age 為空拋出異常
val age = kotlinInfo.age ?: thorw IllegalArgumentException("age is null")
// 對(duì)應(yīng) if 代碼是這樣的
var name = kotlinInfo.name
if (kotlinInfo.name == null){
name = "沒(méi)有姓名"
}
when 操作符
when
不僅條件分支上支持條件表達(dá)式耻煤,分支上也可以多個(gè)值,通過(guò),
分割;還可以有返回值哈蝇;默認(rèn)分支條件都是布爾表達(dá)式棺妓。
// num 大于 0 哦~
val str = when (num) {
in 0..99 -> "< 100"http:// 大于等于 0 小于100;返回字符串
100 -> "100"http:// 等于 100炮赦;返回字符串
200 -> "200"http:// 等于 200怜跑;返回字符串
300, 301 -> "300,301"http:// 等于 300 或 301;返回字符串
else -> ">300"http:// 其他情況全部返回字符串
}
operator
一般用的時(shí)候找下就行吠勘,與 Java 的大致差不多性芬,列舉下加減乘除
操作符 | 函數(shù) |
---|---|
a+b | a.plus(b) |
a-b | a.minus(b) |
a*b | a.times(b) |
a/b | a.div(b) |
Lambda
Lambda 在 Java 中也接觸過(guò),這里主要說(shuō)下哪些可以省略
val kotlinInfos: List<KotlinInfo> = ArrayList()
// 1剧防、常規(guī)寫法
for (kotlinInfo in kotlinInfos) {
if (kotlinInfo.name.equals("Kotlin")) {
return
}
}
// 2植锉、使用 Lambda 的常規(guī)寫法
kotlinInfos.forEach({ kotlinInfo: KotlinInfo ->
if (kotlinInfo.name.equals("Kotlin")) {
return
}
})
// 3、函數(shù)最后一個(gè)參數(shù)是 Lambda 峭拘,那么可以將 Lambda 表達(dá)式移至外面俊庇,如下:
kotlinInfos.forEach() { kotlinInfo: KotlinInfo ->
if (kotlinInfo.name.equals("Kotlin")) {
return
}
}
// 4.1、如果傳入?yún)?shù)只有一個(gè) Lambda鸡挠,小括號(hào)可以省略暇赤;forEach()
// 4.2、Lamdda 表達(dá)式只有一個(gè)參數(shù)宵凌,類型也可以省略鞋囊;kotlinInfo: KotlinInfo
kotlinInfos.forEach{ kotlinInfo ->
if (kotlinInfo.name.equals("Kotlin")) {
return
}
}
// 5、Lambda 表達(dá)式只有一個(gè)參數(shù)還可以通過(guò)隱式的 it 來(lái)訪問(wèn)參數(shù)瞎惫,如下:
kotlinInfos.forEach {
if (it.name.equals("Kotlin")) {
return
}
}
循環(huán)
repeat(100){
// do something
}
for (i in 0..99){
// do something
}
for (i in 0 until 100) {
// do something
}
infix
前提:
- 必須是成員函數(shù)或擴(kuò)展函數(shù)溜腐;
- 必須只能接收一個(gè)參數(shù)且不能有默認(rèn)值;
下面是until
的源碼瓜喇,函數(shù)是Int
的擴(kuò)展函數(shù)且只有一個(gè)參數(shù)無(wú)默認(rèn)值
public infix fun Int.until(to: Int): IntRange {
if (to <= Int.MIN_VALUE) return IntRange.EMPTY
return this .. (to - 1).toInt()
}
嵌套函數(shù)
Kotlin 的函數(shù)中是可以在嵌套函數(shù)的挺益,而且在內(nèi)部函數(shù)里面可以訪問(wèn)外部函數(shù)的參數(shù);但是有一點(diǎn)需要注意乘寒,每次調(diào)用外部函數(shù)時(shí)不僅會(huì)產(chǎn)生一個(gè)外部函數(shù)的對(duì)象而且內(nèi)部函數(shù)也會(huì)產(chǎn)生一個(gè)函數(shù)對(duì)象望众,所以用的時(shí)候需要謹(jǐn)慎。
fun external(string: String) {
fun internal() {
print(string)
}
print(string)
}
函數(shù)簡(jiǎn)化
var string: String? = null
fun printText() {
print(string)
}
fun getText(): String? {
return string
}
var string: String? = null
fun printText() = print(string)
fun getText(): String? = string
函數(shù)參數(shù)默認(rèn)值
fun toast(context: Context, text: CharSequence) {
toast(context, text, Toast.LENGTH_SHORT)
}
fun toast(context: Context, text: CharSequence, duration: Int) {
Toast.makeText(context, text, duration).show()
}
fun toast(context: Context, text: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(context, text, duration).show()
}
在 Java 中調(diào)用就只能使用兩個(gè)參數(shù)的方法伞辛,這時(shí)候就需要用到@JvmOverloads
@JvmOverloads
在toast
函數(shù)上標(biāo)記@JvmOverloads
注解后烂翰,Java 就能用了
擴(kuò)展
// 正常寫法
fun dp2px(dx: Float): Float { ...}
// 擴(kuò)展寫法
fun Float.dp2px(): Float { ...}
// 兩者使用區(qū)別
dp2px(10f)
10f.dp2px()
函數(shù)類型
函數(shù)類型傳入?yún)?shù)類型和返回值類型組成,傳入?yún)?shù)需要用()
蚤氏,用->
連接返回值甘耿,返回值為Unit
不可省略;
函數(shù)類型實(shí)際是一個(gè)接口竿滨,傳遞函數(shù)的時(shí)候可通過(guò)::函數(shù)名
佳恬、匿名函數(shù)
或者使用lambda
class View {
interface OnClickListener {
fun OnClick(view: View)
}
fun setOnClickListener(listener: (View) -> Unit) { }
}
fun onClick(view: View) {
print("click")
}
fun main() {
val view = View()
// 方式一:函數(shù)傳入
view.setOnClickListener(::onClick)
// 方式二:匿名函數(shù)
view.setOnClickListener(fun(view: View) { print("click") })
// 方式三:Lambda
view.setOnClickListener { print("click") }
}
內(nèi)聯(lián)函數(shù)
內(nèi)聯(lián)函數(shù)
inline
關(guān)鍵字聲明內(nèi)聯(lián)函數(shù)捏境,編譯時(shí)內(nèi)聯(lián)函數(shù)的函數(shù)體會(huì)被插入至調(diào)用處,因此實(shí)現(xiàn)內(nèi)聯(lián)函數(shù)時(shí)注意毁葱,函數(shù)體內(nèi)的代碼行數(shù)盡量減少垫言。
內(nèi)聯(lián)函數(shù)有什么作用呢?看看代碼
inline fun log(text: String) {
Log.d("TAG", text)
}
fun main() {
log("log text")
}
// 用 inline 修飾聲明內(nèi)聯(lián)函數(shù) log
// 實(shí)際編譯后的代碼就變成這樣了
fun main() {
log.d("TAG", "log text")
}
// 將內(nèi)聯(lián)函數(shù) log 里面的代碼直接放到了調(diào)用的地方
// 到底有什么用呢倾剿?
// 從調(diào)用棧的角度來(lái)看看
// 不使用內(nèi)聯(lián)函數(shù)大致是這樣的
// main 入棧 → log 入棧 → log.d() → log 出棧 → main 出棧
// 使用內(nèi)聯(lián)函數(shù)是這樣的
// main 入棧 → log.d() → main 出棧
所以內(nèi)聯(lián)函數(shù)能減少一層調(diào)用棧骏掀,但是如果函數(shù)體里面內(nèi)容較多時(shí),編譯器會(huì)變得比較辛苦柱告,因此我們?cè)趯?shí)現(xiàn)內(nèi)聯(lián)函數(shù)時(shí)一定要注意。
思考:示例代碼里面的內(nèi)聯(lián)函數(shù)log
的inline
有警告笑陈!
部分禁用內(nèi)聯(lián)
noinline
可以禁止部分參數(shù)參與編譯
具體化的類型參數(shù)
通過(guò)配合inline + reified
達(dá)到真泛型的效果
interface Api{ ... }
val retrofit = Retrofit.Builder()
.baseUrl("https://api.com")
.build()
inline fun <reified T> create(): T {
return retrofit.create(T::class.java)
}
val api = create<Api>()
委托
屬性委托
常見(jiàn)的屬性操作际度,通過(guò)委托的方式,讓其只實(shí)現(xiàn)一次
-
lazy
:延遲屬性涵妥,值僅在首次訪問(wèn)時(shí)計(jì)算 -
observable
:可觀察屬性:屬性發(fā)生改變時(shí)通知
類委托
通過(guò)類委托的模式減少繼承
標(biāo)準(zhǔn)函數(shù)
-
apply
- 返回自身:作用域中使用
this
做為參數(shù) - 適合對(duì)一個(gè)對(duì)象做附加操作
- 返回自身:作用域中使用
-
also
- 返回自身:作用域中使用
it
做為參數(shù)
- 返回自身:作用域中使用
-
run
- 無(wú)需返回自身:作用域中使用
this
做為參數(shù)
- 無(wú)需返回自身:作用域中使用
-
let
- 無(wú)需返回自身:作用域中使用
it
做為參數(shù) - 適合配合空判斷時(shí)
- 無(wú)需返回自身:作用域中使用
-
with
- 使用對(duì)同一個(gè)對(duì)象進(jìn)行多次操作時(shí)