oynix

于无声处听惊雷,于无色处见繁花

Kotlin基础

今天看了篇文章,写点东西做个总结,也算是没有白花时间。

原文地址在这

相对于写惯了Java的人来说,Kotlin算是相当友好的,因为在看完基础语法之后,就完全可以按照写Java的习惯来写Kotlin,并且可以达到一样的效果,这还不算友好吗,这可以算是相当友好。但是,Kotlin还是有着自己一些独特的特点,可以不用,但是了解还是要了解的,相同的就不再啰嗦,这里就把不一样的抽出来说一说。

1. 嵌套类和内部类

在Java里,在类内部声明的类叫做内部类,内部类默认持有外部类的引用,例如,可以在内部类里调用外部类的方法,原因就是持有外部类的引用,自然可以调用其方法,除此之外,通过外部类的实例,才能创建内部类的实例,原因也是在此。要想解除这种引用,那么就要用static修饰,使其变成静态内部类,此时便不再持有外部类的引用,与普通的类无异,唯一的区别就是全类名中含有外部类的名字,依此来定位到自己。

在Kotlin里,大致也是如此,依然是内部类默认持有外部类的引用,但是,声明内部类时需要用inner关键词修饰class,使其变成内部类方可。当没有inner修饰时,和Java中的静态内部类等同,不持有外部类的引用。

平常叫惯了内部类,查了查发现这样是不严谨的,严格来说是叫嵌套类,和静态嵌套类,Static Nested Class。

2. lambda调用外部临时变量

Java7之前,匿名内部类要想使用外部临时变量,必须是final的,到了Java8,引入了lambda,在匿名内部类和lambda中使用外部的临时变量时,可不加final,但是依然不能修改。原因在于,内部类会被编译成一个新的类,其中保留外部类的引用,以及临时变量的副本,所以内部类修改的是变量的副本,这就会造成歧义,所以禁止修改。但这种问题只局限于基本类型变量,如果是引用型变量,就没有这种问题,引用的地址不变,但其指向的内存的数据是可变的。

但是,在Kotlin中,lambda外部的基本类型临时变量也能引用并且可以修改,这就和Java有些不同了。非内联的lambda在编译后会生成一个内部类,这一点同Java,但是对于基本类型也会自动包裹一层,kotlin提供了Ref类,例如IntRef、BooleanRef等,这样基本类型就转成了引用类型,所以也可以在内部类中也可以修改。

3. let/run/apply/also

还是这4个方法,之前写了一篇文章专门介绍,发现分析地再细致,对比的越全面,等到用的时候,还是容易一脸懵:这个和这个的区别是什么来着?现在想了个新角度,如果需要返回自身,就用apply和also,返回lambda的值就用let和run。这样好记了吗,还不好记吗,那就来个更简单的,3个字母的返回lambda,剩下的返回调用者本身。至于lambda内部的参数,不是this就是it,试一下就知道了。

4. 操作符

kotlin里面提供了好大一堆操作符,都是为了方便日常开发使用,要是能记住,用的时候可以少写几行代码,记不住也没关系,就自己写呗,也没几行。这里列举几个出来:

  • any:list.any,有一个符合就返回true,any就是任一的意思,与之相反的是none
  • all:同上,但相反,必须都符合
  • count:统计符合条件的数量
  • flod:带初始值,叠加,flodRight,是从最后一项开始;reduce不带角标,reduceRight从最后开始
  • forEach:不多说,forEachIndexed,带角标遍历
  • max:最大,maxBy,指定对比值,min和minBy同理
  • sumBy:求和
  • drop:丢掉前n个元素,dropWhile,碰到符合条件后则将剩下的返回,dropLastWhile,从后开始
  • filter:过滤,filterNot,取反,filterNotNull,滤掉空
  • silce:指定角标
  • take:拿前多少个,takeLast,从后面开始拿,takeWhile,返回false时停止拿
  • map:映射
  • flatMap:展开
  • zip:返回由Pair组成的list,unzip将pair list转成Pair([],[])

5. 委托

在kotlin使用委托在代码上简化了不少,使用by关键字即可

  • 接口委托,InterfaceImple虽然实现了接口,但是都是通过调用agent的方法
    1
    2
    interface InterF {}
    class InterfaceImpl(val agent: InterF) : InterF by agent
  • 属性委托,将属性委托给一个有getValue和setValue方法的类,val只有get没有set
    1
    2
    3
    4
    5
    6
    7
    class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {}

    operator fun setValue(thisRef: Any?, property: KPropergy<*>, value: String) {}
    }

    var strValue: String by Delegate()
  • 延迟委托,一般使用lazy方法,也可以自己写一个方法,返回Lazy<T>即可,在第一次调用get时,会调用初始化方法
    1
    2
    3
    4
    5
    6
    // LazyThreadSafeMode.SYNCHRONIZED,同步锁
    // LazyThreadSafeMode.PUBLICATION,多线程
    // LazyThreadSafeMode.NONE,不关心
    val lazyValue: String by lazy {
    "hello world"
    }
  • 可观察属性,Delegates.observable(initValue, {}),在调用set时会被调用
    1
    2
    3
    4
    5
    class Example {
    var age: Int by Delegates.observable(-100) { kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
    println("kProperty.name: ${kProperty.name} , oldValue: $oldValue , newValue: $newValue")
    }
    }
  • 拦截属性值,Delegates.vetoable(initValue, {})
    1
    2
    3
    4
    5
    6
    class Example {
    var age: Int by Delegates.vetoable(-100) { kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
    println("kProperty.name: ${kProperty.name} , oldValue: $oldValue , newValue: $newValue")
    age <= 0 //返回true 则表示拦截该赋值操作
    }
    }
  • 把属性存储在映射中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    fun main() {
    val student = Student(
    mapOf(
    "name" to "leavesCZY",
    "age" to 24
    )
    )
    println(student.name)
    println(student.age)
    }

    class Student(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
    }
------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2022/04/470a9330a8ff/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道