Android Kotlin学习笔记(未完成)

一、声明及初始化

1、类

1)创建

类的声明由类名、类头(指定参数类型、主构造函数、继承实现关系)、类体(花括号及其中的变量、函数)构成,类头、类体都是可选,如果没有可以省略,如果主构造函数如果没有其他注解或可见修饰符,‘ constructor’关键字也可以省略

  • 声明一个普通类
    1
    2
    class Test{
    }
  • 声明及继承类/接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //普通继承
    class MainActivity: AppCompatActivity(){
    }
    //普通实现
    class User: Serializable {
    }
    //普通继承、实现
    class User : ViewModel(), Serializable {
    }
    //继承类、实现接口,以及带主构造函数
    class User(var id: Int, var name: String) : ViewModel(),Serializable {
    }

差别:

  • 默认即为public公开,所以可以省略public关键字;
  • 使用":"代替"extends""implemenet"关键字;
  • 自带初始化函数init{},作为类初始化过程、主构造函数的一部分,但不代表构造函数,也不能管理传入参数

2)构造函数

  • 分为主构造函数、二级构造函数
    • 主构造函数:在类名之后携带的(var id: Int, var name: String)即为主构造函数及其传参,而init{}作为主构造函数的内部代码块,如果参数或初始花代码为空,可以省略不写;
    • 二级构造函数,在类中声明,以constructor关键字声明,没有具体函数名,但要在构造函数声明:的后面调用主构造函数;
  • 以二级构造函数初始化类,方法执行顺序是主构造函数 -> init{} -> 二级构造函数;
  • 主构造函数不是必须的,如果一个类没有受限于父类,可以定义两个互不关联的二级构造函数,且无需定义主构造函数;

3)构造传参

  • 格式:在类名之后以()包含,单个参数格式为val/var/缺省 参数名:类型/类型?类型?代表可以为空
  • 加val/var或不加
    • 加val/var代表,参数传递到该类之后将作为类的成员属性,init{}方法、成员方法中都可以引用;
    • 不加,则只是代表构造函数中有这个参数,只能够在init{}方法中引用,成员函数无法使用该参数变量;
  • 参数默认值,可以通过设定参数的默认值,使得该参数作为可选传递参数,格式:(参数名:类型 = 默认值)
    • 如果是java+kotlin混合项目,kotlin类中带有默认值的构造函数,需要加上注解@JvmOverloads,否则Java代码无法识别默认参数;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      class User(var id: Int, name: String, sex: Int = 0) : ViewModel(), Serializable {

      init {
      /*初始代码块*/
      }

      constructor(id: Int, age: Int, name: String) : this(id, name) {
      /*二级构造函数*/
      }

      }
      //简化成:
      class User(var id: Int, age: Int, name: String, sex: Int = 0) : ViewModel(), Serializable {

      init {
      /*初始代码块*/
      }

      }

2、接口

3、函数

1)声明定义

  • 函数声明使用关键字fun,默认为public,可以省略该关键字;
  • 参数格式为:参数:类型
    • 如果参数可以为空,则应在类型后加上?,格式为:参数:类型?
    • 参数允许具有默认值,格式为参数:类型 = 默认值
    • 具有多个默认参数时,可以通过参数名指定传递参数,如:User().setInfo(1, home = "shenzhen")
  • 在参数定义之后,可以添加返回值申明,格式为:fun tese(name:String):String{}
    • 如果返回参数为空,具体定义类型为:Unit,但可以忽略不写;
  • 通过在声明关键字fun前添加关键字override来声明对父类的该函数进行重载;
  • 通过关键字vararg来声明可变参数,即类似于Java中的public void setNames(String... names),kotlin的写法为fun setNames(vararg names: String?),不允许传递多个可变参数;
  • kotlin中的函数方法体也可当做变量来赋值,例如toast扩展函数的声明实现
    1
    fun Context.toast(msg: CharSequence) = Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()

2)方式

  • 普通函数(无返回值)

    1
    2
    3
    4
    5
    6
    fun test(){
    }
    //无返回值函数也可以写成以下方式,Unit标记无返回值
    fun log(): Unit {
    Log.e(·····)
    }
  • 函数传参

    1
    fun test(text:String){}
  • 函数+可变参数

    1
    2
    3
    4
    5
    6
    7
    8
    fun testArray(vararg arr: String) {
    for (str in arr) {
    Log.i(TAG, "testArray: $str" )
    }
    }

    //调用
    Test().testArray("111","222","333","444","555","666")
  • 函数返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fun sum(a: Int, b: Int): Int {   // Int 参数,返回值 Int
    return a + b
    }

    //表达式作为函数体,返回类型自动推断

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

    // public 方法则必须明确写出返回类型,除非无返回值或Unit
    public fun sum(a: Int, b: Int): Int = a + b
  • 静态方法

    1
    2
    3
    4
    5
    companion object {
    fun testStatic(): Boolean {
    return true
    }
    }
  • 匿名函数(Lambda)

    1
    2
    3
    4
    5
    fun testArray(vararg arr: String) {
    for (str in arr) {
    val forLambda: (String) -> (Int) = { text -> Log.i(TAG, "testArray: $text") }
    }
    }

3)特殊类型

a)全局函数

正常的函数定义在类的内部,依赖类进行调用,而kotlin中允许在独立的kt文件中直接定义全局函数,区别与java的静态函数,定义之后可以在程序中直接使用,不需要通过类,例如:arrayOf();

b)泛型函数

通过在函数名之前使用<*>泛型(例如<T>)声明未被定义的参数类型(包括传入和输出),之后必须在使用函数时传入参数类型进行定义才能使用;

1
2
3
fun <T> addHouse(address: T):T {
··· 省略 ···
}

c)内联函数

  • 在开发过程中通常会将重复的代码抽离为一个函数,而函数在调用过程会经历一系列的压栈出栈操作,频繁的调用某一个函数,也会额外的性能消耗;
  • 内联函数可以理解为一个代码块,通过inline声明之后,会在编译期间将内联函数与调用位置进行替代;
  • 关键字

    • inline(标记为内联函数)
      1
      2
      3
      4
      5
      6
      7
      inline fun getName(vararg names: String): String {
      var all = "";
      for (name in names) {
      all = "$all, $name"
      }
      return all;
      }
  • noinline

    如果将一个函数作为参数传给内联函数,在内联函数中又将该函数传参传递给另一个非内联函数作为参数时,编译时就会报错,因为作为内联函数形参的时候,函数传参也是inline的函数对象,但编译时函数传参已经转变为一个具体的值,而不再是一个对象,另一个内联函数要接收函数对象时则会报错;
    通过noinline关键字标记参数为非内联函数来解决上述问题;

    1
    2
    3
    4
    5
    6
    7
    inline fun <T> getName(noinline names: () -> T) {
    mark(names)
    }

    fun <T> mark(names: () -> T){

    }
  • crossinline

    • 禁止被标记的lambda表达式进行局部返回,只能以return@XXXinterface进行返回;
    • 官方释义:一些内联函数可能调用传给它们的不是直接来自函数体、而是来自另一个执行上下文的 lambda 表达式参数,例如来自局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许非局部控制流。为了标识这种情况,该 lambda 表达式参数需要用 crossinline 修饰符标记。
  • reified

    泛型具体化,与inline配合使用,可以在内联函数中具体化泛型来使用;

d)简化函数

简化函数结构,变成类似于三元运算符?:结构;

e)尾递归函数

f)高阶函数

4、变量、常量

1)创建、声明

创建实例时不需要使用关键字new

  • var:可变变量定义:
    • var <标识符> : <类型> = <初始化值>
  • val:不可变变量定义,只能赋值一次的变量,类似Java中final修饰的变量
    • val <标识符> : <类型> = <初始化值>
      常量与变量都可以没有初始化值,但是在引用前必须初始化
      声明时类型指定不是必须的,kotlin编译器支持自动类型判断,所以可以由编译器自己判断。

2)延迟初始化

  • lateinit:延迟初始化

    • 只能用来修饰作为成员变量的var对象,不能是局部变量,也不能是基本类型,因为基本类型在类加载时会自动赋予初始默认值;
    • 作用是让编辑器检查是不会因为属性变量未被初始化而报错,使用前需要判断以及初始化,但并不是直到第一次使用才初始化;
    • 使用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      class RepoApplicarion : Application() {

      override fun onCreate() {
      super.onCreate()
      instance = this
      }

      companion object {

      private lateinit var instance: RepoApplicarion

      fun get() = instance

      }
      }
  • by lazy:延迟初始化

    • 属性委托,作用于常量val,接收一个lambda并返回一个Lazy实例的函数;
    • 仅当变量第一次被调用时委托方法才会执行,并且只执行一次,启后每次调用都是返回记录的结果值;
    • 也是一种懒汉单例模式
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      val lazyTest: Int by lazy {
      var x = 1
      Log.i(TAG, "x:$x")
      var y = 2
      Log.i(TAG, "y: $y")
      x + y
      }

      fun test(text: String) {
      Log.i(TAG, "test: $lazyTest")
      Log.i(TAG, "test: $lazyTest")
      }

      //输出结果如下
      I/Test: x:1
      I/Test: y: 2
      I/Test: test: 3
      I/Test: test: 3

3)set/get方法

1
2
3
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
  • 对于var修饰的变量,set/get方法有默认实现,可以不用写;而val修饰的变量则没有相应的set默认实现,也不允许重写
  • set/get方法的自定义实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var title: String = ""
    get() {
    return field.toUpperCase(Locale.getDefault())
    }
    set(value) {
    field = value.trim()
    Log.d(TAG, "set: $value")
    }

    val content: String
    get() {
    return "this is content"
    }
  • field变量

    • 方法中需要以field替代自身变量,不允许通过变量名引用set/get方法本身的变量,会导致递归死循环;
    • 方法中也不允许声明定义与自身变量同名的其他变量;
    • field被称为Backing Fields(后端变量),相当于this,代指变量自身的引用;
  • private修饰

    • 可以通过private将set/get方法修饰为私有,但如果变量本身为public,则get方法无法定为私有函数;
      1
      2
      3
      4
      5
      6
      7
      var title: String = ""
      get() {
      return field.toUpperCase(Locale.getDefault())
      }
      private set(value) {
      field = value.trim()
      }

二、数据类型

1、基本类型

  • 1)基本数据类型
基本数据类型名称 Kotlin数据类型 Java数据类型
整形 Int int、Integer
长整型 Long long、Long
浮点型 Float float、Float
双精度 Double double、Double
布尔型 Boolean boolean、Boolean
字符型 Char char
字符串 String String
  • 2)类型转换
Kotlin 说明
toInt 转为整型数值
toLong 转为长整型数值
toFloat 转为浮点型数值
toDouble 转为双精度数值
toChar 转为字符
toString 转为字符串
toBoolean 转为布尔型

String可以使用相应的函数转为其他基本类型,但要注意得是如果是转换非数字(例如“hello”)、数字格式不匹配(“0.01”转int),转换时会抛出NumberFormatException异常;

2、数组

  • 1)基本类型
Kotlin基本数组类型 数组类型的名称 类型初始化方法
整型数组 IntArray intArrayOf
长整型数组 LongArray lonArrayOf
浮点数组 FloatArrary floatArrayOf
双精度数组 DoubleArray doubleArrayOf
布尔数组 BooleanArrat booleanArrayOf
字符数组 CharArray charArrayOf

使用,例如

1
2
> var int_array:IntArrat = intArrayOf(1,2,3,4)
>

  • 2)其他类型

例如:String,Kotlin中没有提供基本的StringArray,所以只能使用其他类型的数组来储存:

1
var string_array:Array<String> = arrayOf{"Hello","World"}

除了String,其他类型、包括基本类型,都可以使用该Array数组来存放数据

  • 3)操作
    • 获取长度(.size)
    • 获取元素[index]get(index: Int)
    • 存放元素set(index: Int, value: T)
    • 遍历
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      //for循环
      for (str in string_array){
      Log.i(TAG, "test: $str")
      }
      //while循环
      var index:Int = 0
      while (index <string_array.size){
      Log.i(TAG, "test: ${string_array[index]}")
      index++
      }

3、容器

kotlin与Java类似,也有三类基本的容器:集合Set、映射Map、队列List,而每种类型又分为只读、可读写两种,需要在初始化时就声明该容器的类型,比如:MutableSet为可读写集合、Set为普通只读集合。

1)容器的基本操作

  • isEmpty
  • isNotEmpty
  • clear
  • contains
  • iterator
  • count/size

2)Set/MutableSet集合

a)特点

  • 容器内部元素进行无序排列,所以无法按照下标进行访问、删除;
  • 容器内部元素具有唯一性,通过哈希值校验判断是否存在相同元素,如果存在,则覆盖(MutableSet);
  • MutableSet的add方法仅仅是往集合中添加元素、由于是无序排列的,所以无法确定元素的位置;
  • 没有提供修改元素的方法,一个元素添加后就无法进行变更;
  • remove方法仅用于杀出指定的元素,而无法删除指定位置的元素;

    b)遍历

  • for-in循环
  • iterator迭代器遍历
  • forEach遍历

3)List/MutableList队列

4)Map/MutableMap映射

三、条件判断

1、if-else/when-else

a)if-else

1
2
3
4
5
6
7
8
9
//与Java一致,普通判断使用
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
Log.d(TAG, "onCreate: is Android Q")
}
//可作为参数赋值
var version = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P)
"Android Q"
else
"Android Q 以下"

b)when-else

  • 作为“switch-case”的替代;
    • 也可以与“if-else”一样作为参数的赋值;
    • 作为判断对比的不再必须是常量,可以为变量,也可以为语句;
  • 格式
    • 以“判断值/语句 -> {}”替代 “case 常量: break;”
    • 以“else -> {}”替代“default: break;”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

when(baseContext){
is Context -> {}
is Application -> {}
is AppCompatActivity -> {}
}

when (Build.VERSION.SDK_INT) {
Build.VERSION_CODES.Q -> {
Log.d(TAG, "onCreate: Android Q")
}
else -> {
Log.d(TAG, "onCreate: Android 其他")
}
}

version = when (Build.VERSION.SDK_INT) {
Build.VERSION_CODES.Q -> {
"Android Q"
}
else -> {
"其他"
}
}

2、循环语句

1)while

a)while

b)do-While

2)for

a)forin

  • 常见循环方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    val list:ArrayList<String> = arrayListOf("1","2","3")

    //元素循环
    for (item in list){
    Log.d(TAG, "onCreate: $item")
    }

    //下标遍历循环
    for (index in list.indices){
    Log.d(TAG, "onCreate: ${list[index]}")
    }

    //区间循环
    for(i in 0..10){
    Log.d(TAG, "onCreate: $i")
    }
  • until、step、downTo关键字

    • until 左开右闭区间
      1
      2
      3
      4
      //左开右闭区间,即从11开始,到小于66为止,包含11、但不包含66
      for(i in 11 until 66){
      Log.d(TAG, "onCreate: $i")
      }
  • step 递增设定

    1
    2
    3
    4
    //从23递增到89,但每次递增值为4,而不是1
    for(i in 23..89 step 4){
    Log.d(TAG, "onCreate: $i")
    }
  • downTo 递减

    1
    2
    3
    4
    //forin默认递增,downTo改为递减
    for(i in 50 downTo 7){
    Log.d(TAG, "onCreate: $i")
    }

b)forEach/forEachIndexed

List、Set容器自带遍历方法forEach/forEachIndexed,但Map不能使用下标遍历,所以只有forEach

3)repeat

4)跳出循环

  • contiune
  • break
  • 标记

3、为空判断

1)可空属性

在Kotlin中,对为空判断控制的非常严格,一般变量常量的声明都需要使用varval关键字并赋值,如果可能为空则需要在类型声明后面加上?标记(var msg:String? = null),声明该属性为可空属性,使用时也需要在变量后面加上?.,例如:(msg?.lenght),如果使用时变量为空,则直接返回null

2)Elvis运算符

获取字符长度时,变量为空则返回null,此时的返回值肯定与逻辑的并不相符,可以使用?:为空值校验设置默认值,即为空时,以默认值替代null,例如msg?.length ?: 1;

3)强制非空转换

还有另外一种运算符!!,用于标记可空属性,表示强制将该可空属性转换为非空属性,但要注意得是,需要自行判断是否为空,如果标记的对象为空,则使用时可能会抛出空指针异常;

4、等式判断

1)结构相等

使用==替代Java原有的equals()函数进行值对比,判断是否相等,而非参数内存地址对比,而判断两个属性不相等,则可以使用!=,这种对比在kotlin中称为结果相等对比,即两个属性值、内部等都一致;

2)引用相等(全等判断)

使用===来判断两个属性之间,是否结构、引用内存地址等都相等,即两个属性全等,而判断两个属性非全等,可使用”!==”;

  • 对于基本类型而言,结构相等、引用相等判断并无区别;
  • 对于一个类的不同实例,如果有一个属性equals或引用不相等,则既是结构不相等也是引用不相等;
  • 对于一个类的不同实例,如果属性都equals相等,则为结构相等,但引用不相等;

5、类判断

1)类实例判断

Java中通过instanceof判断一个属性是否为某个类的实例化对象,但在kotlin中,将该命令简化为is,使用方式与Java中差不多:if(user is User),如果要判断不相等,则加上!,如:if(user !is User);

2)数组元素判断

Java中判断一个数组中是否包含某个元素得时候,基本都是通过遍历循环来判断,而在kotlin中,提供了关键字in来判断,例如:判断Int数组arr是否包含元素4,为if(4 in arr),除了判断基本类型也可用来判断类对象,如:(user in arr),如果要进行不存在判断,则加上!,为(user !in arr)

疑问?

1、控件变量自动映射功能,直接通过控件id名引用控件
2、set中元素具有唯一性,kotlin的setOf中添加多个同样的,会有多少个元素?

1
2
val _str = "123"
val map = setOf(_str, _str, _str)

3、set是无序的,为什么可以forEach

参考文档

Kotlin内联函数的使用