全网最全面的由浅到深的Kotlin基础教程(六)
前言
本篇文章接着上一篇文章全网最全面的由浅到深的Kotlin基础教程(五)继续进阶学习kotlin,建议看完上一篇文章,再来看本篇文章。本篇主要讲解kotlin扩展函数,注解,DSL,kotlin与java的相互调用等相关语法。
1. kotlin扩展函数
kotlin可以很方便的为任何类添加新的函数,这个函数称为扩展函数,这点真的超级牛,kotlin源码中到处可见这种扩展函数。
1.1 定义类扩展函数
可以给一个类添加新的自定义函数,且这个函数和类中定义的函数没有任何区别,当做类中定义的函数用就可以了,示例代码如下:
class Kt28(val name:String)
// 增加扩展函数
fun Kt28.show() = println("Kt28类的扩展函数,name=$name")
//每个扩展函数都拥有一个类对象本身this
fun String.addExtend(num:Int) = this + "#".repeat(num)
fun main() {
Kt28("sun").show()
println("mekeater".addExtend(6))
}
运行结果如下:
1.2 超类上定义扩展函数
既然kotlin可以为任意类添加自定义函数,那么如果给kotlin的超类Any添加自定义扩展函数,也就意味着任何类都可以使用该函数。示例代码如下:
data class TestClass1(val name:String)
fun Any.show(): Any {
println(this)
return this //可以实现链式调用
}
fun main() {
TestClass1("mekeater").show().show()
}
这点是不是超级牛,想象一下,如果在代码中用到了一个闭源的库,如果想要为该库插入一个和自己项目相关的代码片段,那你就可以直接对该库进行定义扩展函数了。
1.3 泛型扩展函数
kotlin不仅可以为类添加扩展函数,同时也可以为泛型添加扩展函数,这就更牛了,因为所有类型,都是泛型,那么为泛型添加的扩展函数,任何类型都可以调用。示例代码如下:
fun <T> T.showContent(){
if (this is String)
println("字符串长度为:$length")
else
println("不是字符串")
}
fun <I> I.showTime() = println("当前时间:${System.currentTimeMillis()}")
fun main() {
//所有类型,都是泛型
234.showContent()
"mekeater".showContent()
567.showTime()
}
运行结果如下:
1.4 基于泛型扩展函数实现let函数
前面的文章全网最全面的由浅到深的Kotlin基础教程(二)中我们讲过,let函数通过lambda的一个参数it,持有调用者,let函数的最后一行作为返回值。其实let就是通过泛型扩展函数实现的,如下示例代码,我们实现一个与let同样功能的mLet函数。
/**
* 泛型扩展函数详细解释如下:
* private 私有化
* inline 因为有lambda表达式,用于优化性能
* fun <I,O> 声明泛型,I代表输入类型,O代表输出类型
* I.mLet 对于I输入类型增加函数扩展
* block:(I) -> O mLet函数中需要传入的lambda表达式
* block(this) mLet函数内部实现,调用lambda表达式,传入的参数this代表I类型的对象本身
*/
private inline fun <I,O> I.mLet(block:(I) -> O) : O = block(this)
fun main() {
var mLet = 345.mLet {
"sun"
}
println(mLet)
}
运行结果如下:
kotlin let源码如下所示,可见与我们上面的实现方式一致。
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
//暂时不要关注该契约函数,去掉也不影响实现
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
1.5 可空类型扩展函数
对空指针情况的函数扩展,示例代码如下:
//可空的扩展函数,如果当前值为null,则返回扩展函数中的值,否则返回本身的值
fun String?.default(value:String) = this ?: value
fun main() {
var str: String? = null
println(str.default("你是null呀"))
str = "sun"
println(str.default("你是null呀"))
}
运行结果如下:
1.5 扩展属性
kotlin不仅可以实现对类或者泛型进行函数扩展,也可以实现属性扩展,示例代码如下:
//扩展String类中的属性myInfo
val String.myInfo:String
get() = "$this 扩展属性:mekeater"
fun main() {
val str:String = "sun"
println(str.myInfo)
}
运行结果如下:
1.7 infix关键字
通过infix fun定义中缀表达式,使得两个变量的操作函数,不需要用.这样的符号调用扩展函数,简化代码。示例代码如下:
//自定义中缀表达式
private infix fun <C1,C2> C1.go(c2:C2) {
println("参数1:$this,参数二:$c2")
}
fun main() {
//不使用infix关键字,只能通过下面的方式调用扩展函数
"sun".go("mekeater")
//使用infix关键字,也可以用过下面的方式调用扩展函数
"you" go "good"
}
运行结果如下:
kotlin中创建map对象也使用了中缀表达式to,实现源码如下,可见将A和B创建为一个Pair对象返回了
/**
* Creates a tuple of type [Pair] from this and [that].
*
* This can be useful for creating [Map] literals with less noise, for example:
* @sample samples.collections.Maps.Instantiation.mapFromPairs
*/
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
使用示例代码:
fun main() {
mapOf("s" to 0)
mapOf("u" to 1)
}
1.8 定义扩展文件
定义扩展文件,其实就是在一个指定的文件中,写大量的扩展函数,然后在其它地方通过导包使用,这样比较规范,如我们新建一个ExtFile.kt文件,专门写扩展函数,示例代码如下:
package com.kt.step5.com.ext
//定义扩展函数,获取随机值
fun<E> Iterable<E>.random():E{
return this.shuffled().first()
}
//定义扩展函数,获取随机值,并输出
fun<I> Iterable<I>.randomPrint(){
println(this.shuffled().first())
}
那么我们在使用这些扩展函数的时候,需要先导入该扩展函数文件,再使用,示例代码如下:
import com.kt.step5.com.ext.random
import com.kt.step5.com.ext.randomPrint
//TODO 定义扩展文件(其实就是在一个指定的文件中,写大量的扩展函数,然后在其它地方通过导包使用,这样比较规范)
fun main() {
var list = listOf("sun", "mekeater", 18)
var set = setOf("hello", true, 18)
//使用扩展文件中的扩展函数
println(list.random())
set.randomPrint()
}
示例运行结果如下:
1.9 通过as重命名扩展函数名
有时候别人写的扩展函数名字太长,我们可以用as给这些扩展函数起别名,示例代码如下:
//通过as重命名扩展函数名
import com.kt.step5.com.ext.random as r
import com.kt.step5.com.ext.randomPrint as rp
//TODO 重命名扩展函数
fun main() {
var list = listOf("sun", "mekeater", 18)
var set = setOf("hello", true, 18)
println(list.r())
set.rp()
}
示例运行结果如下:
2. kotlin注解
2.1 @file:JvmName
JvmName注解:通过JvmName注解可以修改kotlin自动生成的java代码类名,方便在java中调用kotlin的代码。可以通过查看反编译代码了解该注解的作用,示例代码如下:
Kt01.kt文件代码
@file:JvmName("Stu")
package com.kt.step6
fun getStudentName() = println("mekeater")
fun main() {
}
Kt01.kt反编译为java的部分代码,可以添加JvmName注解后,编译器自动生成了类名为Stu的类,因此可以在java中通过Stu调用kotlin的方法。
public final class Stu {
public static final void getStudentName() {
String var0 = "mekeater";
System.out.println(var0);
}
public static final void main() {
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
Kt01_java.java文件代码
package com.kt.step6;
public class Kt01_java {
public static void main(String[] args) {
Stu.getStudentName();
}
}
2.2 @JvmField注解
添加JvmField注解,此时kotlin转为java代码,不需要额外生成一个get函数,而是直接公开这个字段,java中直接当做属性直接调取,不需要再用get函数。
可以通过反编译为java代码,查看加上JvmField注解与不加的区别,一看就懂了。
kotlin示例代码如下:
package com.kt.step6
//TODO @JvmField注解。可以通过反编译为java代码,查看加上JvmField注解与不加的区别,一看就懂了
class Person{
val names = listOf("sun", "mekeater") //此时kotlin转为java代码会有一个get函数来拿这个值,因此在java中只能通过get函数拿这个属性
@JvmField
val age = listOf(18,19) //添加JvmField注解,此时kotlin转为java代码,不需要额外生成一个get函数,而是直接公开这个字段,java中直接当做属性直接调取,不需要再用get函数
}
fun main() {
}
java调用的示例代码如下:
public class Kt02java {
public static void main(String[] args) {
Person person = new Person();
for (String name : person.getNames()) {//没有添加JvmField注解,需要用get获取kotlin中的names属性
System.out.println(name);
}
for (Integer age : person.age) {//添加JvmField注解,直接当做属性调用,不要再用get函数
System.out.println(age);
}
}
}
2.3 @JvmOverloads注解学习
该注解将kotlin中的函数,带的默认参数值给java使用。
kotlin示例代码如下:
//此时在java中调用该函数,无法使用age的默认值,必须填写全部参数
fun show(name:String,age:Int = 18){
println("name=$name,age=$age")
}
//通过JvmOverloads注解,就可以实现java中使用age的默认值
@JvmOverloads
fun show1(name:String,age:Int = 18){
println("name=$name,age=$age")
}
java调用的示例代码如下:
public class Kt03_java {
public static void main(String[] args) {
Kt03Kt.show("sun",20);//无法使用默认参数
Kt03Kt.show1("mekeater");//可以使用默认参数
}
}
2.4 @JvmStatic注解学习
就是把kotlin的静态函数,共享给java使用。具体实现可以看反编译的java源码,一看就懂。
kotlin示例代码如下:
class Kt04{
companion object{
@JvmField
val name="mekeater"
//不添加@JvmStatic注解,java无法直接调用该函数,需要看反编译java源码,来实现调用。Kt04.Companion.show();
fun show() = println("name=$name")
//添加@JvmStatic注解,java可以直接调用该函数,无需再加一个Companion类
@JvmStatic
fun show1() = println("name:$name")
}
}
java调用的示例代码如下:
public class Kt04_java {
public static void main(String[] args) {
System.out.println(Kt04.name);
Kt04.Companion.show();//不添加@JvmStatic注解,必须通过Companion调用kotlin的静态函数
Kt04.show1();//添加@JvmStatic注解,可以直接调用
}
}
3. DSL编程范式
DSL(Domain Specified Language) 领域专用语言,定义输入输出等规则。
入门DSL编程范式示例代码:
//入门DSL编程范式的示例
class Context{
val info = "Context DSL"
val name = "sun"
fun toast(str:String) = println("弹出${str}提示")
}
/**
* 输入规则:必须Context类才能使用applyDSL, 同时持有this 和 it
* 输出规则:始终返回Context本身
*/
inline fun Context.applyDSL(lambda:Context.(str:String)->Unit):Context{
lambda(info)
return this
}
fun main() {
Context().applyDSL {
toast(it)
toast(this.name)
toast("mekeater")
}.applyDSL { }
}
运行结果如下:
4. kotlin调用java,及可空性
没什么特别好说的,直接看示例代码。
java示例代码如下:
public class Kt44_java {
public String getInfo1(){
return "mekeater";
}
public String getInfo2(){
return null;
}
}
kotlin调用java示例代码如下:
fun main() {
println(Kt44_java().info1.length)
//println(Kt44_java().info2.length) //会引发空指针crash
//采用下面的写法才是规范的,可以避免空指针异常
var info2 : String? = Kt44_java().info2
println(info2?.length)
}
运行结果如下: