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

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 匿名内部类的替代品,并增加了诸如实现多个接口的能力和修改在创建对象的作用域中定义的变量的能力等功能 。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容