Posts /

Kotlin 学习记录(类相关)

15 Jul 2019

Kotlin 学习记录


本文只记录重要的或者与C/C++、Java 出入较大的内容

Class

  1. 当类没有结构体时可以省略大括号:class Test

  2. 主构造函数

    class Test constructor(num :Int){
    	//...
    }
       
    class Test (num:Int){
        //...
    }
    
  3. 初始化代码块

    class Test constructor(var num:Int){
        init{
         	num = 5
            println("num = $num")
        }
    }
    
  4. 如上,声明属性可以直接在类头声明

  5. 当构造函数不具有注释符或者使用默认的可见性修饰符时,可以省略constructor关键字

  6. 辅助构造函数

    class Test{
        constructor(参数){
        }
    }
    
  7. 同时存在主构造函数和二级构造函数

    ​ 如果类具有主构造函数,则每个辅助构造函数需要通过另一个辅助构造函数直接或间接地委派给主构造函数。 使用this关键字对同一类的另一个构造函数进行委派:

    class Class_example2 constructor( num1:Int = 2){
        private var num2 :Int = 30
       
        init {
            println(num1)
            num2
        }
       
        constructor( num1:Int = 2, num3:Int):this(num1){
            println("num3+num1 = $num1 + $num3")
        }
           
        //虽然这里写的是num3,但其实调用的是主构造函数
        constructor(num1: Int = 2,num3: Int,num2:Int):this(num3){ 
            println("constructor - 3 para")
        }
       
    }
    fun main(){
        var c :Class_example2 = Class_example2( 3,num3 = 3)
        var cc:Class_example2 = Class_example2(1,2,3)
       
    }
       
    /*
    输出:
    3
    num3+num1 = 3 + 3
    2      
    constructor - 3 para
       
    */
    
  8. 当类的主构造函数都存在默认值的情况下

    • JVM上,如果类主构造函数的所有参数都具有默认值,编译器将生成一个额外的无参数构造函数,它将使用默认值。 这使得更容易使用Kotlin与诸如JacksonJPA的库,通过无参数构造函数创建类实例。
    • 同理可看出,当类存在主构造函数并且有默认值时,二级构造函数也适用
    • 如果第一个第一个参数不是默认的,则不会有无参的,可以调用一个参数的。
    • 总结:如果中间有不具备默认参数的,则到该参数为止都需要进行强制给值,直到参数赋值完或者后面的都是由默认值的形参。
  9. 类的实例化

    没有 new 关键字。

  10. 类的类别

    密封类、内部类、抽象类、枚举类、接口类、数据类

属性与字段

  1. Getter & Setter

    1. 在Kotlin中,想要外部变量不能访问某类内的变量则将Setter进行private修饰,若使用private修饰属性则该变量不能对该属性进行访问
      1. val属性不能有setter函数
      2. getter一般写,默认实现。写了 get()=”修改也不变”,则当前属性值永远为“修改也不变”
  2. 修改访问器(Getter/Setter)的可见性

    1. get函数前面的可见性修饰符需要和属性一直
    2. 可以用@Inject set 来对实现Setter
    3. 共有属性var,setter用private进行修饰,则表示该属性不能外部修改值。
  3. 后备字段 (What is backing field)

    1. 定义:如果属性使用至少一个访问器的默认实现,或者自定义访问通过field标识符引用,则将为属性生成后备字段。 (换句话,如果没有默认访问器实现 && 没有自定义通过访问field标识符进行引用,则不会有后备字段)

    2. 原理:Kotlin中没有字段,但有后备字段。在isEmpty例子中可以学习到,判断类中是否为空不需要单独的字段,只需要对size进行判断即可,因此该变量不需要字段。而size则需要后备字段。

      class DummyClass {
          var size = 0;
          var isEmpty //no backing field
              get() = size == 0
              set(value) {
                  size = size * 2
              }
      }
      
         public final class DummyClass {
         private int size;
            
         public final int getSize() {
            return this.size;
         }
            
         public final void setSize(int var1) {
            this.size = var1;
         }
            
         public final boolean isEmpty() {
            return this.size == 0;
         }
            
         public final void setEmpty(boolean value) {
            this.size *= 2;
         }
      }
            
      
  4. 后备属性

    _table是private 没有法访问,定义了table属性来对 _table进行get操作。

    private var _table: Map<String, Int>? = null
    public val table: Map<String, Int>  /// table就是后备属性。
        get() {
            if (_table == null) {
                _table = HashMap() // 初始化
            }
            // ?: 操作符,如果_table不为空则返回,反之则抛出AssertionError异常
            return _table ?: throw AssertionError("Set to null by another thread")
        }
    
  5. 编译时常数

    编译时常数必须为顶层声明,初始化为String或者基本类型,没有自定义的getter()

    const val CONST_NUM = 5
    const val CONST_STR = "Kotlin"
    
  6. 后期初始化属性

    只能用于修饰var,没有自定义的setter与getter函数,属性必须为空且类型不能为基本类型。

  7. 委托属性

修饰符

public、internal、protected、private

  1. 顶层声明

    1. 在顶层声明中,文件不能用protected修饰
    2. 不同文件中,访问顶层声明的可以访问,public和internal
  2. 在类中声明

    1. 类中可以使用任意修饰符,且类内可以任意访问
    2. 类外的函数,只能访问public和internal
  3. 在接口中声明

    1. 只能声明public属性。
    2. 修饰private类和private的方法
    3. 用private修饰的方法不能被实现该接口的类重载。
  4. 在构造函数中的声明

    1. 任意使用修饰符
    2. 在二级构造函数中,不能用任意修饰符,可以说是默认修饰(public)
  5. 在局部声明中同上步中的二级构造函数

  6. 与Java中的对比

    四个修饰符不同、默认修饰符不同

继承

  1. 超类(Any);用:符号继承

  2. open修饰符

    1. open修饰符是定义继承类的修饰符
    2. 类和成员都需要使用open关键字
  3. 继承类的构造函数

    1. 实现类无主构造函数

      每个辅助构造函数必须使用super关键字初始化或者委托给另一个构造函数。

    2. 存在主构造函数

      主构造函数一般实现基类中参数最多的构造函数,参数少的哦那个this引用即可。

  4. 函数的重写

    1. 子类不能重写基类中没有用open修饰的同名函数。

    2. 当一个类不是用open修饰时,该类默认实final,不能被再次继承

    3. 子类用final关键字修饰方法,以此来禁止后续子类重写该方法。

      open class A{
          open fun foo(){}
      }
            
      // B这个类继承类A,并且类B同样使用open修饰符修饰了的
      open class B : Demo(){
               
          // 这里使用final修饰符修饰该方法,禁止覆盖掉类A的foo()函数
          final override fun foo(){}
      }
      
  5. 重写属性

    1. 重写属性必须用override修饰。

    2. 当基类属性修饰为val时,实现类可以用var去重写,反之却不行。

      open class Demo{
          open val valStr = "我是用val修饰的属性"
      }
            
      class DemoTest : Demo(){
            
          /*
           * 这里用val、或者var重写都是可以的。
           * 不过当用val修饰的时候不能有setter()函数,编辑器直接会报红的
           */
                
          // override val valStr: String
          //   get() = super.valStr
            
          // override var valStr: String = ""
          //   get() = super.valStr
            
          // override val valStr: String = ""
            
          override var valStr: String = "abc"
              set(value){field = value}
      }
            
      fun main(arge: Array<String>>){
          println(DemoTest().valStr)
            
          val demo = DemoTest()
          demo.valStr = "1212121212"
          println(demo.valStr)
      }
            
      
    3. 重写属性是不能用 get() = super.xxx,因为这样的话,不管你是否重新为该属性赋了新值,还是支持setter(),在使用的时候都调用的是基类中的属性值。

      class DemoTest : Demo(){
            
          /*
           * 这里介绍重写属性是,getter()函数中使用`super`关键字的情况
           */
                
          override var valStr: String = "abc"
              get() = super.valStr
              set(value){field = value}
      }
            
      fun main(arge: Array<String>>){
          println(DemoTest().valStr)
            
          val demo = DemoTest()
          demo.valStr = "1212121212"
          println(demo.valStr)
      }
      

      也不能 get() = this.valStr / get() = valStr 。会报运行错误。

      Exception:StackOverflowError

      java.lang.StackOverflowError:stacksize8MBStackOverflowError是由于当前线程的栈满了,也就是函数调用层级过多导致。堆栈溢出错误一般是递归调用。出现这种异常,大多是由于循环调用。出现的情况:大多数都是在本方法中调用本方法。也就是我们常说的递归调用,所以才导致这个错误的出现。

      应该用默认的 get() = field

  6. 在主构造函数重写

    class DemoTest2(override var num: Int, override val valStr: String) : Demo()
       
    fun main(args: Array<String>){
        val demo2 = DemoTest2(1,"构造函数中重写")
        println("num = ${demo2.num} \t valStr = ${demo2.valStr}")
    }
    
  7. 覆盖规则:解决两个接口方法名相同问题

    open class A{
        open fun test1(){ println("基类A中的函数test1()") }
       
        open fun test2(){println("基类A中的函数test2()")}
    }
       
    interface B{
        fun test1(){ println("接口类B中的函数test1()") }
       
        fun test2(){println("接口类B中的函数test2()")}
    }
       
    class C : A(),B{
        override fun test1() {
            super<A>.test1()
            super<B>.test1()
        }
       
        override fun test2() {
            super<A>.test2()
            super<B>.test2()
        }
    }
    

接口类/枚举类

枚举

  1. 枚举类的初始化及使用

    enum class Color(var argb : String){
        RED(""),
        WHITE(""),
        BLACK(""),
        GREEN("")
    }
    

    枚举常量,枚举类中的每个枚举常量都是对象,用逗号分隔。(如上述的RED(“”),)

    直接用Color.RED进行访问。

  2. 枚举常量匿名类,必须提供一个抽象方法,且该方法定义在枚举类内部。而且必须在枚举变量的后面,有抽象函数,则最后一个枚举变量必须用;隔开。

  3. 枚举常量的属性:name(常量名)和ordinal(常量位置)

  4. 可以用enumValues<T>()enumValuesOf<T>()访问

    println(enumValues<Color>().joinToString { it.name })
    println(enumValueOf<Color>("RED"))
       
    //输出
    //RED, WHITE, BLACK, GREEN
    //RED
    
  5. valueof()values()检测

    println(Color.valueOf("RED"))
    println(Color.values()[0])
    println(Color.values()[1])
    println(Color.values()[2])
    println(Color.values()[3])
    

    其中,若使用Color.valueOf("不存在的枚举常量"),则会抛出IllegalArgumentException 异常,即枚举变量不存在。若使用Color.values()[大于枚举常量位置],则会抛出下标越界异常。

接口类

  1. 进行对接口的实现
  2. Kotlin中接口中可以写属性,作为抽象属性、作为访问器
  3. 多接口可用super<接口名>.方法名来区分。

数据类和密封类

数据类

  1. 关键字data
  2. 构造函数必须存在至少一个参数。
  3. 数据类的特性:
    1. 数据类不能是抽象的、开放的、密封的或者内部的。
    2. 数据类可以实现接口,同时也可以继承其他类,如密封类。

密封类

  1. 关键字sealed
  2. sealed class SealedExpr()
  3. 密封类不能被实例化,他的作用是表示受限的类继承结构
  4. 密封类可以有多个实例。
  5. 密封类的子类必须是在密封类的内部或必须存在于密封类的同一文件,密封类可以有效地保护代码。

抽象类&内部类

抽象类

  1. 抽象类有抽象成员,抽象成员都带abstract关键字
  2. Kotlin中的抽象类,在顶层定义时只能使用public
  3. 抽象类中可以定义内部抽象类
  4. 只能继承一个抽象类
  5. 抽象类,可以通过子类向上转型
  6. 抽象类可以继承另一个类,但不建议用open修饰抽象类

嵌套类类

  1. 定义:一个类嵌套在另一个类当中

  2. 外部类.嵌套类().嵌套类方法/属性。在调用的时候嵌套类是需要实例化的

    class Outter{
        class Nested{
            fun execute(){
                Log.d("test", "Nested -> execute")
            }
        }
    }
       
    // 调用
    Outter.Nested().execute()
       
    //输出
    Nested -> execute
    

内部类

  1. 定义:用inner class 来进行声明类

  2. 内部类不能直接被实例化,需要外部的类实例化了对象,再利用该对象进行实例化内部类。

    class Outter{
        val testVal = "test"
        inner class Inner{
            fun execute(){
                Log.d("test", "Inner -> execute : can read testVal=$testVal")
            }
        }
    }
       
    // 调用
    val outter = Outter()
    outter.Inner().execute()
       
    // 输出
    Inner -> execute : can read testVal=test
    
  3. 监听器的实现方法

    package edu.zju.maple.learning
       
    class NickInnerClass{
        lateinit private var listener:OnClickListener
       
        fun setOnClickListener(listener: OnClickListener){
            this.listener = listener
        }
       
        fun activeListener(){
       
            listener.onItemClick()
        }
       
    }
       
    interface OnClickListener{
        fun onItemClick()
    }
       
    fun main(){
        val nick = NickInnerClass()
        nick.setOnClickListener(object:OnClickListener{
            override fun onItemClick() {
       
                println("执行了activeListener函数,才有这句输出")
            }
       
        })
        var i=0;
        while (i<20){
            i++
       
            nick.activeListener() ///唤醒方法
            for ( temp in 1..0xFFFF){
       
            }
       
        }
    }
    

局部类

  1. 局部类只能在定义该局部类的方法中使用
  2. 定义在实例方法中的局部类可以访问外部类的所有变量和方法,但不能修改
  3. 局部类可以定义属性、方法。