类、对象和接口(四)

    xiaoxiao2023-11-22  163

    Kotlin的接口可以包含属性声明,且声明默认是final和public,嵌套的类默认并不是内部类:它们并没有包含对其外部类的隐式引用

    1.定义类继承结构

        1.1 Kotlin中的接口

    //声明一个接口 interface Clickable{ fun click() } //实现一个接口 class Button:Clickable{ override fun click() = println("I was clicked") } 调用: Button().click() 输出: I was clicked

    override:和Java中的@Override注解类似,override修饰符用来标注被重写的父类或接口的方法和属性,在Kotlin中使用override修饰符是强制要求的

    interface Clickable { fun click() fun showOff() = println("I'm clickable!") } interface Focusable { fun setFocus(b: Boolean) = println("I ${if (b) "got" else "lost"} focus.") fun showOff() = println("I am focusable") } class Button : Clickable, Focusable { override fun showOff() { super<Clickable>.showOff() super<Focusable>.showOff() } override fun click() = println("I was clicked") } 调用: val button = Button() button.showOff() button.setFocus(true) button.click() 输出结果: I'm clickable! I am focusable I got focus. I was clicked

    1.2 open、final和abstract修饰符:默认为final

            如果你想允许创建一个类的子类,需要使用open修饰符来标示这个类,此外,需要给每一个可以被重写的属性或方法添加open修饰符

    //声明一个带open方法的open类 open class RichButton : Clickable { override fun click() {}//重写一个基类或接口的成员,同样默认也是open的 fun disable() {} open fun animate() {} } //禁止重写 open class RichButton : Clickable { final override fun click() {} }

         abstract类不能被实例化。在抽象类中通常包含一些没有实现并且必须在子类重写的抽象成员。抽象成员始终是open,所有不需要显式地使用open修饰符:

                                                                              类中访问修饰符的意义

    修饰符

    相关成员

    评注

    final

    不能被重写

    类中成员默认使用

    open

    可以被重写

    需要明确地表明

    abstract

    必须被重写

    只能在抽象类中使用:抽象成员不能有实现

    override

    重写父类或接口中的成员

    如果没有使用final表明,重写的成员默认是开放的

        1.3 可见性修饰符:默认为public

                                                                               Kotlin的可见性修饰符

    修饰符

    相关成员

    评注

    public(默认)

    所有地方可见

    所有地方可见

    internal

    模块中可见

    模块中可见

    protected

    子类中可见

    -

    private

    类中可见

    文件中可见

    1.4 内部类和嵌套类:默认是嵌套类

                                                          嵌套类和内部类在Java与Kotlin中的对应关系

     

    类A在另一个类B中声明

    Java

    Kotlin

    嵌套类(不储存外部类的引用)

    static class A

    class A

    内部类(储存外部类的引用)

    class A

    inner class A

    嵌套类不持有外部类的引用,而内部类持有

    class Outer{ inner class Inner{ fun getOuterReference():Outer = this@Outer } }

    1.5 密封类:定义受限的类继承结构

    sealed class Expr { class Num(val value: Int) : Expr() class Sum(val left: Expr, val right: Expr) : Expr() } fun eval(e: Expr): Int = when (e) { is Expr.Num -> e.value is Expr.Sum -> eval(e.right) + eval(e.left) }

    sealed修饰符隐含的这个类是一个open类,不需要再显式地添加open修饰符,密封类不能在类外部拥有子类

    2.声明一个带非默认构造方法或属性的类

        2.1 初始化类:主构造方法和初始化语句块

    class UserModel constructor(nickName: String) { val nickName: String init { this.nickName = nickName } }

    constructor关键字:用来开始一个主构造方法或从构造方法的方法

    init关键字:用来引入一个初始化语句块

           2.2 构造方法:用不同的方式来初始化父类

    open class View { constructor(context: Context) {} constructor(context: Context, attributes: Attributes) {} } class MyButton : View { constructor(context: Context) : super(context) {} constructor(context: Context, attributes: Attributes) : super(context, attributes) {} }

    通过this将一个构造方法委托给同一个类的另一个构造方法,为参数传入默认值

        2.3 实现在接口中声明的属性

    interface UserInterface { val nickname: String } class PrivateUser(override val nickname: String) : UserInterface class SubscribingUser(val email: String) : UserInterface { override val nickname: String get() = email.substringBefore('@') } 调用: println(SubscribingUser("caotong@163.com").nickname) 输出结果: caotong

         2.4 通过getter和setter访问支持字段

    class User(val name: String) { var address: String = "unspecified" set(value: String) { println("""Address was changed for $name: "$field"->"$value".""".trimIndent()) field = value } } 调用: fun main() { val user = User("Mr.C") user.address = "I am your father" } 输出结果: Address was changed for Mr.C: "unspecified"->"I am your father".

     

        2.5 修改访问器的可见性

    class LengthCounter { var counter: Int = 0 private set//不能在类外部修改这个属性 fun addWord(word: String) { counter += word.length } } 调用: val lengthCounter = LengthCounter() lengthCounter.addWord("HaHa") println(lengthCounter.counter) 输出结果: 4

     

    3.编译器生成的方法:数据类和类委托

        3.1 通用对象方法

    字符串表示:toString()

    class Client(val name: String, val postalCode: Int) { override fun toString() = "Client(name = $name,postalCode = $postalCode)" } 调用: val client = Client("Mr.c", 1240) println(client) 输出结果: Client(name = Mr.c,postalCode = 1240)

     

    对象相等性:equals()

    ==运算符:在Kotlin中,用来检查对象是否相等,而不是比较引用,会调用"equals"来比较值

    class Client(val name: String, val postalCode: Int) { /** * Any 是java.lang.Object的模拟:Kotlin中所有类的父类。可空类型意味着other是可以为空的 */ override fun equals(other: Any?): Boolean { if (other == null || other !is Client) return false return name == other.name && postalCode == other.postalCode } } 调用: val client = Client("Mr.c", 1240) val client2 = Client("Mr.c", 1240) println(client == client2) 输出结果: true

     

    Hash容器:hashCode()

        通用的hashCode契约规定:如果两个对象相等,它们必须有着相同的hash值,只有它们的hash值相等时,才会去比较真的的值,所有hashCode方法通常与equals一起被重写。

    override fun hashCode(): Int = name.hashCode() * 31 + postalCode

        3.2 数据类:自动生成通用方法的实现

        data class Client(val name: String, val postalCode: Int)

     

        数据类和不可变性:copy()方法

    虽然数据类的属性并没有要求是 val,同样可以使用 var,但还是强烈推荐只使用只读属性,让数据类的实例不可变 。 如果你想使用这样的实例作为 HashMap 或者类似容器的键,这会是必需的要求,因为如果不这样,被用作键的对象在加入容器后被修改了,容器可能会进入一种无效的状态。不可变对象同样,特别是在多线程代码中: 一旦一个对象被创建出来,它会一直保持初始状态,也不用担心在你的代码工作时其他线程修改了对象的值 。

    class Client(val name: String, val postalCode: Int){ ... fun copy(name: String = this.name,postalCode: Int = this .postalCode) = Client(name,postalCode) } 调用: val bob = Client("Bob",973293) println(bob.copy(postalCode = 382555)) 输出结果: Client(name = Bob,postalCode = 382555)

    通过copy创建副本是修改实例的好选择:副本有着单独的生命周期而且不会影响代码中引用原始实例的位置

         3.3 类委托:使用"by"关键字

    4."Object"关键字:将声明一个类与创建一个实例结合起来

        4.1 对象声明:创建单例易如反掌

            对象声明通过object关键字引入

    object Payroll { val allErnployees = arrayListOf<Person>{) fun calculateSalary() { for (person in allErnployees){ ... } } } //与变量一样,对象声明允许你使用对象加.字符的方式调用方法和访问属性: Payroll.allErnployees.add(Person(...)) Payroll.calculateSalary()

    对象声明同样可以继承自类和接口

    //使用对象来实现一个忽略大小写比较文件路径的比较器 object CaseInsensitiveFileComparator : Comparator<File> { override fun compare(o1: File, o2: File): Int { return o1.path.compareTo(o2.path, ignoreCase = true) } } 调用: println(CaseInsensitiveFileComparator.compare(File("/User"), File("/user"))) 输出结果:0 //可以在任何可以使用普通对象的地方使用单例对象。例如,可以将这个对象传入一个接收Comparator函数 var list = listOf(File("/Z"),File("/f")) println(list.sortedWith(CaseInsensitiveFileComparator)) 输出结果:[\f, \Z]

           4.2 伴生对象:工厂方法和静态成员的地盘

                Kotlin中的类不能拥有静态成员

                                          私有成员不能在类外部的顶层函数中使用

    companion是一个特殊的关键字,通过这个标记可以获得直接通过容器类名称来访问这个对象的方法和属性的能力,不再需要显式地指明对象的名称。看起来非常像Java的静态方法调用

    class A { companion object { fun bar() { println("Companion object called") } } }

     

    @JvmStatic:该注解可以声明类中的成员为静态

    @JvmField:该注解可在顶层属性或声明在object中的属性上使用

     

    小结:

     Kotlin的接口和Java相似,但是可以包含默认实现和属性

    所有的声明默认都是final和public的

    要想使声明不是final的,将其标记为open

    internal声明在同一模块中可见

    嵌套类默认不是内部类,使用inner关键字来储存外部类的引用

    sealed类的子类只能嵌套在自身的声明中

    使用field标识符在访问器方法体中引用属性的支持字段

    类委托帮忙避免在代码中出现许多相似的委托方法

    对象声明是Kotlin定义单例类的方法

    伴生对象(与包级别函数和属性起)替代了 Java 静态方法和字段定义,伴生对象与其他对象一样,可以实现接口,也可以拥有有扩展函数和属性

     对象表达式是 Kotlin 中针对 Java 匿名内部类的替代品,并增加了诸如实现个接口的能力和修改在创建对象的作用域中定义的变量的能力等功能

     

     

     

    最新回复(0)