Kotlin进阶

Kotlin进阶

集合创建

Kotlin没有自己的集合API而是直接调用Java原有的集合,这样有两个好处,一:对Kotlin开发人员来说不用去”重复造轮子“;二:对使用者来说,节省了学习成本。

  • 普通集合

    • 我们知道在Java中初始化一个集合通常分为两步:创建,赋值。咋一看是没什么毛病的,但是在我们想要创建一个已知固定的集合时就会很麻烦,因为不能像数组一样在初始化的同时给集合赋值。Kotlin中优化了这个操作,使得我们可以像创建一个数组一样初始化一个含有默认值的集合。
    //Java
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    
    //Kotlin
    val list = listOf(1,2,3)
  • 键值对集合

    • 如此对于含有键值对的集合,如:map。也提供了更加方便的写法。
     val map = mapOf(1 to "one",2 to "two",3 to "three")
    • 通过to可以直接生成一个Pair对象,也就是键值对对象。在这里to并不是一个特殊的结构,而是名叫中缀函数的普通函数,关于中缀函数稍后会详细介绍。

中缀函数

普通函数的使用方法都是通过对象.函数名实现的,在kotlin中为了方便我们的使用引入了另外一种函数类型——中缀函数。这种函数通过infix关键字定义,有且只有一个参数,通过放在对象之后直接使用,在函数名之后紧接着的是该函数传入的值。中间无需.()等特殊符号链接。

//中缀函数
infix fun String.getChar(position: Int) = this.get(position)
val s = "Hello world"
val c = s getChar 1
//普通函数
fun String.getChar(position: Int) = this.get(position)
val s = "Hello world"
val c = s.getChar(1)

顶层函数和属性

Java中本着”万物皆为对象“的设计原理,所有的函数和属性都是依附于class来实现的,因此很多独立的工具方法我们不得不为其创建一个类来进行管理,也就是我们常说的工具类。这样的实现其实是有些奇怪的,但是在Kotlin编程里为我们解决了这样的烦恼。

  • 顶层函数

    • 我们可以在任何一个普通的.kt文件中声明我们想要的函数,并且我们不需要为该函数创建包装类,该函数就是函数本身(有点拗口),就和我们C语言一样,以函数为基本单位而不是类。
    package com.example.xxx.kotlinapplication
    
    fun log(message: String?) {
        Log.i("TAG", message)
    }
    • 是不是感觉身心都变得舒服了,但其实因为最终会编译成Java字节码,所以实现代码其实是这样的:
    package com.example.xxx.kotlinapplication
    
    public class XXX() {
        public static void log(String message) {Log.i("TAG", message)}
    }
    • 看到这里你可能发现了,其实从实现原理上顶层函数只是方便了我们程序员的编写,而实际上还是和普通的静态方法一样。
  • 顶层属性

    • 和顶层函数类似,可以用于存储一些固定的URL等等。

拓展函数和属性

在Java中”万物皆为对象“的思想下,所有的函数和属性都是基于class存在的,但是很多类都是由JDK或者别人封装好的,这时候我们如果需要对其进行拓展的话就必须对其进行extend,这无疑加大了我们架构的复杂性和耦合性。为了解决这种现状,kotlin引入了拓展函数和属性的概念。

  • 拓展函数

    • 作为kotlin的一大特色,拓展函数可以在任何地方为已有类进行函数的拓展,拓展之后的函数使用方法和原声函数方法一致

    • 以我们最常用的Toast为例,生成一个Toast需要我们至少传入三个参数,其中最关键的就是Context上下文对象,因为创建窗口需要建立在上下文的基础上才能完成,因此我们通常需要写一个Toast的工具来来方便我们使用,但是有了拓展函数之后就能在任意Context子类下直接调用我们拓展的方法。

    //定义拓展函数
    fun Context.toast(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_LONG).show()
    }
    //点击监听
    fun onclick(){
      toast("点击了")
    }
  • 拓展属性

    • 和拓展函数类似,可以为已存在的类拓展属性,并且属性同样是可以设置其具体的settergetter的。
    var StringBuilder.lastChar: Char
        get() = get(length - 1)
        set(value) {
            this.setCharAt(length - 1, value)
        }
    
    val name = StringBuilder("name")
    name.lastChar = 'o'
    log("lastChar:${name.lastChar} name:$name")
    
    打印结果:lastChar:o name:namo
  • 注意

    • 实际上”拓展“的原理是声明出对应的静态属性和函数,比如拓展函数就是声明一个静态的函数然后将拓展对象作为参数传入。

    • 因此,拓展的函数是不能被重写的,说的具体点拓展的函数是不满足多态原理的。例如:

    open class Father
    
    class Son : Father()
    
    fun Father.onClick() {
        log("I'm Father")
    }
    
    fun Son.onClick() {
        log("I'm Son")
    }
    //初始化两个Son对象
    val father: Father = Son()
    val son: Son = Son()
    //调用
    father.onClick()
    son.onClick()
    //输出结果:
    //father: I'm Father  
    //son: I'm Son
    • 如上,如果按照我们面向对象里氏替换原则,两个对象其实都是Son的对象,只是一个是通过父类的引用指向,而另一个是类本身的引用,因此输出结果都应该是”I’m Son“。但是因为拓展函数是实现原理是基于静态函数的,所以不符合面向对象的规则。简单说就是,它不管最终生成的是什么对象,它只关心系统识别出来是什么对象,也就是会按照引用的类型来调用。

局部拓展函数

评价一段代码的优劣最直接的方法就是看它有没有重复的代码。但是在我们正常编码中,经常会在一个小功能中遇到重复的小片段代码,如果将这段代码封装成方法会则会使得整个项目(类)变得臃肿,降低了代码的阅读性和精简性。在Kotlin中可以有更优雅的解决方案,就是局部拓展函数。

  • 局部拓展函数用直白的话来说就是:可以在函数中定义函数,并且调用。

  • 大致用法和内部类相似,可以直接在某个函数中再定义一个内部函数,内部函数中同样能定义内部函数,并且该内部函数只能在内部被调用,不像内部类可以在别的地方被单独调用。

  • 同时内部函数可以像内部类一样直接访问其附属函数所能访问的所有参数和变量。

  • 如下:

    val User(name:String,address:String)
    fun (user:User){
    if(user.name.isEmpty){user.name = "匿名"}
    if(user.address.isEmpty){user.address = "未知"}
    }
    
    //使用局部拓展函数
    val User(name:String,address:String)
    fun (user:User){
      fun validate(value:String,hint:String){
        if(value.isEmpty){value = hint}
      }
    validate(user.name,"匿名")
    validate(user.address,"未知")
    }

函数默认值

在Java中经常会因为某个函数需要传入的参数数量不同而需要写很多同名的参数,也就是我们熟悉的方法的重载。那如果我们的参数是可以设置默认值,并且可以选择传入多少的话,就可以节省很多重载的方法了。

  • 在Kotlin的函数规则中,可以为每个参数设置一个默认值,当没有传入该参数的时候函数就会以默认值进行编译。

  • 必须按照函数声明中的定义的参数顺序来给定参数,可以省略的只有排在末尾的参数。

    fun addUser(name: String = "无名", age: Int = 100, sex: Char = '男', id: Int = -1) {}
      addUser()
      addUser("张三")
      addUser("李四", 10)
      addUser("王五", 10, '男', 1)
  • 如果使用命名参数就可以指定你要设置的任何参数。

      addUser(age = 5, id = 10)
      addUser("赵六",sex = '妖')

命名参数

在我们查看他们代码和自己以前写的代码时,常常会遇到这样的问题,那就是不知道某个函数传入的参数到底是什么东西,遇到这种情况我们就只能点进去查看方法的详细情况才能知晓。命名参赛可以帮我们友好的优化这么问题。

  • 命名参数可以在我们调用某个方法时显式的写出我们传入的参数名以及对应值,这样能提高我们代码的可阅读性。

    fun test(key:Int,value:String)
    test(key = 1,value = "one")

    但其实在Android Studio中编辑器已经为我们实现了类似的功能,能够让我们在阅读代码时直接看到对应函数参数的命名。

  • 既然借助编辑器已经能够实现优化阅读的效果,那为什么还要引入命名参数呢?其实之前我也不知道,只觉得这是个鸡肋功能,直到我学会了函数默认值的用法,如上,当使用函数默认值时,本来是只能通过参数顺序进行参数传入和缺省,但是有了命名参数后可以完全忽略参数位置,直接赋值给你想要的任何参数。

    fun addUser(name: String = "无名", age: Int = 100, sex: Char = '男', id: Int = -1) {}
      addUser(age = 5, id = 10)
      addUser("赵六",sex = '妖')

猜你喜欢

转载自blog.csdn.net/ccw0054/article/details/79045504