kotlin入门精简版

/ kotlin / 没有评论 / 374浏览

构造函数

一个类可以有一个主构造函数和一个或多个次构造函数

class Person @inject constructor(firstName: String) { /*……*/ }

当没有访问修饰符或者注解时可以省略constructor关键字

class Person (firstName: String) { /*……*/ }

在成员变量和init代码块中可以直接使用主构造方法中的参数

class Person (firstName: String) {
	val firstNameOfUpperCase = firstName.toUpperCase()
    init {
        Log.d("yaocai", firstName)
    }    
}

成员变量和init代码块按定义顺序依次执行。

次级构造函数需要直接间接委托给柱构造函数

class Person(firstName: String) {
    val firstNameOfUpperCase = firstName.toUpperCase()
    val children = mutableListOf<Person>()

    init {
        Log.d("yaocai", firstName)
    }

    // 次级构造函数,直接委托主构造函数
    constructor(firstName: String, son: Person) : this(firstName) {
        children.add(son)
    }
    // 次级构造函数,间接委托主构造函数
    constructor(son: Person) : this("", son)
}

继承

kotlin中类默认是final的,若要允许类被继承,需要手动声明为open(同理也适用于函数)

open class Base(val p: Int, val q: String) {
    constructor(q: String) : this(1, q)
}

interface Function<T, R> {
    fun invoke(t: T): R
}

// 通过父类的次级构造方法间接调用父类的主构造方法
class Derived() : Base("foo"), Function<Int, String> {
    override fun invoke(t: Int): String {
        return t.toString()
    }
}
// 直接调用父类的主构造方法
class Derived() : Base(1, "foo"), Function<Int, String> {
    override fun invoke(t: Int): String {
        return t.toString()
    }
}

子类是用:表示继承的类或实现的接口,子类必须要直接或间接调用父类的主构造方法

class BarAdapter(data: MutableList<Person>) :
    BaseQuickAdapter<Person, BaseViewHolder>(R.layout.item_bar, data) {
    override fun convert(holder: BaseViewHolder, item: Person) {
    }
}

属性和字段

var 可变

val 不可变,只读,类似java final

声明一个字段的完整语法

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]
val <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]

class Foo(var name: String) {
    val encryptName: String = name
        get() {
            return Encrypt.md5(field)
        }
}

一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:控件一般在Activity或者FragmentonCreate方法中实例化、利用BundleActivityFragment传值。 这种情况下,不能在构造函数内提供一个非空初始器。但是任然想在使用该字段时候避免空检测。

这种场景下我们可以使用延迟初始化lateinit var

class MainActivity : AppCompatActivity() {
    private lateinit var personLateInit: Person
    private var person: Person? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        intent.extras?.let {
            personLateInit = it.getSerializable("person") as Person
            person = it.getSerializable("person") as Person
        }

    }
    private fun foo() {
        if (this::personLateInit.isInitialized) {
            personLateInit.children
        }
        person?.children
    }
}

使用lateinit var personLateInit就可以避免空检测。

同样可以用this::personLateInit.isInitialized来判断是否该字段已经实例化

数据类

使用data关键字标识一个类,该类会变成数据类

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

编译器自动从主构造函数中声明的所有属性生成以下方法:

    val user1 = User("yaocai", 27)
    val user2 = User("yaocai", 27)
    println(user1)
    println(user1 == user2)
    println(user1.component1())
    println(user1.component2())
	// 解构声明, 根据声明字段个数,调用相应`componentN
    val (name, age) = user1
    println("name ${name},age ${age}")

===================================
User(name=sadhu, age=27)
true
sadhu
27
name sadhu,age 27
===================================

密封类

使用sealed关键字标识一个类,该类会变成数据类,常用语限定类的继承类型,使用when就不需要else分支了,因为所有情况都是已知。

sealed class ItemType
class TextType : ItemType()
class RichTextType : ItemType()
class MediaType : ItemType()

fun foo(itemType: ItemType) {
    when (itemType) {
        is TextType -> {
        }
        is RichTextType -> {
        }
        is MediaType -> {
        }
        // else ->{} 不需要else分支
    }
}

函数

定义

函数使用 fun 关键字声明,括号后面声明返回值类型:

fun foo(x: Int): Int {
    return 2 * x
}

如果返回值可以推导出来,可以省略返回值类型

override fun getLayoutResourceId() = R.layout.act_foo
参数

函数的参数是val隐性修饰的,遵循Effective java中形参的最佳实践,避免方法内部对实参引用地址的修改

fun foo(user: User){
    user = User() // error 编译不通过
}

默认参数

函数参数可以有默认值,当省略相应的参数时使用默认值。与其他语言相比,这可以减少重载数量:

class NetWork {
    fun fetchData(page: Int, pageSize: Int = 20) {
    }
}

val network = NetWork()
network.fetchData(0)
network.fetchData(0, 10)

如果我们需要生成所有重载方法,可以使用@JvmOverloads注解,譬如在自定义控件时:

class CustomView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)

具名参数

当参数太多时候,为了阅读代码更加便捷,可以使用具名参数,是我们的代码更加有可读性:

    fun check(content: String, 
              isWholeString: Boolean, 
              isUpperCase: Boolean, 
              needCheckLength: Boolean) {

    }

不使用具名参数时,该调用看起来就像:

check(content, true , false ,true)

阅读起来看很困难,使用具名参数后调用看起来就像:

check(content, isUpperCase = true, needCheckLength = false, isWholeString = false)

还允许我们更改参数位置。

高阶函数

高阶函数是将函数用作参数或返回值的函数。

fun filter(predicate: (String) -> Boolean) {}

filter是高阶函数,函数类型作为参数

fun getFilterFunction(): (String) -> Boolean {
    return { input: String ->
            input.length > 10
        }
    }

getFilterFunction也是高阶函数,函数类型作为返回值。

函数类型

括号中定义形参的类型,箭头后面定义返回值类型:

(String, String) -> Boolean

可以像其他类型子字段一样使用函数类型字段,对函数类型字段赋值和调用:

class Bar {
    var function: ((String, String) -> Boolean?)? = null


    fun isEqual(str1: String, str2: String): Boolean {
        return str1 == str2
    }
}
val bar = Bar()
//使用::形式实例化
bar.function = bar::isEqual
//使用invoke方法调用
bar.function?.invoke("str1", "str2")

如果函数类型字段可以空,定义时应该像上述示例代码一样,将括号后面使用?,返回值后的?表示返回值可空。

有了函数类型,我们就可以替换项目中使用interface定义的callback了:

class BaseAdapter(private val listener: (View?, Int) -> Unit) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>(),
    View.OnClickListener {

    override fun onClick(v: View?) {
        // 直接调用
        listener(v, position)
    }
}   
// 使用lambda表达式实例化
val adapter = BaseAdapter { _, position ->
            Log.d("sadhu", "click position${position}")
    }

两处实力代码函数类型实例化分别使用::functionNamelambda表达式

两处实力代码函数类型调用分别使用invoke()直接调用

lambda表达式

fun filter(predicate: (String) -> Boolean) {

}
filter({ input:String -> input.length > 12 })

函数filter是高阶函数,它接受一个函数作为参数。该表达式本身是一个函数,相当于有具体名字的函数:

fun filtLength(input: String):Boolean{
	return input.length > 12
}

lambda表达式始终定义在花括号里面,先定义参数类型,函数体在箭头后面。lambda表达式一般可以根据规则精简得很短。

如果作为方法的最后一个参数,可以放到方法括号外面,如果方法只这一个参数,则可以省略括号:

filter({ input:String -> input.length > 12 })
// 放到括号外面
filter(){ input:String -> input.length > 12 }
// 只有这一个参数,省略括号
filter{ input:String -> input.length > 12 }

接着参数类型也可以省略,如果只有一个参数,这参数也可以省略用,使用时用it代替:

// 省略参数类型
filter{ input -> input.length > 12 }
// 只有一个参数,参数可以可以省略,使用时用it代替
filter{ it.length > 12 }

Android中,监听点击事件使用Lambda表达式后就类似如下样子了:

view.setOnclickListener{
    /**/
}

if、when、for、while

基本用法和java类似,这里说下不同之处。

if可以作为表达式,他可以返回一个值,默认分支中的最后一行就代表该分支的返回值,因此可以是用if``esle代替java中的三元运算

val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

when类似于java中的switch,功能更加强大,每个判断分支可以是表达式:

when(x){
    0,1->{}
    in 2..5->{} 
    x>10->{}
}

for 循环可以对任何提供迭代器(iterator)的对象进行遍历,即任何实现了Iterable接口的对象

for (item in collection) print(item)

对于java中索引循环,可以使用IntRange代替

for (int i = 0; i < 100; i++) {

}
for(i in 0..99){
    
}
for(i in 0 until 100){
    
}

..右边是闭区间,until右边是开区间,还可以使用downTo降序循环。

return、break、contine

return

根据定义,如下代码只会打印1、2,然后返回到foo()

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return // 非局部直接返回到 foo() 的调用者
        print(it)
    }
    println("this point is unreachable")
}

若我们想返回到forEach,只是过滤掉3,且打印this point is unreachable,则可以使用标签,使用标签名@定义标签,使用return@标签名返回到指定标签。

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{ 
        if (it == 3) return@lit
        print(it)
    }
    println("this point is unreachable")
}

还可以使用隐式标签,默认标签与lambda表达式同名:

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach { 
        if (it == 3) return@forEach 
        print(it)
    }
    println("this point is unreachable")
}

当然,还是使用匿名函数:

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
        if (value == 3) return  // 局部返回到匿名函数的调用者,即 forEach 循环
        print(value)
    })
    print(" done with anonymous function")
}

这种场景类似于循环中的contine,对于break的情况,若要使用return,可以在外出嵌套run方法:

fun foo() {
    run {
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@run
            println(it)
        }
    }
    print(" done with nested loop")
}

break contine

循环时,breakcontine同样也可以使用标签

fun foo() {
    out@ for (i in 0..10) {
        for (j in 0..10) {
            if (i == 2 && j == 2) {
                break@out
            }
            println("i:${i},j:${j}")
        }
    }
}

fun foo() {
    out@ for (i in 0..10) {
        for (j in 0..10) {
            if (i == 2 && j == 2) {
                contine@out
            }
            println("i:${i},j:${j}")
        }
    }
}

作用域函数(scope function)

作用域函数一共有五种:letrunwithapply 以及 also,是在一个对象的上下文中执行代码块,形如:

val user = User()
with(user) {

}
user.apply {

}
user.run {

}
user.let {

}
user.also {

}

他们间的区别在于

引用上下文对象的方式

runwithapply通过this引用上下文对象,因此可以直接使用该对象的成员变量和函数,例如:

user.apply{
    name = "sadhu"
    age  = 27
}

letalso通过it引用上下文对象,例如:

user?.let{
    print(it)
}

返回值

alsoapply返回对象本身

// 返回对象本身 user
val user = user.also{
    
}

letwithrun返回代码块的返回值

// 返回代码块的返回值,String类型
val string = user.let{
  
   it.name.toUpperCase()
}

主要区别表

函数对象引用返回值
letitLambda 表达式结果
runthisLambda 表达式结果
withthisLambda 表达式结果
applythis上下文对象
alsoit上下文对象

场景

大部分情况5中作用域函数是可以互相替换,对于一些特定场景,可以优选用特定作用域函数

let通常用于非空值执行代码块

val str: String? = "Hello" 
//processNonNullString(str)       // 编译错误:str 可能为空
val length = str?.let { 
    println("let() called on $it")
    processNonNullString(it)     
    it.length
}

with同样用于对接受对象,进行一下操作

override fun convert(holder: BaseViewHolder, item: Person) {
    with(item)
    {
        holder.setText(R.id.name,name)
        holder.setText(R.id.age,age.toString())
    }
}

run用于代码块同时对上下文对象初始化和返回值时

val user = User()
val firstName = user.run{
    name = "sadhu.static"
    queryFirstNameByName(name)
}

apply通用用于配置上下文对象

val user = User().apply{
    name = "sadhu.static" 
    age  = 27
}

also执行一些将上下文对象作为参数的操作

val user = User()
user.also{
    queryInfoByUser(user)
}