全网最全面的由浅到深的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)
}

运行结果如下:
在这里插入图片描述

  • 12
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mekeater

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值