Kotlin 学习笔记(三)函数的调用、中缀、解构、顶层扩展函数 使用

Kotlin 学习笔记(三)函数的调用、中缀、解构、顶层扩展函数 使用_第1张图片
PineLogSP_ZH-CN1105763820_1920x1080.jpg

前言
本文章只是用于记录学习,所以部分地方如果有错误或者理解不对的地方,麻烦请指正。本篇为 csdn 原文章 转移修改版 原文章

Kotlin 学习笔记(二)

简述:

  1. kotlin 中集合如何简单的创建
  2. kotlin 中函数如何定义 及 更好、更明白的调用
  3. kotlin 中顶层函数 和顶层属性的简单使用
  4. kotlin 中扩展函数 和 扩展属性的讲解
  5. kotlin 中中缀 方法的使用 和 简单了解
  6. kotlin 中解构 使用

1. 集合

    // set
    val set = hashSetOf(1,2,3)
    // list
    val arr = arrayListOf(2,3,4,5)
    // hashmap
    val map = hashMapOf(1 to "A",2 to "B")

   上述代码 展示的是一半的 set , arraylist , hashmap 三种数据结构的创建方法,kotlin 中的 集合部分都是沿用了 java 的 集合类,在java 的基础上包裹了一层,更方便开发者的调用 和 创建。在java 的基础上 还进行了一部分api 的封装。

// 获取数组中最后一个
arr.last()
// 获取数组中最大的一个
arr.max()

   为了 方便开发人员,更好的知道函数的用法,我们也可以在 方法头部添加注释,包括方法名称的定义和参数 名称的设置,合理的设置,可以提升代码的阅读力,当然,kotlin也为我们做了一些事情

fun getColor(color :Color){
    when(color){
        Color.BLUE ,Color.RED -> println("this is blue or red")
        Color.GREEN -> println("this is GREEN")
    }
}

// 普通调用
getColor(Color.GREEN)
// 优化后的调用
getColor(color = Color.GREEN)

   如果你是开发人员,在看着 第二种调用方法,会更加简单明了的知道这个参数是干什么的 ,我们应该怎么传,but,如果我们在调用的时候 指明了参数的名称,为了避免混淆,后边的参数也有点要 指明 参数名称。

2.函数定义、顶层函数 和属性

2.1 函数的定义 及调用

1、函数的定义使用关键字"fun",函数参数格式为: "参数:类型",函数返回值类型 "fun(...): Int"

fun sum(a: Int, b: Int, c: Int): Int {
    return a + b + c
}

2、表达式作为函数体,返回值的类型可以省略,可以利用Kotlin的类型推导功能,推测出函数返回值的类型。

fun sum(a: Int, b: Int, c: Int) = a + b + c

3、无返回值的函数(类似Java中的void空类型返回值)

// Unit 也可以省略不写

fun printSum(a: Int, b: Int, c: Int): Unit {
    println(a + b + c)
}

4、可变长参数函数可以使用 "vararg" 关键字标识类似Java中的public void setData(Object... objects)。

fun vars(vararg args: Int) {
    for (arg in args) {
        print(arg)
    }
}

5、lambda(匿名函数)

val sumLambda: (Int, Int, Int) -> Int = { a, b, c -> a + b + c }

6 函数调用

在Kotlin函数中有这么一种参数叫做命名参数,它能允许在函数调用的地方指定函数名,这样就能很好使得调用地方的参数和函数定义参数一一对应起来,不会存在传递参数错乱问题。

println(printSum(a=1,b=2,c=3))

AndroidStudio3.0带命名参数的函数调用高亮提示更加醒目

 2.2 顶层函数

   该节点我们先使用两段代码,就很好的了解顶层函数说的什么意思了。
main

import util.*

fun main(args: Array) {
    HelloWorld()
}

Util

package util

fun HelloWorld(){
    println("hello")
}

   看完上边两个代码块,我们就大致了解 顶层函数,类似于 我们java 中的 Util类,我们在main 中通过 依赖util .* ,就可以直接在类中使用 util中的方法,并且不用想java 中的 还有前边加上 类名.方法名 来调用。

java的Util 情况。相对于我们写过的项目,不管大小,都会有一个Util文件将爱专门放一些 静态函数,我们一个界面或者 一个Model 里边都会引用很多其他地方的类。
Kotlin 中的 顶层函数:根本不需要创建那么多类,我们可以继续写util 类,但是我们直接把函数放在文件的顶层,这样不用从属与任何的类,并且还是 该类中的一部分,如果在包外,我们就可以通过 import 的方式引入,并且外边也不用包一层。

2.3 关于顶层函数实质原理

要想知道内部调用原理很简单,我们只需要把上面例子代码反编译成Java代码就一目了然了。这里科普一下反编译Kotlin代码步骤,因为这是查看Kotlin语法糖背后实质很好的方法。

步骤一: 确认IDE安装好了Kotlin Plugin
步骤二: 在IDE中打开你需要查看反编译的代码文件,然后打开顶部的"Tools",选择"Kotlin",再选择"Show Kotlin ByteCode"


Kotlin 学习笔记(三)函数的调用、中缀、解构、顶层扩展函数 使用_第2张图片
在这里插入图片描述

步骤三: 左边是Kotlin的源码,右边是Kotlin的ByteCode


Kotlin 学习笔记(三)函数的调用、中缀、解构、顶层扩展函数 使用_第3张图片
在这里插入图片描述

步骤四: 点击右侧“Decompile”
Kotlin 学习笔记(三)函数的调用、中缀、解构、顶层扩展函数 使用_第4张图片
在这里插入图片描述

通过以上的代码可以总结出两点内容:

1、顶层文件会反编译成一个容器类。(类名一般默认就是顶层文件名+"Kt"后缀,注意容器类名可以自定义)
2、顶层函数会反编译成一个static静态函数,如代码中的formateFileSize和main函数

对于Java中如何调用Kotlin中的顶层函数了吧。调用方式很简单,就是利用反编译生成的类作为静态函数容器类直接调用对应的函数

System.out.println("文件大小: " + FormateFileKt.formateFileSize(1111));// Java中调用Kotlin中定义顶层函数,一般是顶层文件名+"Kt"后缀作为静态函数的类名调用相应函数

2.4 顶层函数名称修改

Kotlin中的顶层函数反编译成的Java中的容器类名一般是顶层文件名+“Kt”后缀作为类名,但是也是可以自定义的。也就是说顶层文件名和生成容器类名没有必然的联系。通过Kotlin中的@file: JvmName("自定义生成类名")注解就可以自动生成对应Java调用类名,注意需要放在文件顶部,在package声明的前面

@file: JvmName("FormateUtil")
package com.ymc.kotlindemo

import java.math.BigDecimal

fun formateFileSize(size: Double): String { ... }

我们通过同样的方法看下 kotlin 会生成怎么的文件内容


Kotlin 学习笔记(三)函数的调用、中缀、解构、顶层扩展函数 使用_第5张图片
在这里插入图片描述

这样Java调用自定义类名顶层函数就更加自然,一般建议使用注解修改类名,这样在Java层调用还是我们习惯工具类的命名,完全无法感知这个函数是来自Java中还是Kotlin中定义的,做到完全透明。

System.out.println("文件大小: " + FormateUtil.formateFileSize(1111));

2.5 顶层属性

   和 顶层函数 相似,属性也是可以放在文件的顶层

BUtil

var index = 0
const val indexx = 0

fun todoSomething(){
    index ++
}

main

import util.indexx

fun main(args: Array) {
    println(indexx)
}

   和顶层函数一样的,不过这里学到了新的关键词 const ,他等同于 java 中的 public static final ,因为不可变,所以 const val 标配。

   总结:使用顶层函数和属性从此消除Java中的static、中缀表达式调用和解构声明等。

3.扩展函数和属性

扩展部分 算是kotlin 的一大特色,扩展函数 扩展属性 同理,其实 他就是成员内的函数或者 属性,但是写在了 成员外部。

 3.1 扩展函数

   只需要把扩展的类或者接口名称,放到即将要添加的函数名前面。这个类或者名称就叫做接收者类型,类的名称与函数之间用"."调用连接。this指代的就是接收者对象,它可以访问扩展的这个类可访问的方法和属性。

// 扩展 函数
fun String.lastData() : Char{
    return this.get(this.length-1)
}


// 获取最后一个单词
import util.lastData

println("Kotlin".lastData())

   从上边代码中,我们可以看出 我们想要扩展的类 或者接口名称,添加在函数名称的前面 ,这个 类的名称 称为 接收者类型,所以在本例中 String 就是接收者类型,而 “kotlin” 就是接收者对象,其实我们这样写,就相当于给String 添加了一个方法。但是扩展函数是 不希望你打破 原有类的封装性的,所以扩展函数是无法 访问到 私有的或者受保护的成员

   注意: 一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、相同的名字并且参数都没有 或者参数类型都一样,这种情况总是取成员函数。(成员函数优先级高

class C {
    fun foo() { println("member") }
}

fun C.foo() { println("extension") }
// 如果这里调用 foo 方法 就会打印 member。

// 如果这里 调用 C().foo(1) ,就会打印 extension。(前提是参数不一样)
fun C.foo(i: Int) { println("extension") }

使用的话 记得引用你写的扩展函数地址就可以了 (import util.lastData)
如果你写的函数比较多的话,也可以 import util.*
如果想要动态的换一个名字 (import util.lastData as lastD),这样的话 引用就需要 println("Kotlin".lastD())

 3.2 从java 中调用扩展函数

   如果我们的 扩展函数所在 文件位置在 Func.kt ,如果在java 中调用

Char c = Func.lastData("Kotlin")

扩展函数的静态性质,也决定了不可以被子类重写。

  3.3 不可以被重写的 扩展函数

open class View{
    open fun click(){
        print("view")
    }
}

class Button : util.View() {
    override fun click() {
        print("Button")
    }
}

// 探讨 扩展函数 的不可继承
fun View.shutOff(){
    println("view shut off")
}

fun Button.shutOff(){
    println("Button shut off")
}

// 调用语句
val view : View = Button()
view.shutOff()

输出语句

view shut off

   扩展属性的基本使用 扩展属性实际上是提供一种方法来访问属性而已,并且这些扩展属性是没有任何的状态的,因为不可能给现有Java库中的对象额外添加属性字段,只是使用简洁语法类似直接操作属性,实际上还是方法的访问。
   扩展函数 并不算是类的一部分,他是声明在类之外的,这里的函数调用 取决于 该变量的静态类型所决定的,而不是编译时 的数据类型所决定的。
所以 扩展函数是不可以 继承的,因为kotlin 是 将他当做静态函数来看待的

如果扩展函数 和 成员函数 签名一样,调用的时候会优先使用 成员函数。

4.扩展属性

例子 1

var StringBuffer.lastDataString : Char
    get() {
       return this.get(this.length -1 )
    }
    set(value) {
        this.setCharAt(this.length-1 ,value)
    }

   和扩展函数差不多,不过扩展属性一定要写 getter 方法,因为没有默认字段,所以没有默认的getter方法,同理,初始化也不可以,因为没有存储地址。

例子2

inner class CButton {
        var b: Int = a
        val MeButton.dd: Int
            get() = 1
    }

如果 不添加get 方法,编译器 也会给我们报错 “extension property cannot be initialized because it has no backing field” 。

5. 扩展函数及属性的原理

  1. 通过对文章全边的阅读,我们这里还是使用 同样的方法 阅读 kotlin code
// 以这个扩展函数为例
fun View.shutOff(){
        println("view shut off")
    }

以下为 kotlin code 转换后的代码

public final void shutOff(@NotNull View $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      String var2 = "view shut off";
      System.out.println(var2);
   }

  总结:扩展函数实际上就是一个对应Java中的静态函数,这个静态函数参数为接收者类型的对象,然后利用这个对象就可以访问这个类中的成员属性和方法了,并且最后返回一个这个接收者类型对象本身。这样在外部感觉和使用类的成员函数是一样的。

  1. 接下来我们看下扩展属性。 扩展属性实际上就是提供某个属性访问的set,get方法,这两个set,get方法是静态函数,同时都会传入一个接收者类型的对象,然后在其内部用这个对象实例去访问和修改对象所对应的类的属性。
// kotlin code

public final char getLastDataString(@NotNull StringBuffer $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return $receiver.charAt($receiver.length() - 1);
   }

   public final void setLastDataString(@NotNull StringBuffer $receiver, char value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.setCharAt($receiver.length() - 1, value);
   }

总结:

  1. 扩展函数和成员函数使用方式类似,可以直接访问被扩展类的方法和属性。(原理: 传入了一个扩展类的对象,内部实际上是用实例对象去访问扩展类的方法和属性)
  2. 扩展函数不能打破扩展类的封装性,不能像成员函数一样直接访问内部私有函数和属性。(原理: 原理很简单,扩展函数访问实际是类的对象访问,由于类的对象实例不能访问内部私有函数和属性,自然扩展函数也就不能访问内部私有函数和属性了)
  3. 扩展函数实际上是一个静态函数是处于类的外部,而成员函数则是类的内部函数。
  4. ]父类成员函数可以被子类重写,而扩展函数则不行

6. 可空接收者

fun Any?.toString(): String? {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}

  上段代码,我们看到 在函数方法名 后边和 返回值后边都有 “?” ,kotlin 中问号表示可以为空,所以我们代码中要对 null 进行判断。

7. 扩展声明为成员

  在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个 隐式接收者 —— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为 分发接收者,扩展方法调用所在的接收者类型的实例称为 扩展接收者.(虽然我也不懂字面意思 但是看完代码就懂了)

class D {
    fun bar() { …… }
}

class C {
    fun baz() { …… }

    fun D.foo() {
        bar()   // 调用 D.bar
        baz()   // 调用 C.baz
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}
class C {
    fun D.foo() {
        toString()         // 调用 D.toString()
        [email protected]()  // 调用 C.toString()
    }
}

8. 中缀语法

先来看一组代码

//普通使用字符串对比调用StringUtils.equals(strA, strB)
fun main(args: Array) {
    val strA = "A"
    val strB = "B"
    if (StringUtils.equals(strA, strB)) {//这里对比字符串是了apache中的StringUtils
        println("str is the same")
    } else {
        println("str is the different")
    }
}

//利用中缀调用sameAs对比两个字符串
fun main(args: Array) {
    val strA = "A"
    val strB = "B"
    if (strA sameAs strB) {//中缀调用 sameAs
        println("str is the same")
    } else {
        println("str is the different")
    }
}

通过 中缀 语法 可以节省 部分代码的 书写 (类 + “.”+ 方法 的调用方式的省略)

再来看一组

//普通调用集合contains方法判断元素是否在集合中
fun main(args: Array) {
    val list = listOf(1, 3, 5, 7, 9)
    val element = 2
    if (list.contains(element)) {
        println("element: $element is into list")
    } else {
        println("element: $element is not into list")
    }
}

//利用中缀调用into判断元素是否在集合中
fun main(args: Array) {
    val list = listOf(1, 3, 5, 7, 9)
    val element = 2
    if (element into list) {
        println("element: $element is into list")
    } else {
        println("element: $element is not into list")
    }
}

中缀调用,这样的写法,会更加接近我们自然语言的表达,更容易理解

1、前面所讲into,sameAs实际上就是函数调用,如果把infix关键字去掉,那么也就纯粹按照函数调用方式来。比如 1.to("A"), element.into(list)等,只有加了中缀调用的关键字infix后,才可以使用简单的中缀调用例如 1 to "A", element into list等
2、并不是所有的函数都能写成中缀调用,中缀调用首先必须满足一个条件就是函数的参数只有一个。然后再看这个函数的参与者是不是只有两个元素,这两个元素可以是两个数,可以是两个对象,可以是集合等。

9 解构 使用

解构声明实际上就是将对象中所有属性,解构成一组属性变量,而且这些变量可以单独使用,为什么可以单独使用,是因为每个属性值的获得最后都编译成通过调用与之对应的component()方法,每个component()方法对应着类中每个属性的值,然后在作用域定义各自属性局部变量,这些局部变量存储着各自对应属性的值,所以看起来变量可以单独使用,实际上使用的是局部变量。

我们先来举个例子

// data class 是重点

data class Student(var name: String, var age: Int, var grade: Double)

---------------

fun main(args: Array) {
    val student = Student("mikyou", 18, 99.0)
    val (name, age, grade) = student//将一个student对象解构成一组3个单独的变量
    println("my name is $name , I'm $age years old, I get $grade score")//解构后的3个变量可以脱离对象,直接单独使用

}

很纳闷怎么可以这么写?我们这里通过 kotlin code 看下 真面目

public final class DestructTestKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      Student student = new Student("mikyou", 18, 99.0D);
      String name = student.component1();//返回对应就是Student中name属性
      int age = student.component2();//返回对应就是Student中age属性
      double grade = student.component3();//返回对应就是Student中属性
      
      //注意: 这里单独使用的name, age, grade实际上是局部变量
      String var6 = "my name is " + name + " , I'm " + age + " years old, I get " + grade + " score";
      System.out.println(var6);
   }
}

当然 我们也可以 部分解构,不需要全部解构对象,我们可以使用 “_” 符号表示不解构。

val (_, age, grade) = student//下划线_ 忽略name属性

当然我们也可以不加 任何东西 省略掉 name,这样也是可以的

val (age, grade) = student//直接不写name属性

注意点:解构声明的对象类型一定是data class,普通的class是不会生成对应的component的方法。

Kotlin 学习笔记(四)

你可能感兴趣的:(Kotlin 学习笔记(三)函数的调用、中缀、解构、顶层扩展函数 使用)