Kotlin - 类、继承、接口
2017-08-16 本文已影响164人
fish_leong
1.类
-
类(class):使用class关键字声明类,类声明由权限修饰、类名、类头(指定其类型参数、主构造函数等)和由大括号包围的类体构成。类头和类体都是可选的; 如果一个类没有类体,可以省略花括号。
修饰符 class 类名 类头 { //类体,如果没有类体,花括号可省略 } /** * 一个普普通通的类 */ class Simple{ val simple=Simple()//实例化 //成员变量 var num: Int get() { //get方法 return num } set(value) { //set方法 num=value } }
- 修饰符:内部类用inner修饰,抽象类用abstract修饰,其他权限修饰类似Java中的private、public...,也可以修饰类和构造函数的权限,在 [1] 中已经说过不同权限修饰的区别
- 类名:请遵循Kotlin的编码规范,在 [2] 最后一段有讲到
-
类头:包括可选的类型参数、构造函数(constructor)等,根据实际需求而定,没有可以不写
-
构造函数:一个类可以有一个主构造函数和一个或多个次构造函数。可以被权限修饰
-
主构造函数:
- 它是类头的一部分:它跟在类名和可选的类型参数后
/** * 声明有一个主构造函数的类 */ class Fish constructor(name : String){ }
- 如果主构造函数没有任何注解或者可见性修饰符,可以省略constructor关键字
/** * constructor可省略 */ class Fish(name: String) { } /** * constructor不能省略 */ class Fish private constructor(name: String) { }
- 主构造函数不能包含任何的代码,但初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中
/** * 声明有一个主构造函数的类,由于主构造函数只能接收参数,不能包含其他代码,所以想构造时使用参数,可以放到init代码块中 */ class Fish(name: String) { var str:String?=null//Fish类的成员属性,权限修饰符同样试用于它 init { str=name println(str) } }
- 如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面:
class Customer public @Inject constructor(name: String) { }
-
新技能:主构造的参数除了可以像上面示例中在初始化块中使用,它们也可以在类体内声明的属性初始化器中使用(同样可以使用权限修饰符控制成员属性的权限,关于权限修饰符 在[1] 已经说过)
/** * 声明有一个属性的类 */ class Fish(var name: String?) { } /** * age权限是private */ class Leong(private var age: Int?) { private val name="fish_leong"//成员属性的传统写法 } fun main(args: Array<String>) { val fish = Fish("Leong")//在实例化时,将值赋给了实例的name属性 println(fish.name)//打印粗来看看 fish.name = null//name在Fish是用var声明的变量,所以可以再次赋值,这里可以看下之前讲到的《Kotlin - 入门基础》 println(fish.name)//打印粗来看看 val leong=Leong(18) //由于age是private权限,所以无法从外部直接获取leong.age属性的值 }
- 它是类头的一部分:它跟在类名和可选的类型参数后
-
次构造函数:写在类体中的构造函数是次构造函数
-
没有主构造函数的情况
/** * 有一个次构造函数的类 */ class Fish { constructor(name: String) { println(name) } }
-
有主构造函数的情况:如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:
/** * 有次构造函数的类 */ class Fish constructor(name: String?) { /** * 当主构造函数需要参数,次构造函数可提供主构造函数需要的参数 * 当然你可以不提供,如第二个次构造函数 */ constructor(name: String, age: Int) : this(name) { println(name) } constructor(age: Int) : this("default") { println(age) } }
-
主构造函数默认值:在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。这使得 Kotlin 更易于使用像 Jackson 或者 JPA 这样的通过无参构造函数创建类的实例的库。
使用方式和函数的缺省参数一样,在 [3]中讲过)fun main(args: Array<String>) { DefaultParam()//不传参数 DefaultParam("Fish") } /** * 构造函数默认值 */ class DefaultParam constructor(var name: String = "Leong") { init { println(name) } }
-
没有主构造函数的情况
-
主构造函数:
-
构造函数:一个类可以有一个主构造函数和一个或多个次构造函数。可以被权限修饰
-
类体:根据实际需求而定,可以没有
/** * 定义一个没有类体的类 */ class NoBody
-
类的实例化:和Java相比,少了一个关键字——new
val fish=Fish("Leong") val leong=Leong(18)
2.继承
-
超类:在 Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类,是被隐式继承了
-
Any 不是 java.lang.Object,它除了 equals()、hashCode()和toString()外没有任何成员。 更多细节请查阅 Java互操作性 - Kotlin - 中文网 部分。
/** * 我是不能再正常的类...看似没有爹,其实是你们不知道罢了 * 爹如果不开放(open)的话,是不会有子子孙孙(子类...)的 */ open class Normal(yourAge:Int) { /** * 次构造函数 * */ constructor(yourName: String) : this(0) { } } fun main(args: Array<String>) { println(Normal::class.supertypes)//打印Normal的超类,输出:[kotlin.Any],从这可以看出,Normal类隐式继承了Any }
-
Any 不是 java.lang.Object,它除了 equals()、hashCode()和toString()外没有任何成员。 更多细节请查阅 Java互操作性 - Kotlin - 中文网 部分。
-
继承的语法是用冒号(:),并且把父类的类型放到冒号后面;如果又要继承又要实现某些接口,用逗号(,)隔开即可,冒号后的接口和继承不分先后顺序(
class A : B() , C{ fun test() }
)- 如果父类class没有被open修饰的话,它是会不孕不育的(子类无法继承它)!因为默认情况下,在 Kotlin 中所有的类都是 final,对应于 Effective Java书中的第 17 条:要么为继承而设计,并提供文档说明,要么就禁止继承。
- 如果父类只有一个构造函数,并且构造函数需要参数(非缺省参数)的话,那么你必须传递父类所需的参数
- 如果父类有多个构造函数,那么可以根据实际需求选择继承父类时的构造函数
- 如果父类没有主构造函数,但是有次构造函数,那
/** * 我爹是Normal * Simple继承于Normal类,调用的是父类的主要构造函数 */ class Simple : Normal(18) /** * 我爹也是Normal * Complex也继承于Normal,调用的是父类的次构造函数 */ class Complex : Normal("老二") fun main(args: Array<String>) { println(Simple::class.supertypes)//打印Simple的超类,输出:[Normal] println(Complex::class.supertypes)//打印Complex的超类,输出:[Normal] }
-
覆盖(override):Kotlin 力求清晰显式。与 Java 不同,Kotlin 需要显式标注可override的成员(我们称之为开放)和override后的成员,可以override属性和函数
- open:成员能被override的前提要被open修饰,在 [1] 中讲过
- override:属性override与函数override一样,在超类(父类)中声明然后在派生类(子类)中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化器的属性或者具有 getter 方法的属性override。
-
调用超类:派生类中的代码可以使用 super 关键字调用其超类的函数与属性访问器的实现
- 覆盖冲突:在 Kotlin 中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现, 它必须override这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super,如 super<Base>
- 内部类调用超类: 在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer,前面说过,Kotlin的class默认隐式继承Any类,所以不这样写是不行的
下面举个大栗子:
/*** * 服务员,先给我上个接口 */ interface SonAsset { /** * 儿子获取钱的函数 * 默认0元 */ fun getAsset() = 0 } /** * 建立一个父类 */ open class Father { //身高属性,爹年纪大了,身高不会再长了(不要纠结身高缩小...我就举个例子) open val stature: Int? get() { return 180//爹身高180厘米,open修饰了stature,它子类让可以override } //名字属性,name没有open修饰,所以子类不可以override val name: String get() { return "Father" } /** * 获取年龄的函数 * @return 38 年轻有为 */ open fun getAge() = 38 /** * 获取钱的函数 * 爹有这么多钱,可以给儿子花 * @return 10000000 */ open fun getAsset() = 10000000 } /** * Son继承与Father类, * 爹虽然给钱花,但儿子想自食其力,实现SonAsset接口 */ class Son : Father(), SonAsset { //发现没有,Son override 的stature是用var声明的,而Father中的stature是val,这样override可以,但反之,不行 override var stature: Int? = super.stature!!.plus(1)//儿子遗传了父亲基因,又长了1厘米,儿子身高用var声明,才18,身高还是有可能继续长的~ /** * 儿子18岁 * @return 18 */ override fun getAge() = 18 /** * 儿子获取爹的名字 * @return 爹的名字 */ fun getMyFatherName(): String { return super.name//调用父类属性 } /** * IDE要求override getAsset()函数,因为实现SonAsset接口,必须override getAsset方法 * 对于父类中可override的函数,子类可以不override * @return 0 儿子还没赚到钱 */ override fun getAsset(): Int { return 0 } /** * 获取爹的资产 * @return 爹的资产 */ fun getFatherAssets(): Int { /** * 下面代码为什么super后要指定超类的类型? * 因为如果不指定类型,IDE将报错 —— Many supertypes available,please specify the one you mean in angle brackets,e.g. 'super<Foo>' * Son继承Father、实现SonAsset接口,它们都有着 相同参数 和 相同返回值 的getAsset函数,所以要指定类型,区分该调用哪个超类的函数 * 试试 println(Son::class.supertypes)//输出Son的超类[Father, SonAsset],这就是为什么调用super.getAsset方法要指定超类类型的原因 */ return super<Father>.getAsset() } /** * 内部类,女盆友 */ inner class GirlFriend { /** * 查男票他爹的资产 * */ fun getHisFatherAsset() { val hisFatherAsset = super<Father>@Son.getAsset()//内部类调用Son超类的方法 } /** * 查男票的资产 */ fun getHisAsset() { val hisAsset = getAsset() } } }
3.抽象类
-
类和其中的某些成员可以声明为 abstract。 抽象成员在本类中可以不用实现。 需要注意的是,我们并不需要用 open标注一个抽象类或者函数——因为这不言而喻。
- abstract:只有在抽象类(abstract)中才能使用abstract修饰此抽象类中属性或成员
//普通class需要open修饰才能被继承,但这是抽象类,要使用它只能继承,Kotlin有着聪明的语法检查,所以open当然可以不写了 abstract class Base { //成员属性抽象了...成员属性不能有初值,还有子类必须override! abstract var name: String? abstract var sex: String? fun no() { println("就不让你override") } abstract fun test() //抽象类中,被abstract修饰的函数,可以被子类override,不用写方法体,为你省了俩字符 open fun test2() {} //被open修饰的方法,虽然也可以被子类override,但这样写,必须要写方法体 } /*** * 继承于Base类,除了no函数,其他函数都可以override */ open class Child : Base() { //对于抽象属性,不override不行了...并且要赋初值 override var name: String? = "fish_leong" override var sex: String? = "Girl" override fun test() { } override fun test2() { } } /** * 再来一个抽象类,继承于Child */ abstract class Three : Child() { override abstract fun test()//用一个抽象成员override一个非抽象的开放成员 override abstract fun test2()//同上 }
-
伴生对象
与 Java 或 C# 不同,在 Kotlin 中类没有静态方法。在大多数情况下,它建议简单地使用包级函数。如果你需要写一个可以无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法),你可以把它写成该类内 对象声明 - Kotlin - 中文网 中的一员
更具体地讲,如果在你的类内声明了一个 伴生对象 - Kotlin - 中文网, 你就可以使用像在 Java/C# 中调用静态方法相同的语法来调用其成员,只使用类名作为限定符
4.接口
- Kotlin 的接口与 Java 8 类似,既包含抽象方法的声明,也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
- 使用关键字 interface 来定义接口
- 若一个类实现了一个或多个接口,那这个(些)接口也是该类的超类(supertypes)
/** * 接口A */ interface A { var name: String? //接口中的属性 fun test() //接口中的函数 /** * //接口中的函数 */ fun printName() { println("A") } } /** * 接口B与接口A一样 */ interface B { var name: String? fun test() fun printName() { println("B") } } /** * 父类C */ open class C { /** * 与接口A中printName函数名、参数和返回值相同 */ open fun printName() { println("B") } } /** * 类D,实现A和B接口,并继承于C */ class D : A, B, C() { override var name: String? = "Leong"//override接口中的name //override接口中的函数 override fun test() { /** * 与上面讲的继承一样,若实现的接口或继承的超类中, * 出现 函数名相同、参数相同、返回值相同的函数, * 调用超类中的成员时,需要指定超类类型 * 如下: */ super<A>.printName() super<B>.printName() super<C>.printName() } //override接口中的函数 override fun printName() { } }
参考文档:
[1]类与继承 - Kotlin 语言中文站