美文网首页
《Kotlin 实战》- 4 类、对象和接口

《Kotlin 实战》- 4 类、对象和接口

作者: Ronnie_火老师 | 来源:发表于2019-06-18 10:25 被阅读0次

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

    • 默认情况下 Kotlin 的嵌套类不能访问外部类的实例(不持有外部类的引用),如果需要变成内部类以持有外部类的引用,需要给嵌套类使用 inner 修饰符

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

    • sealed 修饰符作用于父类时,对可能创建的子类做出严格限制,所有直接子类必须嵌套在父类中,如此,当使用 when 处理所有子类时,如果添加了新子类,有返回值的 when 表达式会有编译错误,这样强制添加新分支,以免遗漏造成错误。
    • sealed 不能作用在接口上,因为 Kotlin 不能保证“不能在 Java 中实现这个接口”

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

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

    • 同 Java 一样,Kotlin 也可以声明多个构造方法,只是增加了主构造方法从构造方法概念,并且增加了初始化语句块以添加额外的初始化逻辑。
    • 举例
    class User constructor(_nickname: String){ // 带一个参数的主构造方法
        
        val nickname: String
        
        init{
            nickname = _nickname
        }
    }
    
    • constructor 关键字用来声明一个主构造方法或从构造方法,当主构造方法没有注解或可见性修饰符时(如本例),constructor 关键字可以省略。
    • init 关键字表示一个初始化语句块,语句块中的代码会和主构造函数一起使用,其实就是弥补主构造函数不能包含初始化代码的问题。本例中也没有必要使用 init 代码块,因为赋值可以和属性声明结合。
    • val 关键字又可以放在参数前面来简化,这样来替换类中的属性定义,上面结构最终简化版如下:
    class User(val nickname: String)
    
    • 如果父类有带参构造方法,需要在基类列表中使用父类带参构造方法,如果父类没有声明构造方法,则基类列表中使用父类无参构造方法:(),而实现的接口不需要这么做。
    • 如果要确保类不被外部实例化,需要把构造方法标记为 private,做法是,在 constructor 前面加上 private

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

    • 如果有必要,可以使用 constructor 关键字定义任意多个从构造方法。
    • 子类中依然可以使用 super 指定父类构造方法。
    • 同类中也可以使用 this 在一个构造方法中调用另一个构造方法,语法与 Java 有所不同:
    constructor(ctx: Context): this(ctx,MY_STYLE){
        // ...
    }
    

    4.2.3 实现在接口中声明属性

    • kotlin 中接口可以包含抽象属性(所谓抽象属性,是属性不能存储状态,简单说就是只声明不赋值),接口的实现类可以在主构造方法、自定义getter、属性初始化的位置给接口中的属性赋值,但都需要加入 override 关键字。
    • 这里说的 getter 是定义字段并加入自定义 get()=xxx,属性赋值是直接用 = 赋值,二者是有区别的,自定义 getter 每次从 get() 方法中获取数据,每次调用获得的值不一定相同;属性赋值是存储值,后面每次使用都拿到同样的结果。
    • 除了抽象属性外,接口可以包含具有 getter 和 setter 的属性,但是他们不能引用一个支持字段(支持字段需要在接口中存储状态)

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

    • 前面介绍了存储值的属性和具有自定义访问器的属性,下面实现一个既可以存储值又可以在值被访问和修改时提供额外逻辑的属性(具有getter/setter的属性)。
    class User(val name: String){
        var address: String = "unspecified"
            set(value: String){
                println(""" 被改了:"$field" -> "$value" """)
                field = value
            }
    }
    
    • 注意,在 setter 中使用特殊标识符 field 来访问支持的字段的值。
    • 访问器可见性默认与属性可见性相同,也可以通过在 get 和 set 前加可见性修饰符来修改它。

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

    • 关于 ==
      • == 在 Java 中表示值是否相等(基本类型就是指,引用类型是内存中存储的引用(地址)是否相等,其实也是值,也常说是否引用同一个对象);而 Kotlin 中是调用 equals 来比较的,如果要达到和 Java 中一样的效果,需要使用 === 符号
    • Kotlin 中的 is 是 java 中 instanceof 的模拟;Any 是 Object 的模拟,也是 Kotlin 中所有类的父类。
    • 在 equals hashCode 契约上,Kotlin 保持了和 Java 一样的规范,比如:重写 equals 时必须重写 hashCode;两个对象相等,hashCode 必须相等,反之不成立等等。
    • 如果想让一个类是一个方便的数据容器,需要重写 toString、equals、hashCode方法,Kotlin 添加 data 关键字,只要在类前标注 data 修饰符,必要的方法将会自动生成。
      • 生成规则是:hashCode 和 equals 只会考虑主构造方法中声明的属性,hashCode 返回一个根据所有属性生成的哈希值,equals 会检测所有属性的值是否相等。
      • 结合上述说明,对于 data class 对象,只要属性值相等,在 Kotlin 中使 == 比较就应该返回 true。
    • 强烈建议数据类的属性都设为 val 的。
    • 类委托:使用 by 关键字
      • 经常有这样的场景:要扩展或者代理 A 类(假设 A 实现了 IA 接口),通常借助装饰器模式思想,添加一个 B 类,也实现 IA 接口,并且 B 拥有一个 A 的实例,在大多数不需要修改的方法中,直接在 B 类中调用 A 对象对应的方法。一般大多数方法都是不需要修改的,所以会写一堆重复逻辑的代码。
      • Kotlin 为此增加了 by 关键字,来直接实现委托。编译器会默认生成所有委托方法,上述场景示例如下:
        class B{
            val a: A
        }: IA by a{
            // 需要重写的方法在这里写
        }
    

    4.4 object 关键字:将声明一个类与创建一个实例结合起来

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

    • 该关键字定义一个类并同时创建一个实例(对象),他的使用场景主要有:
      • 单例
      • 伴生对象
      • 对象表达式,用来替代 Java 中的匿名内部类
    • object 不允许有构造方法(无论主、从),因为对象声明在定义的时候就立即创建了,不需要调用构造方法。
    • Kotlin 中的对象声明其实被编译成了通过静态字段来持有他的单一实例的类,这个字段名固定为 INSTANCE。所以在 java 中访问 Kotlin 对象时,可以通过访问 INSTANCE 字段,如 TestClass . INSTANCE .**
    • object 也可以定义在类的内部,它同样具有单例特性

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

    • Kotlin 的类不能拥有静态成员,java 中的 static 并不是 Kotlin 的一部分。作为替代,Kotlin 中使用包级别函数(顶层函数)和对象声明(object),但顶层函数不能访问类的 private 成员。
    • 在类内可以通过 object 关键字声明类内对象,该对象可以访问外部类的私有成员,如果在 object 前使用 companion 标记,又可以使用外部类的名称直接访问 object 对象的方法和属性,就像 Java 中静态方法调用。
    class A {
        companion object {
            fun bar() {
                println (”Companion object called")
            }
        }
    }
    // 调用示例
    > > A.bar()
    Companion object called
    
    • 伴生对象可以访问外部类的所有 private 成员,包括 private 构造方法,这是实现工厂模式的理想选择。

    4.4.3 作为普通对象使用的伴生对象

    • 不知是否注意到,上面示例中的伴生对象并没有起名字,因为多数情况下通过外部类名就可以访问伴生对象的方法,但是也可以写上名字,比如上例中如果伴生对象起名为 B ,可以通过 A.B.bar() 进行访问,效果和 A.bar() 是等价的。不起名字时默认分配的名字为 Companion,在 java 中就可以通过 Companion 来访问。
    • 伴生对象的原理:伴生对象会被编译成常规对象,类中有一个引用了它的实例的静态字段。
    • 伴生对象既然是一个声明在类中的普通对象,就可以实现接口或扩展函数或属性。
      • 如果定义了伴生对象的扩展方法,依然可以使用外部类直接访问该扩展方法。

    4.4.4 对象表达式:改变写法的匿名内部类

    • 比如常用的设置监听,可以直接写成 object: xxListener(){},这里依然是声明一个类并创建了类的实例,但并没有分配名字(匿名),也可以给对象分配名字,写为:val listener = object: xxListener(){}
    • 像 Java 匿名类一样,对象表达式可以访问创建它的函数中的变量,但与 Java 不同的是,访问并没有被限制为 final 变量,可以在对象表达式中修改变量的值。
    • 更有趣的是,对象表达式对于需要重写多个方法的匿名对象非常有用,但对于只需要实现一个单方法的接口,使用 lambda 更为方便。

    4.5 小结

    • Kotlin 的接口与 Java 的相似,但是可以包含默认实现和属性。
      Oava 从第 8 版才开始
    • 所有的声明默认都是 final 和 public 的。
    • 要想使声明不是 final 的,将其标记为 open。
    • internal 声明在同一模块中可见。
    • 嵌套类默认不是内部类。使用 inner 关键字来存储外部类的引用。
    • sealed 类的子类只能嵌套在自身的声明中。
    • 初始化语句块和从构造方法为初始化类实例提供了灵活性。
    • 使用 field 标识符在访问器方法体中引用属性的支持字段 。
    • 数据类提供了编译器生成的 equals、hashCode、toString、copy 和其
      他方法。
    • 类委托帮助避免在代码中出现许多相似的委托方法。
    • 对象声明是 Kotlin 中定义单例类的方法。
    • 伴生对象(与包级别函数和属性一起)替代了 Java 静态方法和字段定义。
    • 伴生对象与其他对象一样,可以实现接口,也可以拥有有扩展函数和属性 。
    • 对象表达式是 Kotlin 中针对 Java 匿名内部类的替代品,并增加了诸如实现多个接口的能力和修改在创建对象的作用域中定义的变量的能力等功能 。

    相关文章

      网友评论

          本文标题:《Kotlin 实战》- 4 类、对象和接口

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