Kotlin学习之旅-第三天
今天的主题是:类与继承
前言
今日目标
今天空闲的时候 baidu一下,发现简书,掘金上有很多Kotlin学习总结,但是基本上都是把官方文档一字不落地复制粘贴了过来。我不希望自己也是这样子的,因此在Kotlin学习之旅里面:
- 我只会把最常用的归纳总结起来,其他的大家可以去官方文档进行查看
- 除了官方文档有的知识点,也会加上自己的思考,标注一些不太容易理解,或者容易踩坑的地方
- 根据自己经验,或者评论的意见,不断补充和修改
把这一系列的学习经验写成简洁易懂又实用的文章
话不多说, 今天我们的目标就是搞定下面几个知识点:
- 类与对象基本用法
- 数据类
- 嵌套类
- 内部类
- 继承与接口
Tips:
类与对象基本用法
最基本的用法:
1 | class Day3 |
没有类体,只有类名,连大括号都省了
然后在Day3中加上构造方法
1 | class Day3 constructor(name: String) {...} // 关键字constructor |
但是一般我们看别人写的代码都是没有constructor这个关键字的,为什么呢?
官方文档给了答案:
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字
也就是说,Day3可以写成
1 | class Day3(name: String) |
直接 类名(参数1,参数2){…} 这种格式就可以了
在kotlin里面,只能有一个主构造函数,和多个次构造函数,例如:
1 | class Day3(name: String){ // 1 |
- Day3(…) 括号里的就是主构造函数,只是省略了constructor关键字
- { } 大括号里的 constructor(…) 就是次构造函数,每个次构造函数都要委托给主构造函数
- 实例化一个Day3对象
运行代码的结果:啥都没有~
因为我们没有在主构造函数里面做任何的操作,那么如果我们要做初始化操作,要怎么写呢?
1 | class Day3(name: String){ |
- init就是主构造函数的初始化方法
- 使用主构造函数初始化Day3对象
- 使用次构造函数初始化Day3对象
运行代码的结果:
1 | this is main constructor |
这里输出了两次main和一次second,原因就是上面我们讲到的 每个次构造函数都要委托给主构造函数 ,通过this关键字,在调用次构造函数之前,都会先调用一次主构造函数,因此会有两个main输出~
通过这个例子,应该就能弄懂 类的定义,主/次构造函数,init的用法,如何实例化对象了
数据类
Kotlin中通过 data 关键字来表示数据类:
1 | data class dataClass(val name: String, val age: Int) |
在实际开发中,数据类的使用是很常见的,那么Kotlin里的数据类具有哪些特性呢?
编译器自动从主构造函数中声明的所有属性导出以下成员:
equals()
/hashCode()
对;toString()
格式是"User(name=John, age=42)"
;componentN()
函数 按声明顺序对应于所有属性;copy()
函数(见下文)。
为了确保生成的代码的一致性以及有意义的行为,数据类必须满足以下要求:
- 主构造函数需要至少有一个参数;
- 主构造函数的所有参数需要标记为
val
或var
; - 数据类不能是抽象、开放、密封或者内部的;
因此,有两点需要注意:
- 我们不能像普通类一样 class test(name: String, age: Int) 参数必须标记 val 或 var
- 不能写 data class test(),而是必须至少有一个参数
这样创建数据类,编译器才不会报错~
请注意,对于那些自动生成的函数,编译器只使用在主构造函数内部定义的属性。如需在生成的实现中排出一个属性,请将其声明在类体中
这句话是什么意思呢?举个例子:
1 | data class Person(val name: String) { |
输出结果:
1 | person1 == person2: true |
虽然age不一样,但是 == 结果是 true
说明只有在主构造函数内部定义的属性才具有toString()、 equals()、 hashCode() 、copy()这几个方法,由于属性age定义在类体中,因此是没有的。
最后,标准库提供了Pair和Triple这两个标准数据类
我们来看一下Pair的源码:
1 | public data class Pair<out A, out B>( |
会发现其中他就是key-value格式的数据类,重写了toString()方法,其他的默认属性都是一样的~
在代码中运行一下:
1 | fun main(args: Array<String>) { |
输出结果:
1 | (1, 2) |
Triple也是类似的,这里就不看了。数据类知识点大概就是这么多~
嵌套类与内部类
嵌套在类里面的类,就是嵌套类,这句话说起来比较拗口,直接看代码:
1 | class Outer { // 1 |
- 外部类
- 内部类
通过Outer.Nested().foo()调用内部类的方法
但是这个时候 Nested 类是不能访问Outer类的成员变量的,直接访问的话会报错~
如果需要,要加上inner关键字,让Nested成为内部类
1 | class Outer { |
继承与接口
这部分在实际应用中也是非常重要的,经常都会用到,毕竟抽象封装多态这三大特性,除了类与对象以外基本上就是通过继承和接口实现了
继承
我们都知道,在Java里面是只能实现单继承的,也就是一个子类只能有一个父类,但是通过接口的方式,其实也就相当于实现了多继承,在Kotlin里面也是一样的,我们先说继承
1 | open class Father(name: String) // 1 |
- open class Father就是父类,主构造函数里面需要传入String类型的参数name
- Son继承自Father,需要用父类的主构造函数参数进行初始化
覆盖方法
继承自然免不了要覆盖父类的方法,Kotlin里面通过关键字override 来标识
1 | open class Base { |
- 被覆盖的方法需要用open标识
- 覆盖的方法需要用override标识
那么如果不想让子类继续覆盖要怎么做呢,只要加上final关键字就可以了
1 | open class AnotherDerived() : Base() { |
调用父类实现
在子类中可以通过super关键字来调用父类的属性和方法
1 | open class Foo { |
接口
在Kotlin中,我们使用interface关键字来定义接口
1 | interface MyInterface { // 1 |
- interface 表示接口
- fun xxx() 定义方法名,不需要实现
接口中的继承
1 | interface Named { |
可以看到,Person 继承 Named ,并且重写了 name 属性的get()方法,因此在Employee类实现Person接口的时候,只要实现 firstName, lastName 两个属性就可以了,而不用实现 name 属性
一般来说,我们都会使用到继承+接口两种方式,格式是这样的:
1 | class Day3(name: String) : DayFather(), MyInterface{ // 1,2 |
- 通过 :Father() 来继承父类
- 通过 ,Interface 来实现接口
- 实现接口中定义的方法
总结
- 类与对象基本用法
- 数据类
- 嵌套类
- 内部类
- 继承与接口
这几个知识点我们今天就学习完了,明天我们会继续学习 函数 和 Lambda表达式
Day 3 - Learning Kotlin Trip,Completed.