Kotlin
知识点整理,有一定 Kotlin
基础,适合快速复习查缺补漏,会不断更新。
变量:
//可变 变量,可以被重新赋值
var name:String = 1
//不可变 变量,不可被重新赋值
val age = 1
//不可变 变量定义的 list,map等,本身不可变,但是如果是可变列表,则本身不可变,但内部的数据可以变化
class Test {
val listNumber: List<Int> = listOf(1, 2, 3, 4, 5, 6)
val mutableListNumber: MutableList<Int> = mutableListOf(1, 2, 3, 4, 5, 6)
fun test() {
listNumber = listOf() // 该句报错,不可变类型
listNumber[1] = 1 // 报错,不是可变列表
mutableListNumber = mutableListOf() // 该句报错,不可变类型
mutableListNumber[0] = 2 // 正常
}
}
函数:用fun
声明,默认是public
类型
//冒号后是返回值类型
fun say(message: String): String {
return "说了一句:$message"
}
//返回值可以直接用等号=处理
fun say(message: String): String = "说了一句:$message"
//函数中也可以嵌套函数,只能局部使用
fun out() {
inner() // 编译报错,要在内部声明的函数之后
fun inner() {
}
inner()
}
类型:没有基本数据类型
,都作为对象存在
var number: Int = 10//还有Double Float Long Short Byte
var char: Char = 'c'
var isFood: Boolean = true
var ages: IntArray = intArrayOf(1, 2, 3)//还有FloatArray DoubleArray CharArray,都属于kotlin 的 built-in函数
var str: String = "Lili"
Kotiln
中的Unit
,相当于Java
的Void
,空类型,但Kotlin
还有Any
类型,表示所有非空类型。
比如有返回结果,但不在意是什么类型:
fun getSomething(): Any {
}
类:默认不是public
,需要被继承,则要加open
//有open,可以被继承
open class KotlinTestActivity : AppCompatActivity(){
//初始化代码块,优先于构造器执行
init {
}
//加final,关闭了 override 的遗传性
final override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("hello world")
}
}
对象:实例化不用加new
//实例化一个学生对象
var studen: Student= Student()
对象的属性的get()、set()方法
class Student {
var name: String = "jack"
get() = "我是 $field"
set(value) {
field = "我叫 $value"
}
}
避免写set()
和get()
方法,可以使用元类data class
//注意属性是写在括号中的,会自动有set和get方法,中括号可以不用写
data class Student(
var name:String = ""
){
}
Java
里单例双重锁,枚举等一堆,懒汉饿汉的。Kotlin
里直接用object
标记类即是单例。
object TestConstant {
//静态常量
const val MAX_NUM: Int = 10
}
静态常量还可以写到某个类中
//不用单例修饰的时候,要写companion object 伴生对象处理静态常量
class TestConstant {
companion object {
const val MAX_NUM: Int = 10
}
}
Java
中匿名类,比如设置点击事件,都是直接new()
即可,Kotlin
中要使用object
来处理。比如
//java中
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
}
});
//kotlin中
view.setOnClickListener(object :View.OnClickListener{
override fun onClick(p0: View?) {
}
})
//lambda更简洁
view.setOnClickListener{
}
Kotlin
中有默认参数可以设置,即可以简化Java
中的方法重载
class Test {
private fun getUserName(name: String, age: Int = 10, address: String? = ""): String {
// kotlin中if语句也是表达式,可以有返回值
val add = if (address.isNullOrEmpty()) "" else " $address"
return "我叫 $name 今年 ${age}岁$add"
}
fun test() {
getUserName("Jack")
getUserName("Jack", 22)
getUserName("Jack", 22, "北京")
getUserName("Jack", "北京") // 编译出错,因为第二个参数默认应该是age是Int类型
getUserName("Jack", address = "北京") // 正常,命名可以指定某个参数的值
}
}
Kotlin
中的可变参数,即任意数量的参数
fun setNames(vararg name: String) {
println(name.size) // 打印出传递的参数个数
name.forEach {
print(it) // 打印每个名字
}
}
//调用
setNames("jack", "Bob", "lili")
Kotlin
中可以写顶层函数来直接引用,将属性
或者函数
放到class
之外即可全局引用,Kotlin
中常用的扩展函数,工具类等都可以简便的使用顶层函数处理。
package com.test.ext
import android.widget.EditText
//EditText的扩展函数,放到和package同级
fun EditText.getStr(isTrim: Boolean = false): String = this.text.toString().apply {
if (isTrim) trim()
}
fun test() {
val list = listOf(1, 2, 3, 4, 5, 6) // 是个内联函数
//最大最小、加减、总和
list.any { it > 4 } // 只要有符合条件的,就返回true
list.all { it > 3 } // 集合的全部元素都符合条件,才返回true
list.none { it < 1 } // 集合的全部元素都不符合条件,才返回true
list.count { it > 3 } // 返回集合中符合条件的总数
list.sum() // 请求总和,只有Int类型的list可使用
list.sumOf { // 每个元素都做一定操作后的值相加获得所有的元素总和
it + 1
}
list.maxOrNull() // 返回集合中最大的元素,可为null,元素继承Comparable类型的list可使用
list.minOrNull() // 返回集合中最小的元素,可为null,元素继承Comparable类型的list可使用
//遍历
list.forEach { // 遍历每一个元素,没有返回值
print(it)
}
list.onEach { // 遍历每一个元素,返回集合本身
print(it)
}.size // 可以引用到数量
list.forEachIndexed { index, value -> // 遍历每一个元素,包含下标和值
print("下标 $index 值 $value")
}
//过滤
list.drop(3) // 从0开始,去除前3个元素,返回剩余的元素列表
list.dropWhile { it > 3 } //从0开始,去除符合条件的前面的元素(该元素不去除),返回剩余的元素列表
list.dropLastWhile { it > 3 } // 同上,只不过从最后开始去除
list.filter { it > 3 } //返回所有符合条件的元素的列表
list.filterNot { it > 3 } //返回所有不符合条件的元素的列表
list.slice(2..5) //切片,返回2到5的元素组成的列表
list.take(3) // 返回前3个元素组成列表
list.takeLast(3) // 返回最后的3个元素组成的列表
list.takeWhile { it > 3 } //从0开始,返回符合条件的前面的元素(该元素不返回)组成的集合,和drop相反
list.takeLastWhile { it > 3 }// 同上,只不过从最后开始取
val students: List<Student> = listOf()
val mapResult = students.map {
"你好" + it.name
}
println("mapResult $mapResult")
val flatmapResult: List<String> = list.flatMap {
listOf("数字是" + 1)
}
println("flatmapResult $flatmapResult")
}
data class Student(
val name: String = "",
val age: Int = 0,
)
定义数据类User
data class User(
val name: String = "Jack",
val age: Int = 28,
val ability: List<String> = listOf()
)
调用测试,如果都只用name
字段,则相同结果,因为name字段不是list,无法平铺
val list = listOf(
User("Jack", 18, listOf("吃饭", "睡觉")),
User("Tom", 18, listOf("跑步", "游泳")),
User("Lili", 18, listOf("篮球", "跳远"))
)
//map:将List中每个元素转换成新的元素,并添加到一个新的List中,最后将新List返回
val mapResult = list.map {
it.name
}
//flatMap:返回指定list的所有元素,组成的一个list (表达式中必须返回list)
val flatMapResult = list.flatMap {
listOf(it.name) // name不是list,需要包装成list
}
logDebug("mapResult = $mapResult flatMapResult = $flatMapResult", tag = "test")
结果
mapResult = [Jack, Tom, Lili] flatMapResult = [Jack, Tom, Lili]
如果替换返回的结果name
改为ability
,如下
//map:将List中每个元素转换成新的元素,并添加到一个新的List中,最后将新List返回
val mapResult = list.map {
it.ability
}
//flatMap:返回指定list的所有元素,组成的一个list (表达式中必修返回list)
val flatMapResult = list.flatMap {
it.ability //本身是list不用再包装
}
则打印结果
mapResult = [[吃饭, 睡觉], [跑步, 游泳], [篮球, 跳远]] flatMapResult = [吃饭, 睡觉, 跑步, 游泳, 篮球, 跳远]
Kotlin
中的标准函数with
、run
、apply
等,标准函数是Standard.kt
文件中定义的函数。
val user = User("Jack", 18, listOf("吃饭", "喝水"))
with
最后一行为返回值,参数传入任意对象,且表达式中有该对象的上下文 (注意:需要直接调用)
val result = with(user) { //最后一行为返回值
"年龄是 $age"
}
logDebug(result, "test")
结果是
年龄是 18
run
基本同with,不用传参数,需要对象调用
val result = user.run { //最后一行为返回值
"年龄是 $age"
}
logDebug(result, "test")
结果是
年龄是 18
apply
基本相同,但是无法指定返回值,以当前对象本身,即上下文作为返回值。
val result = user.apply { //返回该对象本身
age = 999
}
logDebug(result, "test")
结果是
User(name=Jack, age=999, ability=[吃饭, 喝水])
高级函数
参数是函数或者返回值是函数的函数
kotlin里的函数类型表示
(String, Int) -> Unit
比如对两个数字操作,具体操作通过传入的方法来处理。
//参数包含一个执行函数
fun handle2Numbers(num1: Int, num2: Int, handle: (Int, Int) -> Int): Int = handle(num1, num2)
//两数相加
fun add(num1: Int, num2: Int): Int = num1 + num2
//两数相减
fun sub(num1: Int, num2: Int): Int = num1 - num2
函数当做参数,可指定方法去处理两个数字
handle2Numbers(1, 2, ::add)
handle2Numbers(1, 2, ::sub)
kotlin
高阶函数支持Lambda表达式
,所以可以不用专门再定义add
和sub
函数,直接使用Lambda表达式
handle2Numbers(1, 2) { n1, n2 ->
n1 + n2
}
handle2Numbers(1, 2) { n1, n2 ->
n1 - n2
}
内联函数的关键字 inline
高阶函数一般实现的Lambda表达式
,实际是被转换成了匿名内部类,即每调用一次Lambda表达式
,就会创建一个新的匿名内部类。因此,直接用inline
内联函数处理,kotlin
在编译的时候自动替换到需要调用的地方,不会有性能开销。注意:inline函数内不能引用该类的私有变量
//函数fun前加inline关键字
inline fun handle2Numbers(num1: Int, num2: Int, handle: (Int, Int) -> Int): Int = handle(num1, num2)
如果一个内联函数有多个函数参数
,默认所有的Lambda表达式
都会进行替换,但是某些函数参数不想替换。或者某些情况,传入的函数参数,还要传递给其他的函数处理,inline
关键字的函数内部只能把函数参数传递给inline
函数,此时就可以用noinline
关键字
//fun2不是内联函数
fun fun2(block: () -> Unit){
}
//fun1是内联函数,内部调用不是内联的fun2,会报错
inline fun fun1(age: Int, block1: () -> Unit, block2: () -> Unit) {
fun2(block1) // 编译不通过
}
可以把fun1
的参数类型新加关键字noinline
可编译通过,加上noinline
的函数就不会走替换代码的流程
inline fun fun1(age: Int, noinline block1: () -> Unit, block2: () -> Unit) {
fun2(block1)
}
普通函数的Lambda表达式
中,可以用return
关键字返回,
fun fun1(block: () -> Unit) {
block()
logDebug("fun1 end")
}
调用如下
fun1 {
logDebug("block start")
logDebug("block 测试普通函数的Lambda返回")
return@fun1 // 普通函数不能只写return
logDebug("block end")
}
结果
block start
block 测试普通函数的Lambda返回
fun1 end
可以看到,局部代码确实返回了,且后续的代码会继续执行
但是如果是inline
内联的函数的Lambda表达式
中的return语句
//改为了内联函数
inline fun fun1(block: () -> Unit) {
block()
logDebug("fun1 end")
}
调用用return
,则会影响外层代码,直接return,如果用return@fun1
,则还是局部返回
fun1 {
logDebug("block start")
logDebug("block 测试普通函数的Lambda返回")
return
logDebug("block end")
}
结果,后续的代码也不再执行,因为底层逻辑是把代码直接替换过去,相当于整块代码里走了return语句
block start
block 测试普通函数的Lambda返回
但如果内联函数的内部又创建匿名函数,则会报错
inline fun runRunnable(block: () -> Unit) {
val runnable = Runnable {
block() // 此句报错
}
runnable.run()
}
因为普通的Lambda表达式
不允许直接return
(非局部),而内联函数里的Lambda表达式
允许直接return
(非局部),因此要加上crossinline
关键字
inline fun runRunnable(crossinline block: () -> Unit) {
val runnable = Runnable {
block()
}
runnable.run()
}
crossinline
关键字相当于明确指出,本来内联函数里的Lambda表达式
可以return
(非局部)返回,现在有crossinline
关键字标记,则该内联函数里的Lambda表达式
不可以直接return
(非局部)返回了。
委托类和委托属性
委托类
将一个类的实例化借助其他类实现
说白了就是借助辅助对象,实例化一个实际的对象,比如
//借助构造函数里的HashSet去实例化一个实现Set接口的对象
class MySet<T>(private val helperSet: HashSet<T>) : Set<T> {
override val size: Int
get() = helperSet.size
override fun contains(element: T): Boolean = helperSet.contains(element)
override fun containsAll(elements: Collection<T>): Boolean = helperSet.containsAll(elements)
override fun isEmpty(): Boolean = helperSet.isEmpty()
override fun iterator(): Iterator<T> = helperSet.iterator()
}
这么看直接用辅助对象也没差别,但是当实现的对象需要其他方法,而大部分方法都需要辅助对象实现的时候就可以用委托的方法。kotlin
里有更简单的写法
//自动已经实现各个方法,需要可重载单独的方法
class MySet<T>(private val helperSet: HashSet<T>) : Set<T> by helperSet
委托属性
就是将属性的实现委托给另一个类
class MyClass {
var p by MyDelegate()
}
该代码表示 调用p
属性的时候,会自动调用 MyDelegate
的getValue()
方法,设置属性的时候,会自动调用setValue()
方法。所以,MyDaelegate
类必须有getValue()
和setValue()
方法,且用operator
关键字声明。
class MyDelegate() {
var p: Any? = null
operator fun getValue(myClass: MyClass, property: KProperty<*>): Any {
return p ?: ""
}
operator fun setValue(myClass: MyClass, property: KProperty<*>, any: Any) {
p = any
}
}
lazy
函数
//注意不能用var,因为lazy里没有setValue函数
val p by lazy {
""
}
lazy
函数延迟加载,其实就是生成了一个委托类,会返回一个Delegate
对象,对象的getValue()
方法,其实就是传入的Lambda表达式
,即p
的值,就是在首次调用的时候 Lambda表达式
最后一行的返回值
看一个Android
里常用的方法,在Activity
页面中获取ViewModel
,会使用下边的代码
注意:要先添加依赖,否则会引用不到该扩展函数
implementation "androidx.activity:activity-ktx:1.2.2"
implementation "androidx.fragment:fragment-ktx:1.3.2"
private val myViewModel by viewModels<MyViewModel>()
viewModels
扩展函数其实就是返回了一个Lazy
,有封装的getValue
方法,相当于委托该属性由该扩展函数获取
infix
函数,其实就是用更接近语言的方式写的代码,相当于自定义了关键字,比如map
里的 to
val map: MutableMap<String, Any> = mutableMapOf(
"name" to "Jack",
"age" to 66,
"address" to "北京",
)
to
不是关键字,但是可以已这种语法写代码。例如,自定义一个字符串检测函数
infix fun String.begin(pre: String): Boolean = this.startsWith(pre)
调用
"你好".begin("你") // 普通扩展函数的用法
"你好" begin "你" // 类似to的语法
限制条件:infix
函数必须是某个类的成员函数,可以定义成扩展类
,同时必须只有一个参数。
泛型擦除
:泛型的约束只在编译时期,运行的时候就擦除其类型。比如Java
中的List
private List<String> list = new ArrayList<>();
public void test(){
list.add("你好");
list.add(1); // 报错,不可以添加字符串以外的类型
}
但是到运行时候,JVM不管是什么类型,只能识别出是个List
。因此,在代码里也无法使用如下的代码
fun <T> test() {
T::class.java // 报红
}
因为在运行的时候是不知道泛型的具体类型的,而Kotlin
因为有内联函数inline
的存在,会把代码在编译时期自动替换到调用的地方,所以类型还是可以直接确定的。即在inline
函数里,泛型可以使用上述的语句,但要加上reified
关键字表示该泛型要实化。
修改代码
inline fun <reified T> test() {
T::class.java // 无问题
}
泛型实化的实际应用
最常见的就是Activity
的跳转,直接写一各Context
的扩展函数,注意Intent的实例化方式,使用了T::class.java
inline fun <reified T> Context.start(vararg params: Pair<String, Any?> = arrayOf(), flag: Int = -1) {
val intent = Intent(this, T::class.java).apply {
if (flag != -1) {
this.addFlags(flag)
}
if (params.isNotEmpty()){ // 有参数
val bundle: Array<Pair<String, Any?>?> = arrayOfNulls(params.size)
for (i in params.indices){
bundle[i] = params[i]
}
putExtras(bundle.toBundle()) // 是一个array的扩展函数,直接把array转换成budle
}
}
startActivity(intent)
}
调用
start("name" to "jack", "age" to 10)
在TestActivity
中获取参数
intent.extras?.let {
val name = it.getString("name", "")
val age = it.getInt("age", 1)
}
协变
先看一些代码
//定义Person类,Student和Teacher继承该类
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int) : Person(name, age)
class Teacher(name: String, age: Int) : Person(name, age)
//定义一个泛型类
class TestData<T> {
private var data: T? = null
fun set(t: T?) { // 可以设置值
data = t
}
fun get(): T? = data
}
class Test {
fun test() {
val student = Student("jack", 18)
val data = TestData<Student>() // 实例化一个指定泛型是Student的类
data.set(student)
handleData(data) // 报错,因为需求的是TestData,传入的是TestData
}
fun handleData(data: TestData<Person>) {
val teacher = Teacher("tom", 20)
data.set(teacher)
}
}
报错原因,提示是需求的是TestData
,因为如果传递了,在handleData
里可能会把数据更换为其他的,比如Student
换为Teacher
,类型不统一,无法替换。所以编译是不通过的。即虽然Student
是Person
的子类,但是TestData
不是TestData
的子类。当做不同的参数类型,所以无法传递。其实是因为在handleData
里对参数进行了set
操作,因为不是一个类型,类型转换错误,所以无法编译通过,但是如果我只是get
数据,即该类型是只读
,本质上仍旧是可以传递的。
关键字处理:协变-只读-out
修改代码
//定义Person类,Student和Teacher继承该类
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int) : Person(name, age)
class Teacher(name: String, age: Int) : Person(name, age)
//定义一个泛型类
class TestData<out T> {
private var data: T? = null
// fun set(t: T?) {
// data = t
// }
fun get(): T? = data
}
class Test {
fun test() {
val student = Student("jack", 18)
val data = TestData<Student>() // 实例化一个指定泛型是Student的类
// data.set(student)
handleData(data) // 报错,因为需求的是TestData,传入的是TestData
}
fun handleData(data: TestData<Person>) {
//这里只能读取
// val teacher = Teacher("tom", 20)
// data.set(teacher)
}
}
这次是可以编译通过的,可以看到泛型前加了out
关键字,表示他只读。但是只读了,要怎么赋值呢?可以放到构造函数里。
//定义一个泛型类
class TestData<out T>(val data: T) {
fun get(): T? = data
}
如果改成var
则报错,因为val
是声明后不可更改的,var
的话可能更改,就不符合只读out
,不过可以把var
声明成private
,则可正常,因为外界改不了,就还是只读out
的
把最开始的例子里handleData(data: TestData
方法换为handleData(data: List
,则Java
不可以,照样报错,Kotlin
则可以正常通过,因为Kotlin
里有好多内置的支持协变的数据结构,比如List
源码就可以看到
public interface List<out E> : Collection<E>{
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
...
}
直接就是out
只读,但是会发现contains
函数里,还是传参修改了该泛型类型,因为该方法只是判断是否包含某个元素,并没有赋值改变,虽然出现在in
的位置,表示可能会改变,和只读out
冲突,但是明确函数里没有改变的代码,可以加个@UnsafeVariance
,注解,表示编译时期不再检查处理,一切由自己承担。
可以总结定义
协变: 定义MyClass 泛型类,若A是B子类,同时MyClass也是MyClass子类,则MyClass在T类型上协变。
逆变: 定义MyClass 泛型类,若A是B子类,同时MyClass也是MyClass子类,则MyClass在T类型上逆变。
逆变
则只能set
值,不可以读取get
逆变应用例子Comparable
public interface Comparable<in T> {
public operator fun compareTo(other: T): Int
}
逆变
关键字in
,表示只读,即Student是Person子类,Comparable也是Comparable子类。因为谁是谁子类 对比较的逻辑无关,可以设置成逆变。
线程
需要依赖操作系统调度,但是协程
是依赖于线程
。即协程
允许在单线程模式下
模拟多线程
效果。
注意:并不是多线程,还是单个显示中模拟并发场景,例如Android要求网络请求在子线程,就不能在主线程开启协程后执行,必须到子线程开启执行。
创建协程
GlobalScope创建的是顶层协程,程序退出才会取消。一般不使用。
GlobalScope.launch {
logDebug("协程工作开始")
}
RunBlocking创建的协程作用域,可以保证在作用域内代码执行完前阻塞当前线程
runBlocking {
logDebug("创建协程")
}
RunBlocking只在测试环境用,因为会挂起外部线程,如果在主线程使用,则可能会ANR
创建多个协程,只要在协程作用域中调用launch
runBlocking { // 外层作用域结束,子协程也会结束
launch {
logDebug("协程1开始,当前线程是 ${Thread.currentThread().name}")
}
launch {
logDebug("协程2开始,当前线程是 ${Thread.currentThread().name}")
}
}
结果可以看到,是在同一线程,但是并发执行
15:18:56.424 协程1开始,当前线程是 main
15:18:56.425 协程2开始,当前线程是 main
launch
函数中如果调用其他函数,则其他函数需要被声明成挂起函数suspend
挂起函数中是没有作用域的,不能直接新开启协程,但是可以调用其他挂起函数。可以用coroutineScope
新开启协程作用域,coroutineScope
其实是个挂起函数,会继承外部的协程作用域并创建子协程,同runBlocking
,外部的协程会一直挂起,但只会阻塞当前协程,不影响线程。
fun test() {
runBlocking { // 外层作用域结束,子协程也会结束
launch {
logDebug("协程1开始")
count()
}
}
}
suspend fun count() {
logDebug("倒计时")
coroutineScope { // 会继承作用域且创建子协程
}
}
val job = Job()
val scope = CoroutineScope(job) // 注意是个函数 不是实例化一个类
scope.launch {
logDebug("协程1开始")
}
scope.launch {
logDebug("协程2开始")
}
job.cancel()
这样取消的时候,只用调用一次cancel
即可
有时候需要协程返回的结果,而不是job,则可以使用async
,会返回一个Deferred
对象,然后调用其await()
,该函数会等待执行结果后才会继续执行。
val job = Job()
val scope = CoroutineScope(job) // 注意是个函数 不是实例化一个类
scope.launch {
logDebug("scope launch开始")
val result1 = scope.async {
delay(1000)
10 * 10
}.await()
val result2 = scope.async {
delay(1000)
10 * 10
}.await()
logDebug("结果是 result2+result2 = ${result1 + result2}")
}
结果
15:42:43.794 scope launch开始
15:42:45.805 结果是 result2+result2 = 200
因为await
会挂起协程,所以相当于是串行执行。可以修改代码
val job = Job()
val scope = CoroutineScope(job) // 注意是个函数 不是实例化一个类
scope.launch {
logDebug("scope launch开始")
val d1 = scope.async {
delay(1000)
10 * 10
}
val d2 = scope.async {
delay(1000)
10 * 10
}
logDebug("结果是 result2+result2 = ${d1.await() + d2.await()}")
}
withContext
函数,强制要求指定一个线程参数,先看代码
scope.launch {
logDebug("scope launch开始")
val result = withContext(Dispatchers.IO) { // withContext函数要指定线程参数
10 * 10
}
logDebug("结果是 result = $result")
}
withContext
相当于ayanc+await的逻辑。给协程指定的线程参数有
Dispatchers.Default 默认低并发
Dispatchers.IO 较高并发,开启子线程
Dispatchers.Main 主线程
领域特定语言,可以构建一些专有的语法结构。
先写个例子,类似Gradle
里添加依赖,并且对比下,DSL
写法和普通调用方法的差别。
class Dependency {
val libraries = ArrayList<String>()
fun implementation(lib: String) {
libraries.add(lib)
}
fun dependencies(block: Dependency.() -> Unit): List<String> {
val dependency = Dependency()
dependency.block()
return dependency.libraries
}
//DSK方式调用
fun test1() {
val result = dependencies {
implementation("测试添加依赖")
}
logDebug("result = $result")
}
//普通方式调用
fun test2() {
val d = Dependency()
d.implementation("测试添加依赖")
val result = d.libraries
logDebug("result = $result")
}
}
测试
val d = Dependency()
d.test1()
val d2 = Dependency()
d2.test2()
结果是相同的
result = [测试添加依赖]
result = [测试添加依赖]
再一个动态制作HTML表格的例子
class Td(var content: String = "") {
fun html() = "\n\t\t$content "
}
class Tr {
val children = mutableListOf<Td>()
fun td(block: Td.() -> String) {
val td = Td()
td.content = td.block()
children.add(td)
}
fun html(): String {
val builder = StringBuilder()
builder.append("\n\t")
for (childTag in children) {
builder.append(childTag.html())
}
builder.append("\n\t ")
return builder.toString()
}
}
class Table {
val children = mutableListOf<Tr>()
fun tr(block: Tr.() -> Unit) {
val tr = Tr()
tr.block()
children.add(tr)
}
fun html(): String {
val builder = StringBuilder()
builder.append("")
for (childTag in children) {
builder.append(childTag.html())
}
builder.append("\n
")
return builder.toString()
}
}
调用
fun test1() {
val result = table {
tr {
td { "1" }
td { "2" }
td { "3" }
}
tr {
td { "4" }
td { "5" }
td { "6" }
}
}
logDebug(result)
}
fun test2() {
val table = Table()
val tr1 = Tr()
val td1 = Td("1")
val td2 = Td("2")
val td3 = Td("3")
tr1.children.add(td1)
tr1.children.add(td2)
tr1.children.add(td3)
val tr2 = Tr()
val td4 = Td("4")
val td5 = Td("5")
val td6 = Td("6")
tr2.children.add(td4)
tr2.children.add(td5)
tr2.children.add(td6)
table.children.add(tr1)
table.children.add(tr2)
val result = table.html()
logDebug(result)
}
结果是一样的
1.判断延迟初始化的变量是否初始化
if(!::a.isInitialized){ // 如果还没有初始化
}
2.密封类,可以省去when
语句最后没必要的else
分支
注意:密封类及其子类 必须在同一个文件中
sealed class A
class B : A()
class C : A()
//调用when的时候
when(a:A){
is B -> "B"
is C -> "C" // 不用再写else
}
3.运算符重载
关键字operator
,部分函数(plus()、minus()等
)前加上operator
关键字,即可使对象使用+/-操作符。
class Student(val grade: Int) {
//两个学生对象相加,返回一个新的对象
operator fun plus(student: Student): Student {
return Student(grade + student.grade)
}
//常用的函数
//plus()加、minus()减、times()乘、div()除
}
常用例子,字符串重复n次
//可以直接用字符串*次数 eg:你好*8
operator fun String.times(n: Int): String {
val builder = StringBuilder()
repeat(n) { builder.append(this) }
return builder.toString()
}
//可以再简化
// 这个repeat是字符串内置的重复次数方法
operator fun String.times(n: Int): String = repeat(n)
调用方式
"重复这段话3次".times(3) //正常调用
//或者
"重复这段话3次" * 3 // 操作符调用