为什么使用 kotlin 简洁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 data class Customer (val name: String, val email: String, val company: String)val positiveNumbers = list.filter { it > 0 }object ThisIsASingleton { val companyName: String = "JetBrains" }
安全 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var output: String output = null val name: String? = null println(name.length()) fun calculateTotal (obj: Any ) { if (obj is Invoice) obj.calculateTotal() }
互操作性 Kotlin 可以使用 JVM 上的任何现有库,100%兼容 Java 可直接调用 Kotlin 写的文件
学习语法 官网
和java对比
注意点 java 和 kotlin 互相调用时,不可空校验就在 java 上失效了,所以 Java 调用 kotlin 时要判空。 或者将类型定义成可空。 java 返回 null 给 kotlin 也可以定义定义可空类型,不要定义不可 null 类型
https://blog.csdn.net/Goals1989/article/details/126964125 == :可以对基本数据类型以及 String 等类型进行内容比较,相当于 Java 中的 equals === :对引用的内存地址进行比较,相当于 Java 中的 ==
Elvis 操作符 可以通过 ?: 的操作来简化 if null 的操作
1 2 3 4 5 6 7 8 val date = lesson.date?: "⽇日期待定" val state = lesson.state?: return val content = lesson.content ?: throw IllegalArgumentException ("content expected" )
kotlin 默认不能空,变量类型后面跟? 号定义,表明这是一个可空类型
?. 代表着如果该类型为空的话就返回 null 不做后续的操作,如果不为空的话才会去访问对应的方法或者属性
!!. 代表着如果该类型为空的话就抛出 NullPointerException,如果不为空就去访问对应的方法或者属性,所以只有在很少的特定场景才用这种符号,代表着程序不处理这种异常的 case 了,会像 java 代码一样抛出 NullPointerException。而且代码中一定不用出现下面这种代码,会让代码可读性很差而且,如果有空指针异常,我们也不能马上发现是哪空了:
1 2 3 4 5 6 7 8 9 10 val user = User ()user !!.name!!.subSequence(0 ,5 )!!.lengthif (view != null ) { //view .setBackgroundColor(Color.RED) // 这样写不对,因为在多线程中也可能报错,需要在view 后加上?view ?.setBackgroundColor(Color.RED)
使用 lateinit
关键字修饰属性,您可以将属性的初始化推迟到稍后的时间点。这样,您可以声明一个非空的属性,但在构造函数中可以不进行初始化。然后,在稍后的某个时间点,通过对该属性进行赋值操作来完成初始化。搭配 byLazy 使用,否则别使用。
lateinit 关键字只能用于 var(可变)属性,并且只能用于非空类型。它通常用于在某些情况下延迟初始化属性,例如在依赖注入框架中。
如果想要带着 index 遍历集合的话:
1 2 3 4 5 for ((index , element ) in newList?.withIndex()!!) { if (index ==6 ){ break } }
构造函数 使⽤类名:: class 获取的是 Kotlin 的类型是 KClass 使⽤类名:: class. java 获取的是 Java 的类型 Kotlin 的顶层⽗父类是 Any ,对应 Java 当中的 Object ,但是比 Object 少了wait ()/notify ()等函数
Unit Kotlin 中的 Unit 对应 Java 中的 void 在 Java 中通过 「类名. this」 获取⽬标类引⽤ 在 Kotlin 中通过「this@类名」获取目标类引⽤
if 不仅是是条件语句,也是表达式,可以接收参数,返回最后一行
1 2 3 4 5 6 val mode = if (args.isNotEmpty() && args[0] == "1" ) { DEBUG } else { USER }
无论是初始化代码块还是伴生对象的初始化块,它们都会在构造函数之前执行 首先执行初始化代码块,然后执行伴生对象的初始化块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class MyClass { init { } companion object { init { } } constructor () { } }
构造函数 不管什么构造方法,都是先执行 init 模块逻辑,后执行构造方法的逻辑
伴生对象 在 Kotlin 中,虽然伴生对象的行为类似于某些编程语言中的静态成员,但它们并不是真正的静态。Kotlin 中没有直接的静态成员概念,而是使用伴生对象来模拟类级别的静态成员。
在创建对象时,首先会执行类的初始化代码块,然后才会执行伴生对象的初始化块。这是因为伴生对象的初始化在类的实例化过程中被视为类的一部分。
虽然伴生对象的初始化块在概念上类似于静态初始化块,但它们不是在类加载时执行的。相反,它们在首次访问伴生对象或通过创建对象时执行。
因此,在 Kotlin 中,初始化代码块先于伴生对象的初始化块执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public final class MyClass { public static final class Companion { private static final int someProperty = 42 ; public final void someFunction () { System.out.println("Hello from companion object" ); } } public static final Companion Companion = new Companion (); private MyClass () { } }
companion object
被编译为一个名为 Companion
的静态内部类,它包含伴生对象的成员变量和方法。 外部类加载后才会加载内部类,类似静态内部单例
扩展函数 在编译时,编译器会将 obj.extensionFunction () 转换为 MyClass.extensionFunction (obj) 的静态函数调用。
1 2 3 4 5 6 7 8 9 10 11 fun toast (message: String, length: Int = Toast.LENGTH_SHORT) { Toast.makeText(this , message, length).show() } toast("hello" ) fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) { Toast.makeText(this , message, duration).show() }
常见操作符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 val list = listOf<Int>(1 , 2 , 3 , 5 , 10 , 8 , 2 )val newList = ArrayList<Int>(); list.forEach { val newElement = it * 2 + 3 newList.add(newElement) }val newList2 = list.map { it * 2 + 3 } newList2.forEach(::println) newList2.map(::println) list.takeWhile { it <= 3 }.forEach(::println) list.forEach { if (it % 2 == 0 ) { println(it) } } list.filter { it.isEvent() }.forEach(::println)
take 是从集合中取前几个元素 takeLast 是从集合中取后几个元素 sortedBy 排序 过滤 list,符合过滤条件的就是过滤结果 filterNot 把符合条件的过滤掉,剩下的是结果。这个操作和 filter 相反 slice, 取集合中的某一部分takeIf : 避免在某些情况下使用冗长的 if-else 语
List 防止崩溃取值方式 getOrElse () 提供用于计算默认值的函数,如果集合中不存在索引,则返回默认值。 getOrNull () 返回 null 作为默认值。 joinToString 字符串拼接
kotlin 自带 copy 方法需要更新 State 时,借助 data class 的 copy 方法可以快捷地拷贝构造一个新实例。
难点 高阶函数 高阶函数是将函数用作参数或返回值的函数, 还可以把函数赋值给一个变量。
所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:(A, B) -> C 表示接受类型分别为 A 与 B 两个参数并返回一个 C 类型值的函数类型。参数类型列表可以为空,如 () -> A,Unit 返回类型不可省略。
(Int) -> String
函数类型表示法可以选择性地包含函数的参数名:(x: Int, y: Int) -> Point。这些名称可用于表明参数的含义。 (Button, ClickEvent) -> Unit 如需将函数类型指定为可空,请使用圆括号:((Int, Int) -> Int)?
1 2 3 4 5 6 7 8 fun a (funParam: (Int) -> String): String { return funParam(1 ) } fun b (param: Int) : String { return param.toString() }
调用
1 2 3 4 5 6 a(::b)var d = ::bb (1 ) d(1 ) (::b)(1 ) b.invoke(1 )
对象是不能加个括号来调用的,但是函数类型的对象可以。为什么?因为这其实是个假的调用,它是 Kotlin 的语法糖,实际上你对一个函数类型的对象加括号、加参数,它真正调用的是这个对象的 invoke () 函数
双冒号 :: 创建一个函数引用或者一个类引用
函数引用 1 fun isOdd (x: Int) = x % 2 != 0
我们可以很容易地直接调用它(isOdd (5)),但是我们也可以将其作为一个函数类型的值,例如将其传给另一个函数。为此,我们使用 :: 操作符:
1 2 3 val numbers = listOf(1 , 2 , 3 ) println(numbers.filter(::isOdd))
这里 :: isOdd 是函数类型 (Int) -> Boolean 的一个值。
在类外面,如果我们需要使用类的成员函数或扩展函数 ,它需要是限定的,例如 String:: toCharArray。
1 2 3 4 5 6 7 8 9 10 11 val args: Array<String> = arrayOf("1" , "2" ) args.filter(String::isNotEmpty) class PdfPrinter { fun println (any: Any) { kotlin.io.println(any) } }val pdfPrinter = PdfPrinter() args.forEach(pdfPrinter::println)
类引用
该引用是 KClass 类型的值 请注意,Kotlin 类引用与 Java 类引用不同。要获得 Java 类引用,请在 KClass 实例上使用 .java 属性。 平时写的类,其信息都可以在这个 KClass 来获取
属性引用 1 2 3 4 5 6 7 8 9 10 11 12 data class MediaItem (val title: String, val url: String)var items= mutableListOf<MediaItem>() items .sortedBy { it.title } .map { it.url } .forEach { print(it) } items .sortedBy(MediaItem::title) .map(MediaItem::url) .forEach(::println)
匿名函数 没有名字的函数 要传一个函数类型的参数,或者把一个函数类型的对象赋值给变量,除了用双冒号来拿现成的函数使用,你还可以直接把这个函数挪过来写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 fun b (param: Int ) : String { return param.toString() } a(fun b (param: Int ) : String { return param.toString() });val d = fun b (param: Int ) : String { return param.toString() } a(fun (param: Int ) : String { return param.toString() });val d = fun (param: Int ) : String { return param.toString() }
如果你在 Java 里设计一个回调的时候是这么设计的:
1 2 3 4 5 6 public interface OnClickListener { void onClick (View v) ; }public void setOnClickListener (OnClickListener listener) { this .listener = listener; }
使用的时候是这么用的:
1 2 3 4 5 6 view.setOnClickListener (new OnClickListener() { @Override void onClick(View v) { switchToNextPage (); } });
kotlin 写法
1 2 3 4 5 6 7 fun setOnClickListener (onClick: (View ) -> Unit ) { this .onClick = onClick } view.setOnClickListener(fun (v: View ) : Unit ) { switchToNextPage() })
Lambda 写法:
1 2 3 view .setOnClickListener({ v: View -> switchToNextPage() })
Lambda 表达式 简化匿名函数,代码更简洁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 view.setOnClickListener ({ v: View -> switchToNextPage () }) view.setOnClickListener () { v: View -> switchToNextPage () } view.setOnClickListener { v: View -> switchToNextPage () } view.setOnClickListener { switchToNextPage () }
Lambda 表达式的完整语法形式如下:
1 2 val sum: (Int , Int ) -> Int = { x: Int , y: Int -> x + y } val sum = { x: Int , y: Int -> x + y }
多参数例子: fold 函数:将所提供的操作应用于集合元素并返回累积的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 val items = listOf (1 , 2 , 3 , 4 , 5 ) items.fold (0 , { acc: Int, i : Int -> print ("acc = $acc, i = $i, " ) val result = acc + i println ("result = $result" ) result }) val joinedToString = items.fold ("Elements:" , { acc, i -> acc + " " + i })
输出:
1 2 3 4 5 6 7 acc = 0 , i = 1 , result = 1 acc = 1 , i = 2 , result = 3 acc = 3 , i = 3 , result = 6 acc = 6 , i = 4 , result = 10 acc = 10 , i = 5 , result = 15 joinedToString = Elements: 1 2 3 4 5
总结 函数不能直接传递或者赋给某个变量,需要函数类型实例化,有三种方式:
使用已有声明的可调用引用
函数引用
使用函数字面值的代码块
匿名函数
lambda 表达式
Kotlin 的高阶函数、匿名函数和 Lambda 表达式
例子 实现接口
1 2 3 4 5 6 7 var onVideoStartCallBack: (() -> Unit) ? = null onVideoStartCallBack ?.invoke () videioView .onVideoStartCallBack = {}
函数里实现接口
1 2 3 4 5 6 7 8 9 10 11 object UploaderListHelper { fun startTaskUpload (activity: Activity , startCallBack: ((Int ) -> Unit )?) { startCallBack.invoke(position) } } UploaderListHelper.startTaskUpload(activity) { refreshProgress(it) }
闭包就是 Lambda 表达式 lambda 表达式,通常是在需要一个函数,但是又不想去命名一个函数的场合下使用,也就是匿名函数。而 Lambda 可以简化匿名函数,使代码更加简洁。也可以将一个代码块赋值给一个变量,在调用方法的时候,可以直接将这个 lambda 表达式当作参数传递进去
kotlin 接口回调,省了个方法定义
1 2 3 4 5 6 7 private lateinit var delListener: ((Boolean , DiseaseBean) -> Unit ) fun setDelListener (delListener: ((Boolean , DiseaseBean ) -> Unit )) { this .delListener = delListener } delListener?.invoke(holder.btDel.isChecked, tagBean)
更简单,传个方法,接口都不用写了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mcDiagnosisType.showLayout { if (it){ mcDiseaseType.animateClose() } } fun showLayout (callBack :(((Boolean )->Unit ))?=null ) { if (isShow){ animateClose() callBack?.invoke(isShow) }else { animateOpen() callBack?.invoke(isShow) } }
作用域函数 Kotlin 标准库包含几个函数,它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个 lambda 表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这些函数称为作用域函数。共有以下五种:let、run、with、apply 以及 also。
这些函数基本上做了同样的事情:在一个对象上执行一个代码块。不同的是这个对象在块中如何使用,以及整个表达式的结果是什么。 目的:简洁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 val person = findPerson (); //person是可null 的,所以需要? println (person?.age) println (person?.name) //上面太麻烦,findPerson加了?,所以后面不需要了,减少的判空操作。let可以安全调用 findPerson ()?.let { person -> person.work() println (person.age) } //还可以更简洁,person也不用写 findPerson ()?.apply { work() println (age) }
使⽤时可以通过简单的规则作出一些判断
返回自身 返回值是它本身 从 apply 和 also 中选
作⽤域中使⽤ this 作为参数选择 apply
1 2 3 4 5 val adam = Person ("Adam" ).apply { age = 32 city = "London" }println (adam)
作⽤域中使⽤ it 作为参数选择 also
1 2 3 4 val numbers = mutableListOf ("one" , "two" , "three" ) numbers .also { println ("The list elements before adding new one: $it" ) } .add ("four" )
with 非扩展函数
1 2 3 4 5 6 val numbers = mutableListOf ("one" , "two" , "three" )with (numbers) { println ("'with' is called with argument $this" ) println ("It contains $size elements" ) }
不需要返回自身 从 run 和 let 中选择
作用域中使用 this 作为参数,选择 run 作用域中使用 it 作为参数,选择 let, 适合配合空判断的时候
1 2 3 4 5 6 7 8 9 10 11 12 val service = MultiportService ("https://example.kotlinlang.org" , 80 ) val result = service.run { port = 8080 query (prepareRequest () + " to port $port" ) } val letResult = service.let { it.port = 8080 it.query (it.prepareRequest () + " to port ${it.port}" ) }
==it 作为参数的好处== let 允许我们自定义参数名字,使可读性更强,如果倾向可读性可以选择 T.let
委托机制 Kotlin 中的委托是一种抽象的设计模式,允许将工作委托给另一个对象处理。在 Kotlin 中委托分为两种类型: 类委托和属性委托
类委托 类委托的核心思想在于将一个类的具体实现委托给另一个类完成。通过使用类委托,可以将某些特定行为委托给其他类处理,从而实现代码的解耦和重用。类委托的使用害要创建实现了特定接口或继承了抽象类的类,并将其作为委托对象传递给需要处理的对象。
1 2 3 4 5 6 7 8 9 10 interface IGamePlayer { fun rank () fun upgrade () }class DelegateGamePlayer (private val player: IGamePlayer): IGamePlayer by player
1 2 3 4 5 6 7 fun main () { val realGamePlayer = RealGamePlayer("张三" ) val delegateGamePlayer = DelegateGamePlayer(realGamePlayer) delegateGamePlayer.rank() delegateGamePlayer.upgrade() }
属性委托 属性委托则是将属性的 getter 和 setter 方法逻辑委托给另一个类实现。属性委托允许将属性的读取和设置逻辑放在另一个地方处理,比如更改属性名称或延迟初始化等场景。通过使用属性委托,可以将属性的逻辑封装在一个类中并在需要时将其传递给其他对象使用
一文彻底搞懂Kotlin中的委托 - 掘金
val
属性实现 ReadOnlyProperty
,var
属性实现 ReadOnlyProperty
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Delegate1 : ReadOnlyProperty <Any,String >{ override fun getValue (thisRef: Any , property: KProperty <*>) : String { return "通过实现ReadOnlyProperty实现,name:${property.name} " } }class Delegate2 : ReadWriteProperty <Any,Int >{ override fun getValue (thisRef: Any , property: KProperty <*>) : Int { return 20 } override fun setValue (thisRef: Any , property: KProperty <*>, value: Int ) { println("委托属性为: ${property.name} 委托值为: $value " ) } }class Test { val d1: String by Delegate1() var d2: Int by Delegate2() }
内联函数 内联函数 inline 高阶函数实现的原理:函数类型其实是生成了一个对象
使用 inline 能避免函数的 lambda 形参额外创建 Function 对像
kotlin 源码里使用了很多的内联函数,它的作用简单来说就是方便进行类型推导,能具体化类型参数。
被 inline 标记的函数就是内联函数, 其原理就是:在编译时期, 把调用这个函数的地方用这个函数的方法体进行替换 1 2 3 4 5 6 7 8 inline fun <T> method(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } }
method (lock, {“我是 body 方法体”})//lock 是一个 Lock 对象
其实上面调用的方法, 在编译时期就会把下面的内容替换到调用该方法的地方, 这样就会减少方法压栈, 出栈, 进而减少资源消耗;
1 2 3 4 5 6 7 lock .lock ()try { return "我是body方法体" } finally { lock .unlock() }
也就是说 inline 关键字实际上增加了代码量,但是提升了性能,而且增加的代码量是在编译期执行的,对程序可读性不会造成影响。
inline 的坏处 :
对于一个 public 的 inline 方法,他不可以引用类的私有变量。
inline 关键字对于 lambda 的处理有的时候不是我们想要的, inline 函数的形参 lambda 不能有 return 语句,避免 lambda 中的 return 影响外部程序流程
1 2 3 4 5 6 7 8 fun main (args: Array <String >) { println("Start of main" ) multiplyByTwo(5 ) { println("Result is: $it " ) return } println("End of main" )}
总结
使用 inline,内联函数到调用的地方,能减少函数调用造成的额外开销,在循环中尤其有效
使用 inline 能避免函数的 lambda 形参额外创建 Function 对象
使用 noinline 可以拒绝形参 lambda 内联
使用 crossinline 显示声明
inline
关键字的作用,是把 inline
方法以及方法中的 lambda
参数在编译期间复制到调用方,进而减少函数调用以及对象生成。对于有时候我们不想让 inline
关键字对 lambda
参数产生影响,可以使用 noline
关键字。如果想 lambda
也被 inline
,但是不影响调用方的控制流程,那么就要是用 crossinline
。
Lambda 表达式不允许使用 return,除非这个 Lambda 表达式是内联函数的参数。
(Function1)null. INSTANCE,是由于反编译器工具在找不到等效的 Java 类时的显示的结果。
Kotlin的inline noinline crossinline笔记 - 简书
Kotlin inline noinline crossinline 解答 - 掘金
Reified
由于 Java 中的泛型存在类型擦除的情况,任何在运行时需要知道泛型确切类型信息的操作都没法用了。比如你不能检查一个对象是否为泛型类型 T 的实例,所以需要反射。
而 reified,字面意思:具体化,其实就是具体化泛型 。
主要还是有内联函数 inline, 才使得 kotlin 能够直接通过泛型就能拿到泛型的类型,只有内联函数的类型参数可以具体化。 [[泛型#泛型擦除]]
例子
定义实现一个扩展函数启动 Activity,一般都需要传 Class< T
> 参数:
1 2 3 4 5 6 7 private fun <T : Activity> Activity.startActivity (context: Context , clazz: Class <T >) { startActivity(Intent(context, clazz)) } startActivity(context, NewActivity::class .java)
使用 reified,通过添加类型传递简化泛型参数
1 2 3 4 5 6 7 inline fun <reified T : Activity> Activity.startActivity (context: Context ) { startActivity(Intent(context, T::class .java)) } startActivity<NewActivity>(context)