kotlin入门精简版

构造函数

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

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

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

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

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

1
2
3
4
5
6
class Person (firstName: String) {
val firstNameOfUpperCase = firstName.toUpperCase()
init {
Log.d("yaocai", firstName)
}
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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(同理也适用于函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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()
}
}

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

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

属性和字段

var 可变

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

声明一个字段的完整语法

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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关键字标识一个类,该类会变成数据类

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

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

  • equals()/hashCode()
  • toString() 格式是 "User(name=yaocai, age=27)"
  • componentN() 函数按声明顺序对应于所有属性;
  • copy() 函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    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分支了,因为所有情况都是已知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 关键字声明,括号后面声明返回值类型:

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

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

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

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

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

默认参数

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

1
2
3
4
5
6
7
8
class NetWork {
fun fetchData(page: Int, pageSize: Int = 20) {
}
}

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

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

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

具名参数

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

1
2
3
4
5
6
fun check(content: String, 
isWholeString: Boolean,
isUpperCase: Boolean,
needCheckLength: Boolean) {

}

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

1
check(content, true , false ,true)

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

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

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

高阶函数

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

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

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

1
2
3
4
5
fun getFilterFunction(): (String) -> Boolean {
return { input: String ->
input.length > 10
}
}

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

函数类型

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

1
(String, String) -> Boolean

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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了:

1
2
3
4
5
6
7
8
9
10
11
12
13
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表达式

1
2
3
4
fun filter(predicate: (String) -> Boolean) {

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

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

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

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

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

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

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

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

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

1
2
3
view.setOnclickListener{
/**/
}

if、when、for、while

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

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

1
2
3
4
5
6
7
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}

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

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

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

1
for (item in collection) print(item)

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

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

}
1
2
3
4
5
6
for(i in 0..99){

}
for(i in 0 until 100){

}

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

return、break、contine

  • return。默认从最直接包围它的函数或者匿名函数返回。
  • break。终止最直接包围它的循环。
  • continue。继续下一次最直接包围它的循环。

return

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

1
2
3
4
5
6
7
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@标签名返回到指定标签。

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

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

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

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

1
2
3
4
5
6
7
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方法:

1
2
3
4
5
6
7
8
9
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同样也可以使用标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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,是在一个对象的上下文中执行代码块,形如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
val user = User()
with(user) {

}
user.apply {

}
user.run {

}
user.let {

}
user.also {

}

他们间的区别在于

  • 引用上下文对象的方式
  • 返回值

引用上下文对象的方式

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

1
2
3
4
user.apply{
name = "sadhu"
age = 27
}

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

1
2
3
user?.let{
print(it)
}

返回值

alsoapply返回对象本身

1
2
3
4
// 返回对象本身 user
val user = user.also{

}

letwithrun返回代码块的返回值

1
2
3
4
5
6
// 返回代码块的返回值,String类型
val string = user.let{

it.name.toUpperCase()
}

主要区别表

函数 对象引用 返回值
let it Lambda 表达式结果
run this Lambda 表达式结果
with this Lambda 表达式结果
apply this 上下文对象
also it 上下文对象

场景

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

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

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

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

1
2
3
4
5
6
7
override fun convert(holder: BaseViewHolder, item: Person) {
with(item)
{
holder.setText(R.id.name,name)
holder.setText(R.id.age,age.toString())
}
}

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

1
2
3
4
5
val user = User()
val firstName = user.run{
name = "sadhu.static"
queryFirstNameByName(name)
}

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

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

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

1
2
3
4
val user = User()
user.also{
queryInfoByUser(user)
}
作者

sadhu

发布于

2020-06-15

更新于

2020-06-15

许可协议