作者: 一江碎月 | 来源:发表于2018-06-08 17:24 被阅读0次

    继承和重写

    kt 中使用 :(冒号) 代替 java 中的 extends 和 implements

    重写父类的方法时,必须使用 override 标识。

    当有多个父类,且父类中的方法冲突时,可以通过 把父类放到 super<> 的尖括号中指定调用哪个父类中的方法

    class Impl:Itf,Itf2{
        override fun test() {
            super<Itf>.test() // 调用 Itf 接口中的 test 方法
            super<Itf2>.test() // 调用 Itf 接口中的 test 方法
            super.say()  // 方法不冲突时,直接使用 super. 调用父类的方法
            println("impl")
        }
    }
    

    类的定义

    使用 class 关键字定义一个类

    class Demo2{
        fun test() = println("demo2#test")
    
        fun test2() = println("demo2#open")
    }
    

    kt 中的所有类都是 final ,不能被继承;所有方法也都是 final 不能被重写


    open 与 final

    1. kt 中,类、方法默认都是 final。

    2. 可以使用 open 修饰符取消类、方法或属性的 final 属性

    3. 继承的方法、属性也是 open,除非手动使用 final 进行修饰:

    class Demo
    
    open class Demo2{
        fun test() = println("demo2#test")
    
        open fun test2() = println("demo2#open")
    }
    
    open class Demo3:Demo2(){ // 可以继成 Demo2,但不能集成 Demo
        final override fun test2() { // 重写 test2() ,但不能重写 test
            super.test2()
        }
    }
    
    class Demo4:Demo3(){
        // 由于 Demo3 将 test2() 声明为 final 类型,所以该类中没有可重写的方法
    }
    

    构造函数

    kt 中的构造函数分为 主构造函数,从构造函数。主构造函数在类的外部声明,而从构造函数在类的内部声明。

    1. 所有的从构造函数使用 constructor 声明。

    如果没有给一个类声明任何构造函数,将会生成一个不做任何事情的默认构造函数。

    1. 同时定义主构造函数与从构造函数时,从构造函数必须调用到主构造函数。如下,虽然主构造函数没有做任何操作,但从构造函数最终必须要调用到主构造函数。

    2. 构造函数通过 this 相互调用

    class Child2(){
        constructor(a:Int):this(a,1)
        constructor(a:Int,b:Int):this()
    }
    

    主构造函数与 init

    主构造函数定义在类的外面。

    主构造函数功能很单一,只能 为类定义和参数同名的属性,并生成其对应的 getter/setter。如果需要在构造函数中执行一些初始化操作,需要使用 init 定义初始化语句块

    1. 初始化语句块只能与主构造函数一起使用。一个类中可以定义多个初始化语句块。

    2. init 块中可以使用主构造函数中的参数。如下面代码中,可以在 init 块中直接使用 name 参数。

      // 定义主构造函数
      class Demo constructor(name:String){
      val name:String
      init {
      this.name = name
      }
      }

    上述代码如下面的 java 代码功能一样,为类定义一个 name 属性,同时将构造函数中的 name 值赋值给 name 属性:

    class Demo{
        public final String name;
        Demo(String name){
            this.name = name;
        }
    }
    

    主构造函数的演化

    在不进行简化的情况下,主构造函数的定义如下:

    // 定义构造函数,同时对主构造函数进行修饰与使用注解
    class Test @A private constructor(name:String){
        lateinit var name_:String
        init {
            this.name_ = name
        }
    }
    
    1. 如果主构造函数没有被注解或者可见性修饰符修饰时,可以直接使用 constructor。如下:

      class Test constructor(name:String){
          lateinit var name_:String
          init {
              this.name_ = name
          }
      }
      
    2. 当主构造函数只有 constructor 时,可以省略 constructor 关键字。因此上面代码可以简写如下:

      class Test(name:String){
          lateinit var name_:String
          init {
              this.name_ = name
          }
      }
      
    3. 如果 init 代码块中只是为同名属性赋值,可以省略 init 代码块

      class Test(name:String){
          val name = name // 可以直接使用构造函数中的参数为属性赋值
      }
      
    4. 如果不需要为属性自定义 getter/setter,可以将修饰属性的 var/val 提到构造函数中。此时会生成默认的 getter/setter

      class Test(val name: String)
      
    5. 主构造函数中的属性无法直接设置 getter/setter,需要用另外的属性:

      class Test(name:String,age:Int){
      
          var name = name
              get() {
                  println("getter")
                  return "from getter"
              }
      }
      

    从构造函数

    定义在类的内部,而不是类名后面的构造函数

    大部分情况下,使用主构造函数就够了。之所以定义从构造函数,是为了能通过不同的方式初始化类。

    ```kotlin
    class Demo{
        val name:String
        // 从构造函数
        constructor(name: String){
           this.name = name
        }
    }
    ```
    

    继承时的构造函数

    同 java 一样,子类的构造函数必须调用到父类的构造函数。

    1. 唯一原则:确保无论用户调用哪个构造函数,它必须能调用到父类的构造函数

    2. 有主构造函数时,从构造函数不能直接调用父构造函数

      open class Parent{
         constructor(name: String)
      }
      
      class Child():Parent("x")
      
      class Child2:Parent{
         constructor():super("xx")
      }
      
      class Child3():Parent(""){
         constructor(name:String):this()
      }
      

      第一个类中,用户调用主构造函数时,可以调用到父类。

      第二个类中,用户调用从时,可以调用到父类。如果定义主构造函数,则会报错。因为用户调用主构造函数时,无法调用到父类的构造函数。

      第三个类中,有主从。从只能调用主构造函数。这是因为主构造函数可能会定义一系列的变量,如果调用不到主,则这些变量无法初始化。


    抽象类

    定义方式与 java 一样。

    1. 普通类默认是 final ,但抽象类始终是 open

    2. 抽象类中的非抽象函数默认是 final,抽象函数是 open。

    abstract class Demo2{
        fun test() = println("demo2#test") // 默认是 final,子类不能重写 test()
    
        open fun test2() = println("demo2#open") // 显式使用 open 关键字
    
        abstract fun test3() // 抽象函数,默认是 open。被重写后,默认也是 open
    }
    

    内部类与嵌套类

    1. kt 中直接将一个类定义在另一个类内部,该类叫嵌套类。嵌套类不持有外部类的引用。相当于 java 中被 static 修饰的内部类。

    2. 嵌套类使用 inner 修饰,则该类为内部类,此时同普通的 java 内部类。可以引用外部类的成员属性、方法。

    3. 内部类通过 this@Outer 获取外部类的引用

      class Student{
          val name = "name"
      
          override fun toString(): String {
              return "toString"
          }
      
          class Inner{
              fun test() = println("--") // 无法引用外部类
          }
          // 定义成内部类
          inner class InnerClass{
              override fun toString(): String {
                  return "innerclass"
              }
              fun test() = println("$name ${this@Student.toString()}") // name toString
          }
      }
      

    密封类

    使用 sealed 修饰符定义的类是密封类,密封类要求 所有直接子类必须定义在父类所在的文件中

    1. sealed 隐含的这个类是 open 类,不需要再显式地声明 open。

    2. 密封类的构造方法是 private 。

    3. java 中不能继承密封类。kt 中可以在密封类所在的文件中定义密封类的子类,不是同一文件不继承。

    4. sealed 不能用于修饰接口

    // 密封类
    sealed class Student{
        // 内部类可以继承
        private class S : Student()
    }
    // 同文件中可以继承
    class S1:Student()
    

    数据类

    使用 data 修饰的类为数据类,数据类会根据主构造函数自动重写能用方法

    通用方法一般包括有:equals(),toString(), hashCode()。

    1. equals() 会检测所有属性的值是否相等

    2. toString() 生成按声明顺序排列的所有字段的字符串表达形式

    3. hashCode() 会返回一个根据所有属性生成的哈希值。

    4. equals ,toString() 和 hashCode() 只会考虑在主构造函数中声明的属性

    5. 由于 hashCode 会随属性值的变化而变化,因此在用于 map 中作为键值时,其 主构造函数中的属性必须要声明为 val

      fun main(args:Array<String>){
          val demo = Parent(1,"ls")
          println("hashCode = ${demo.hashCode()} toString = ${demo.toString()}")
      
          val d = Parent(1,"ls",20)
          println("hashCode = ${d.hashCode()} toString = ${d.toString()}")
      
          println(demo == demo) // true
      
          val map = hashMapOf<Parent,String>()
          val key = Parent(1,"ss")
          map.put(key,"000")
          println(map[key]) // 000
          key.name = "xx"
          println(map[key]) // null
      }
      
      data class Parent(var age:Int,var name:String){
          var score = 0
          constructor(age:Int,name: String,score:Int):this(age,name){
              this.score = score
          }
      }
      

      上述代码中,如果修改其中一个属性的值, map 返回的结果为 null。这是因为 hashCode 值已经发生了变化。

    6. data 类在生成时,会自动添加 copy 方法:在调用 copy() 时修改其些属性的值。避免无法修改属性值。这就是原型模式。

      fun main(args:Array<String>){
          val demo = Parent(1,"ls")
      
          val  dc = demo.copy(2,"copy")
          // 两者的 hashCode 返回结果不一样
          println("demo = ${demo.hashCode()} dc = ${dc.hashCode()}")
      }
      

    类委托

    在使用装饰模式时经常会出现这种情况:除了少部分要重写的方法外,大部分方法都只是简单的将请求转发给被装饰者。这些转发请求的代码就是所谓的样板代码 —— 所有的转发代码格式都一样。

    kt 中使用 by 关键字省略样板代码

    如下面代码,除了 add 方法外,其余的方法都是直接将操作转发给 innerSet 的同名操作。

    // 使用 by 关键字常未重写的操作委托给 innerSet ,而重写的方法调用 Count 定义的
    class Count<T>(private val innerSet:MutableCollection<T> = HashSet()):MutableCollection<T> by innerSet{
        override fun add(element: T): Boolean {
            println("add ${element}")
            return innerSet.add(element)
        }
    }
    

    相关文章

      网友评论

          本文标题:

          本文链接:https://www.haomeiwen.com/subject/jjfasftx.html