入職公司第三周時,項目組讓我用kotlin去完成一個內部使用的app。下面記錄一下kotlin相關筆記。因為app比較簡單,可能很多kotlin特性沒有用出來。學習kotlin過程中查找的資料放在了最后。
簡書不支持生成目錄,支持目錄結構的文檔地址
開發環境
AS version: android stdio 3.0 bate 5
android stdio 3.0以上是支持直接創建kotlin項目的,如果是android stdio 3.0以下,需要額外的添加一些依賴。
kotlin幾個直觀的特性
如果看不懂下面的內容沒關系,后面會詳細的講,我也是菜鳥,看不懂就比較慌!
- java中必須使用
;
結尾。 而kotlin中一行代碼結束是不需要使用;
結尾的。
- 一個文件下,不一定需要一個類名,只要新建一個kotlin類型的文件(文件擴展名 為
.kt
的文件),就可以直接寫代碼。
eg: 定義一個名為BaseExtension.kt
的文件,功能是對類擴展了一些方法。
package manage.ad.yiba.com.admanage.common.extension
import android.content.Context
import android.support.v7.app.AlertDialog
import android.util.Log
import android.widget.Toast
import manage.ad.yiba.com.admanage.common.BaseActivity
/**
* Created by yh on 8/24/17.
* 該kt文件用來對類的擴展
*/
//Context 基礎類的擴展
fun Context.toast(content: CharSequence, time: Int = Toast.LENGTH_LONG){
Toast.makeText(this,content,time).show()
}
//擴展一個log方法
fun Any.log(tag: Any = "Tag",content: String?){
Log.i(tag.toString(),content)
}
fun Any?.toString(): String {
if (this == null) return "null"
// 在空檢查之后,`this` 被自動轉為非空類型,因此 toString() 可以被解
// 析到任何類的成員函數中
// ChinaPerson("",2,"","","")
// ChinaPerson("",2,"","","","")
return toString()
}
fun BaseActivity.showDialogMessage(title: String = "", body: String, rightCode: ()->Unit = {System.exit(0)},leftCode: ()->Unit = {}) {
var exitDialog: AlertDialog? = null
val builder = AlertDialog.Builder(this)
builder.setTitle(title).setMessage(body).setPositiveButton("OK") { dialog, which ->
rightCode.invoke()
}.setNeutralButton("Cancel"){dialog, which ->
leftCode.invoke()
try {
exitDialog?.dismiss()
}catch (e: Exception){
}
}
exitDialog = builder.create()
exitDialog.show()
}
- 一個擴展名為
.kt
文件中,可以定義多個類,這里定義的類跟javaBean是一個功能,這是在kotlin中的寫法。
eg: 名為Response.kt
的文件中,包含多個類
package manage.ad.yiba.com.admanage.moudle.adDeploy
import manage.ad.yiba.com.admanage.retrofit.BaseResponse
import java.io.Serializable
/**
* Created by yh on 9/19/17.
*/
data class AdDeployResponse(var data: MutableList<AdDeployData>): BaseResponse()
data class AdDeployData(var app: String = "", var appName: String = "", var token: String = "", var tokenStatus: Int = 0, var date: String = "", var type: Int = 0, var ids: String = "", var adPositions: String = "",var title: String = "",var viewType: Int = 1): Serializable
//詳情頁面
data class AppDetailResponse(var data: AppDetails):BaseResponse()
data class AppDetails(var app: String,var appName: String,var token: String,var tokenStatus: Int,var appDetails: MutableList<AppDetail>)
data class AppDetail(var adPlaceId: String,var app: String,var adStatus: Int,var serverId: Int,var sources: MutableList<Source>?):Serializable
data class Source(var id: String = "1",var packageName: String = "com.wifi.cool",var adPositionId: String = "1",var adCenterId: Int = 23,var level: Int =2,var centerName: String = "xxxx",var size: String = "RECT",var type: String = "DO"):Serializable
kotlin的基礎
kotlin基本數據類型:
類型 | 字寬 |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
在 Kotlin 中,所有的類型都是一個引用類型。在java中,分為基本數據類型和引用數據類型,這里需要注意的是,這些類型的首字母都是大寫的。
如何定義一個常量和變量
- 定義常量的關鍵字為
val
val a: Double = 27 //val + 變量名 +`:` + 空格+"類型" +" = " +"值"
val a = 27 //如果知道具體的類型,可以不帶類型,kotlin會自動類型推斷
注: a. 前面默認添加了public。
- 定義變量的關鍵字
var
var name: String = "yhai" //var + 變量名 +`:` + 空格+"類型" +" = " +"值"
var name = "yhai" //如果知道具體的類型,可以不帶類型,kotlin會自動類型推斷
注意:
1.上面的空格是為了好的代碼格式
- 對于變量,如果需要延遲初始化,需要在var 前面添加lateinit關鍵字。延遲初始化后面還會詳細講解。
lateinit var name: String
name = "yhai"
- 使用var修飾的變量,實際上也就擁有了get和set方法,而val修飾的沒有set方法。
基本數據類型的轉換
val value0: Int = 23
val value1: Long = value0.toLong()
val value2: Double = value0.toDouble()
val value3: Float = value0.toFloat()
kotlin中的基本數據類型轉換需要顯示的調用對應的方法
數組的定義
java
Object[] objects = new String[2];
objects[0] = "0";
objects[1] = "1";
kotlin
var array: Array<Any>
var strings: Array<Any> = arrayOf("1", "2")
array = strings
kotlin中數組使用Array<TYPE> 定義。上面的Any是kotlin中的最根上的類,可以理解為跟java中的Object一樣。
字符串輸出
val value = "hello,world"
println("str == $value")
val person = Person()
println("str == ${person.age}")
在""中,使用$value
就能把value給打印出來,如果person 是一個復雜的對象,需要使用${表達式}
。
kotlin中創建一個對象不需要使用new
關鍵字
java | kotlin |
---|---|
Persion person = new Person() | val person: Persoin = Person() |
val person = Person() 自動類型推斷
|
包
kotlin 中package 比較特殊,文件上的package 可以隨便定義,例如:
package com.xxx.xxx.xxx
class Person{
}
而這個文件真實的路徑在com.yhai.kotlin
目錄下,在導入包的時候,需要注意下,否者看到這個文件在某個目錄下,但是導入這個文件的包名并不是com.yhai.kotlin
,而是com.xxx.xxx.xxx
。
控制流
if else
//傳統用法
var max = a
if (a < b)
max = b
//帶 else var max: Int if (a > b)
max = a
else
max = b
//作為表達式
val max = if (a > b) a else b
if 分支可以作為塊,最后一個表達是是該塊的值:
val max = if (a > b){
print("Choose a")
a
} else{
print("Choose b")
b }
注:kotlin 的if判斷跟java里面的使用方式差不多,唯一不同的是可以作為一個表達式有返回值。
when
用法一
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { //Note the block
print("x is neither 1 nor 2")
}
}
or
when (item.itemId) {
R.id.nav_setting->{
}
R.id.nav_share -> {
}
R.id.nav_send -> {
}
}
用法一
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { //Note the block
print("x is neither 1 nor 2")
}
}
用法二
when (x) {
0,1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
用法三
when (x) {
parseInt(s) -> print("s encode x")
else -> print("s does not encode x")
}
用法四:作為表達式
val hasPrefix = when (x) {
is String -> x.startsWith("prefix")
else -> false
}
while
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y 在這是可見的
for
for(item in collection){
}
對于各種循環,如何正確的終止呢?
參考學習資料
類和對象
類的定義
定義一個類更java中一樣,使用class關鍵字。
第一種定義類的方式:沒有參數,沒有類體
class Person
這樣就定義了一個Person類,定義的這個Person類實際上是沒有寫錯的。kotlin中如果這個類中沒有屬性,沒有方法(后面會講怎么跟這個類添加一個方法),類名后面的(){}就可以省略掉
第二種定義類的方式:有類體,沒有參數
class Person{
}
第三種種定義類的方式:有參數,有類體
class Person(var name: String,var age: Int){
//TODO 做些事情
}
在使用java的時候,我們在構造函數中傳入參數,然后在構造函數體內做一些初始化的事情,而上面的構造函數的參數是直接跟在類名后面,在哪里做初始化的事情呢?實際上上面有個init{}代碼塊沒寫,完整的如下:
class Person(var name: String,var age: Int){
init{
//在這個位置做類似于java中構造函數體類做的事情。
}
}
這上面有個隱藏的知識點:
- 在構造函數中定義的參數使用var修飾,該參數將會在當前類全局都可以使用。不使用var修飾,那么該參數的使用范圍只會在init{}中。這個我是通過將kotlin編譯成.class,然后再翻譯成java代碼知道的。具體操作后面講。
第四中定義類的方式:有參數,沒有類體
class Person(val name: String,val age: Int)
如何定義多個構造函數:如果類有主構造函數,每個二級構造函數都要,或直接或間接通過另一個二級構造 函數代理主構造函數。在同一個類中代理另一個構造函數使用 this 關鍵字:
class Person(val name: String,val age: Int ) {
init{
//這里會是最后調用的地方
}
constructor (name: String,age: Int,var other: String) : this(name,age) {
}
}
注:在上面第一種定義類的地方,存在一個默認的構造函數,這個構造函數是不帶參數的。
class Person(){
}
定義函數: 使用fun關鍵字,
格式: fun + 函數名+(參數列表)+"空格"+":" +"空格" 返回類型+{//函數體}
fun dataIsModify(cache: List<String>,data: MutableList<String>) : Boolean{
//...
return false
}
注:1. 參數列表不能使用var 或val修飾,默認是有val修飾了。
- 空格是為了代碼格式
函數參數列表的默認值:
我們定義函數的時候,調用函數的時候,必須要傳入對應的參數,kotlin中,可以對參數列表設置默認的值,也就是說,有些參數可傳可不傳。
fun showDialogMessage(title: String = "", body: String = "this is a dialog") {
//create a dialog
}
調用上面這個函數:
showDialogMessage() //使用默認值
//或者只修改 body的值
showDialogMessage(body = "change dialog hint")//body 參數不使用默認值
繼承
<span id="jump">跳轉到的地方</span>
在kotlin中,類默認情況下是不能被繼承的,需要在定義類的前面加上open關鍵字,該類才能被繼承。同理,如果函數可以被繼承,也需要使用open修飾,默認不能被繼承。
繼承的語法:使用:
+ 被繼承的類名+(),()如果有參數的話,需要傳遞進去。
open class Base(var x: Int,var b: Int){
open fun add() : Int{
return x + b
}
}
class Person(x: Int, b: Int) : Base(x,b){
}
注:在這里,Person類的構造中帶有兩個參數,沒有使用var或者val修飾,原因是父類已經定義了x,b兩個屬性。在Person中定義就沖突了。如果一定要使用var或val修飾,需要改變參數名;
open class Base(var x: String,var b: Int)
class Person(var xx: String,var bb: Int) : Base(xx,bb){
}
//這里我遇到一個類似的場景,我在后面講。
注: 如果要重寫Base中的方法,那么該方法需要用override修飾,表示是重寫的父類的方法。java中使用的是@override注解表示被重寫的方法。
class Person(x: Int, b: Int) : Base(x,b){
override fun add(): Int {
return super.add()
}
}
抽象類的定義:
使用abstract 關鍵字修飾,跟java中一樣。同理,里面的抽象方法也需要abstract修飾。和java一樣,也可以定義具體的方法。
abstract class Derived {
abstract fun f()
fun f2(){
}
}
sealed修飾的密封類
類的定義中,有一種密封類的概念
密封類相關資料
類的屬性
之前有講到定義一個變量和一個常量的方法,他們的完整的語法如下:
var <propertyName>: <PropertyType> [ = <property_initializer> ]
<getter>
<setter>
如果屬性類型可以從初始化語 句或者類的成員函數中推斷出來,那么他的類型也是忽略的。
語法中的初始化語句,getter 和 setter 都是可選的,他們是什么意思呢?如下:
var name : String
get() = name// 可以對獲取這個做一些修改。類似于java中的get方法體重做了一些事情。
set(value) {
//設置值做一些事情
}
備用字段 或者叫做幕后字段 :field
這個概念的資料
我還沒有怎么使用,所以放個鏈接,自己去看。
空安全
是時候講下空安全是什么東西了。在kotln中,有一個很屌的功能,就是避免java中報null錯誤的處理方式。
var name: String = null //語法不通過。必須對該屬性賦值,而且這個值不能是null
//這樣定義是語法檢查不過的,因為你對name賦值為null,那么如果你一定要讓這個name可以為null怎么做呢
var name: String? = null
//對,在類型String后面跟上一個?,大概意思是說,這個那么可以為空。
那么這個name使用的過程中,就有可能是個null,意味著要報null異常。為了防止報null,如下使用:
name?.toString()
在name上使用?. 調用它的方法。這樣就能保證使用name避免出現null異常。
如果你定義了一個變量,這個變量為null,你還是堅決要調用他,怎么辦呢?使用!!
var name = null
name!!.toString() // 這樣就必然會報null異常
這里只要記住,?的意思是說可能為空,在調用可能為空的時候,使用?.避免異常和使用!!為空也要調用。
kotlin中接口的定義
kotlin中的接口中可以定義方法體,跟抽象類一樣,這樣接口和抽象類就一樣了,我沒有搞懂什么意思!接口的定義還是使用interface關鍵字
interface MyInterface {
val property: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(property)
} }
接口的實現:
class Child : MyInterface {
override val property: Int = 29
}
接口實現和基礎類的區別在于是有后面跟()
繼承一個類和實現多個接口:
class Base(){
}
interface MyInterface0 {
override val property: Int = 29
}
interface MyInterface1 {
fun test(){
}
}
class Child : Base(), MyInterface0, MyInterface1 {
}
解決重寫沖突
多個接口中有相同的方法
在文檔中直接copy的代碼如下:
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
}
可見性修飾詞
在 Kotlin 中有四種修飾:private,protected,internal,以及public,默認的為public。
private 只在該類(以及它的成員)中可見
protected 和 private 一樣但在子類中也可見
internal 在本模塊的所有可以訪問到聲明區域的均可以訪問該類的所有成員 ( internal — any client inside this module who sees the declaring class sees its internal members;)
public 任何地方可見 (public — any client who sees the declaring class sees its public members.)
伴隨對象
這個相當于java中的static 修飾的方法和屬性.因為kotlin沒有static關鍵字,實現靜態方法的方式如下:在***companion object{} ***中de {}定義方法和屬性就可以。
class Person7{
companion object{
var name = ""
fun test(){
}
}
}
Person7.test()//使用方式
擴展方法和屬性和伴隨對象的擴展
關于屬性的擴展沒怎么搞懂,下面不講,項目中我暫時也沒有用到。
在類定義的第一種定義方法中,定義了如下一個類
class Person
顯然,定義了這么一個類是沒有什么實際用途的。我們使用kotlin的擴展extension特性對其擴展一些方法。也就是說,我可以給所有的類添加方法。實現方式如下:
擴在函數格式:fun + "要對擴展的類的名字" + "."+方法名()+ "{} //方法體"
fun Person.eat(name: String){
System.out.println("吃的是什么 = $name")
}
這樣Person這個類就已經有了一個叫做eat(name: String)方法了。
伴隨對象的擴展
格式:fun +類名+"."+"Companion"+"."+"方法名"+"(參數列表
)"+"{}"
fun Person7.Companion.test2(){
}
數據對象
java中有個javaBean的概念,在kotlin中有自己的定義方法使用data修飾的的class類
data class Source(var id: String = "1",var packageName: String = "com.wifi.cool",var adPositionId: String = "1",var adCenterId: Int = 23,var level: Int =2,var centerName: String = "xxxx",var size: String = "RECT",var type: String = "DO"): Serializable
上面有如下幾個特點:
- 參數需要有var或val修飾,可以轉換成java代碼對比。
- 參數有默認值
- 實現了Serializable接口
內部類
類可以標記為 inner 這樣就可以訪問外部類的成員。內部類擁有外部類的一個對象 引用:
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo(): Int{ return bar}
}
}
匿名內部類
使用object關鍵字
window.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
} })
代理屬性
代理屬性的意思我理解為你的屬性交給我來管理。什么意思呢?
class User(){
var name: String = ""
var age: Int = 23
}
val user = User()
user.name = "yhai"
user.age = 27
上面這個User對象的name和age屬性的值發生改變時,我要把這個值通過SharePerference存著本地。那我就可以把這個屬性代理起來,保存在本地的這個事情就在這個代理類中實現。這個類需要繼承ReadWriteProperty<Any?, T>類。下面這個代理類是用來操作sp的。
import android.content.Context
import manage.ad.yiba.com.admanage.App
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* Created by yh on 8/25/17.
*/
class Preference<T>(context: Context = App.instance, val name: String?, val default: T) : ReadWriteProperty<Any?, T> {
val prefs by lazy {
context.getSharedPreferences("yiba",Context.MODE_PRIVATE)
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T = with(prefs){
when (default) {
is Long -> {
getLong(name, default)
}
is String -> {
getString(name, default)
}
is Float -> {
getFloat(name, default)
}
is Int -> {
getInt(name, default)
}
is Boolean -> {
getBoolean(name, default)
}
else -> {
throw IllegalArgumentException("This type can be saved into Preferences")
}
} as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = with(prefs.edit()){
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Float -> putFloat(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
else -> {
throw IllegalArgumentException("存儲的類型不存在,請確認儲存類型")
}
}.apply()
}
}
實際上就是代理這個屬性的get和set方法。
上面有兩個東西沒有提到:
- is 相當于java中的instanceof
- as 相當于java的強制,例如:
java
TextView tv = (TextView)findViewById(id);
kotlin
TextView tv =findViewById(id) as TextView
- with(obj) 相當于在obj的內部。
val user = User()
with(user){
//在這里可以直接操作User的屬性和方法。而不需要user.name或者user.eat("xxxx"),省略user.
}
函數
之前對已經講了怎么定義一個函數,但是在kotlin中函數作為一等公民,顯示還有很多高級的用法。
1.單表達式函數
當函數只返回單個表達式時,大括號可以省略并在 = 后面定義函數體
fun test(){
return 23
}
可以寫成下面
fun test() =23
中綴符號
在滿足以下條件時,函數也可以通過中綴符號進行調用:
它們是成員函數或者是擴展函數 只有一個參數 使用 infix 關鍵詞進行標 記
//給 Int 定義一個擴展方法
infix fun Int.shl(x: Int): Int { ...
}
1 shl 2 //用中綴注解調用擴展函數
1.shl(2)
這個在我第一次看的時候,感覺屌爆了。
默認參數
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size() ) {
...
}
注:函數如果沒有返回值的時候是一個Unit類型,默認可以不寫,例如上面的函數也可以寫成
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size() ) : Unit{
...
}
變長參數
fun asList<T>(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts)
result.add(t)
return result
}
使用如下:
val list = asList(1, 2, 3)
java中使用的是...表示可變參數,kotlin中使用的是vararg。
函數內部定義函數
什么優缺點現在我也沒有搞懂。
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v) }
dfs(graph.vertices[0])
}
函數可以賦值給另一個函數
//定義了一類
class Person6
//給該類擴展了一個方法
fun Person6.eat(name: String){
}
//創建Person6的對象
val v = Person6()
//將v.eat(xxx: String)賦值給另一個函數
fun test(xx: String) = v.eat(xx)
閉包
private val value = {xx: String,bb: String->Unit
}
上面的代碼,定義了一個閉包,并且把這個閉包賦值給了一個value的變量。
怎么使用它了,有兩種方式:
//第一種方法使用invoke,后面括號是參數
value.invoke("value0","value1")
//第二種方法類似于函數的調用
value("value0","value1")
閉包實際上就是一個使用{}包起來的代碼塊,在上面這個閉包中,->左邊是閉包的參數列表,右邊是返回類型。在閉包的最后一行作為默認的返回值。
{xx: String,bb: String->Unit
{
//閉包體
}
}
閉包可以作為一個函數的參數如下:
test方法有一個閉包作為參數,這個閉包的參數列表為空,返回值為Unit
fun test(code: ()->Unit){
code.invoke()
}
//調用寫成下面這樣
val clourse = {
}
test(clourse)
//在可以寫成
test({})
//如果一個函數的參數只有一個閉包參數可以省略()
test{xx: String ->{
}
}
//這個閉包體可以省略,這個以閉包作為參數的函數可以寫成下面這樣。
test{
}
自定義dsl
這個知識點不理解不影響android的開發
在我們的gradle中,有類型與下面的代碼,他是怎么實現的呢?
email{
address = "1454025171@qq.com"
baseInfo()
from("this is from")
to("this is to fun")
body{
p("this is body fun")
}
}
dsl代碼實現
下面這段代碼是網上的一個哥們實現的,我忘了保存其地址!!
class EmailSpec{
var address: String = ""
fun baseInfo() = println("address = $address")
fun from(from: String) = println("From: $from")
fun to(to: String) = println("To: $to")
fun subject(subject: String) = println("Subject: $subject")
fun body(init: BodySpec.() -> Unit): BodySpec { //這個方法的參數為一個函數,該參數函數返回Unit。該方法返回BodySpec
val body = BodySpec()
body.init()
return body
}
}
class BodySpec {
fun p(p: String) = println("P: $p")
}
/**
* 1. 定義一個方法函數作為參數的方法
* 2. 對EmailSpec 擴展了一個方法,該方法沒有參數,沒有返回值;目的是可以調用內部的其他方法
* 3. 該方法返回一個EmailSpec 的對象
*
*/
fun email(init: EmailSpec.()->Unit) {
val email = EmailSpec() //創建一個對象
email.init()
//調用的是參數中定義的init函數,不是系統的init函數
}
上面這個dsl理解了email這個方法,就理解了怎么定義dsl。
首先定義一個反復,這個方法有一個參數,這個參數是對EmailSpec類擴展了一個空函數,返回值也為空。在這個email方法的體中,創建了EmailSpec的實例,然后這個實例調用了這個擴展方法。EmailSpec.()->Unit 這個擴展方法是在方法中定義的,所以他的范圍也只在這個email方法內部區域。
關于操作符的使用
關于操作符,有點想Rxjava里面的操作符的用法。
關于操作符的使用
那些東西沒有說:
對象表達式和聲明,代理類,異常,this表達式,運算符重載 ...
kotlin與java的對比
copy的內容
java 有的而 kotlin 沒有
異常檢查
原始類型不是類
靜態成員
非私有成員
通配符類型
kotlin 有的而 java 沒有
字面函數+內聯函數=高性能自定義控制結構 擴展函數 空安全 智能轉換 String 模板 性能 一級構造函數 First-class delegation 變量和屬性類型的類型接口 單 例模式 變量推斷和類型預測 范圍表達式 運算符重載 伴隨對象
本來準備一次性寫完,太累了,分成兩篇來寫!!!下一篇將如何寫一個android項目,如果你有使用java開發android經驗,那將會是非常的容易。
下一篇將如下類容
kotlin 開發android項目的相關經驗
anko相關的知識點 布局和其他
網絡請求
基礎類的擴展
定義組合式控件
點擊事件,
在學習go語言的時候,有多返回值,在kotlin中,可能用解構聲明比較
解構聲明
這篇文章讓我讀出了閉包的味道,Kotlin語法基礎,函數與閉包
//閉包也是函數,是一個可以讀取其他函數內部變量的函數。