2018-06-08  本文已影响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)
    }
}
上一篇下一篇

猜你喜欢

热点阅读