kotlin 零散笔记

480 阅读8分钟

装箱

基本类型的数组 是没有自动装箱拆箱的过程,而list是有的 

对于基本类型使用,推荐用数组,性能会好点

kotlin装箱需要根据场合

对于 Int 这样的基本类型,尽量用不可空变量
var a: Int = 1 // unbox
var b: Int? = 2 // box    会转成  Integer.parse(2) 执行装箱
var list: List<Int> = listOf(1, 2) // box
满足以下条件就不会执行装箱
1 不可空类型
2 使用 IntArray FloatArray

数组写法

kotlin 数组不支持协变 就是子类数组对象不能赋值给父类的数组变量

在kotlin 数组的写法变成 泛型式写法   arrayOf("","","")

var array: IntArray = intArrayOf(1, 2) //这种是 unbox 的

kotlin 里用专门的基本类型数组类 (IntArray FloatArray LongArray) 才可以免于装箱拆箱。

元素不是基本类型时,相比 Array,用 List 更方便些

集合

支持协变 指的是:子类类型的list 可以赋值给父类的list

List 以固定顺序存储一组元素,元素可以重复。    ===>listOf("a", "b", "c") 支持协变

Set 存储一组互不相等的元素,通常没有固定 顺序.  ===>setOf("a", "b", "c")  支持协变

Map 存储 键-值对的数据集合,键互不相等,但不同的键可以对应相同的值。

var map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 3)
map.put("key1", 2)
map["key1"] = 2  
val value1 = map.get("key1")               
val value2 = map["key2"]
操作函数: forEach filter map flamap
惰性集合sequenceOf    只有使用的使用时候才会执行


用数组还是list

list相对于数组 功能更丰富,但是数组基本类型的数组是没有自动装拆箱过程,而list是有的,

对于基本类型  ,数组的性能会比list好. 

在kotlin ,要用专门的数组类才能免于自动装拆箱 (IntArray FloatArray LongArray)

可变集合/不可变集合

集合分为两种类型:只读的和可变的。这里的只读有两层意思:

  • 集合的 size 不可变
  • 集合中的元素值不可变

以下是三种集合类型创建不可变和可变实例的例子:

  • listOf() 创建不可变的 ListmutableListOf() 创建可变的 List
  • setOf() 创建不可变的 SetmutableSetOf() 创建可变的 Set
  • mapOf() 创建不可变的 MapmutableMapOf() 创建可变的 Map
不可变的 可以通过 toMutable*() 系函数转换成可变的集合,返回的是一个新建的集合,原有的集合还是不可变的,所以只能对函数返回的集合修改。


类构造和对象

主构造器的参数声明时加上 var 或者 val,就等价于在类中创建了该名称的属性
1 类默认是public final,   标识符 : 不仅可以表示继承,还可以表示 Java 中的 implement  基类Any

2 构造函数单独用了一个 constructor 关键字来和其他的 fun 做区分。

3 Kotlin 里的类默认是 final 的,是不可继承的  需要open 声明

4 override 是有遗传性的(如果要关闭 子类的方法写成final override机可),open 没有遗传性

5 open 与 abstract关键字区别,abstract修饰的类无法直接实例化,需要被继承

6 类型判断及强转 is 、as、as?(可空类型使用)

7 init 代码块是紧跟在主构造器之后,先于次构造器执行的   
 
8 把主构造器看成身体的头部,那么 init 代码块就是颈部,次构造器就相当于身体其余部分。

9 关键字var val const open constructor overrider abstract Any objcet 修饰的类是单例类 ,(对象申明 对象表达)单例饿汉式的单例,并且实现了线程安全。

10 修饰符

11 kotlin实现匿名类写法

val listener = object: ViewPager.SimpleOnPageChangeListener() {
    override fun onPageSelected(position: Int) {
        // override
    }
}   

public:公开,可见性最大,哪里都可以引用。
private:私有,可见性最小,根据声明位置不同可分为类中可见和文件中可见。
protected:保护,相当于 private + 子类可见。
internal:内部,仅对 module 内可见。
@hide 限制客户端访问

如果类中有主构造器,那么其他的次构造器都需要通过 this 关键字调用主构造器,可以直接调用或者通过别的次构造器间接调用。如果不调用 IDE 就会报错:

class User constructor(var name: String) {
    constructor(name: String, id: Int) {
    // 👆这样写会报错,Primary constructor call expected
    }
}

从主构造器的特性出发,一旦在类中声明了主构造器,就包含两点必须性:创建类的对象时,不管使用哪个次构造器,
都需要主构造器的参与第一性:

在类的初始化过程中,首先执行的就是主构造器如果把主构造器看成身体的头部,那么 init 代码块就是颈部,
次构造器就相当于身体其余部分。

如果在主构造器的参数声明时加上 var 或者 val,就等价于在类中创建了该名称的属性(property)
并且初始值就是主构造器中该参数的值。


常量

class Sample {
    companion object {
         👇                  // 👇
        const val CONST_NUMBER = 1
    }
}

const val CONST_SECOND_NUMBER = 2 编译期常量 

Kotlin 的常量必须声明在对象(包括伴生对象)或者「top-level 顶层」中,因为常量是静态的。

Kotlin 新增了修饰常量的 const 关键字。

Kotlin 中只有基本类型和 String 类型可以声明成常量。

val 只表示只读

object、companion object 、top-level

juejin.cn/post/684490… 这里单独作了笔记

如果写工具类 那就直接创建一个文件 在top-level层声明

如果只是针对类,而不是针对外部使用者  就可以写成 companion object 或者object

函数

  • 本地函数(嵌套函数)

fun login(user: String, password: String, illegalStr: String) {
           👇 
    fun validate(value: String, illegalStr: String) {
      if (value.isEmpty()) {
          throw IllegalArgumentException(illegalStr)
      }
    }
   👇
    validate(user, illegalStr)
    validate(password, illegalStr)
}
这里将共同的验证逻辑放进了嵌套函数 validate 中,
并且 login 函数之外的其他地方无法访问这个嵌套函数。
  • 返回Unit的函数
  • 返回Nothing函数
  • 单表达式函数
  • 中缀表示法
  • 成员函数
  • 局部函数(本地函数)
  • 泛型函数
  • 内联函数
  • 扩展函数
  • 高阶函数
  • 尾递归函数

kotlin内置关键字

any

data 数据类 自动生成getset

inner 内部类申明 区别于嵌套类是可以访问外部类成员

object

sealed

open 

constructor

require  

inline  

suspend 挂起提醒函数  在协程与withContext配套使用  
 
tailrec 搭配尾递归函数,函数的最后一行是函数调用的场景,是递归的一种特殊情形。尾调用不一定是递归调用

(待增加……)


命名参数

fun sayHi(name: String = "world", age: Int) {
    ...
}
      👇   
sayHi(age = 21) 

在调用函数时,显式地指定了参数 age 的名称,这就是「命名参数」。

Kotlin 中的每一个函数参数都可以作为命名参数。


位置参数

1 与命名参数相对的一个概念被称为「位置参数」,也就是按位置顺序进行参数填写。fun sayHi(name: String = "world", age: Int, isStudent: Boolean = true, isFat: Boolean = true, isTall: Boolean = true) {
    ...
}
调用 :sayHi(name = "wo", age = 21, isStudent = false, isFat = true, isTall = false)

2 如果函数混用位置参数与命名参数,那么所有的位置参数都应该放在第一个命名参数之前fun sayHi(name: String = "world", age: Int) {
    ...
}
sayHi(name = "wo", 21) // IDE 会报错,Mixing named and positioned arguments is not allowed
sayHi("wo", age = 21) // 这是正确的写法


?. 和 ?:

val str: String? = "Hello"
                             👇
val length: Int = str?.length ?: -1
意思是如果左侧表达式 str?.length 结果为空,则返回右侧的值 -1

Elvis 操作符还有另外一种常见用法

fun validate(user: User) {
    val id = user.id ?: return // 👈 验证 user.id 是否为空,为空时 return 
}

// 等同于

fun validate(user: User) {
    if (user.id == null) {
        return
    }
    val id = user.id
}


泛型通配符

定义:带上界和下界的通配符?      与类型上界不是一个东西  T extends Object

区别:?通配符上下界 一个是能不能读我或能不能写我,   类型上界只是限制类型范围

不支持协变:子类的list泛型是不能赋值给父类list的泛型,引发一个类型擦除概念由于有类型擦除存在,保证类型
的安全,java给泛型设置了这种限制

kotlin也有以上的限制 ,由此  引发出泛型通配符,解决了限制  达到协变

java:
List<? extends TextView>   返回的类型对象 能使用get类似的     但不能set值
List<? super Button>       返回的类型对象 不能使用get一类的   但能set值 

kotlin里面变成 :
List<out TextView> out表示 这个参数 只用来输出,不用来输入,只能读我 不能写我
List<in Button>    in 表示 这个参数 只用来输入,不用来输出,只能写我,不能读我

还可以用在泛型的类型参数上  interface Proucer<out T> {}
泛型的继承 在kotlin 是  <T> where T :    (一个以上继承就需要where关键字)
  • 使用关键字 out 来支持协变,等同于 Java 中的上界通配符 ? extends
  • 使用关键字 in 来支持逆变,等同于 Java 中的下界通配符 ? super

Lambda

Kotlin的lambda表达式始终用花括号包围,花括号中通过 箭头(->)将实参和函数体分开,

lambda语法结构:

{x:Int, y:Int -> x+y }