Android开发之Kotlin(二)

一、继承与多态

继承与多态
1 Kotlin中的继承

package com.a51work6.section1

import java.util.*

class Student : Person() {// 所在学校
val school:String? = null

override val info: String
    get() =("Person [name=$name,age=$age,birthDate=$birthDate]")
}
package com.a51work6.section1

import java.util.*

open class Person {//: Any() { ②
// 名字
val name:String? = null
// 年龄
val age: Int =0
// 出生日期
val birthDate:Date? = null

open val info:String
    get() =("Person [name=$name,age=$age,birthDate=$birthDate]")
}

上述代码可见Student类继承了Person类中的成员属性和函数,代码第①行声明Student继承Person,继承使用的冒号(:),冒号前是子类,冒号后是父类。
如果在类的声明中没有使用指明其父类,则默认父类为Any类,kotlin.Any类是Kotlin的根类,所有Kotlin类包括数组都直接或间接继承了Any类,在Any类中定义了一些有关面向对象机制的基本函数,如equals、toString和hashCode等函数。
子类能够继承父类,那么父类需要声明为open,见代码第②行,在Kotlin中默认类不能被继承必须声明为open的。
在这里插入图片描述
2 调用父类构造函数
当子类实例化时,不仅需要初始化子类成员属性,也需要初始化父类成员属性,初始化父类成员属性需要调用父类构造函数。
父类Person代码如下:

package com.a51work6.section2

import java.util.*

open class Person(val name: String,
val age: Int,
val birthDate: Date)
{ //主构造函数
//次构造函数
constructor(name: String, age: Int) :this(name, age, Date())

override fun toString(): String {
    return("Person [name=$name, age=$age, birthDate=$birthDate]")
	}
}

Person类中有两个构造函数,分别是一个主构造函数和一个次构造函数。子类Student继承Person类有多重实现方式。
2.1 使用主构造函数
在子类Student中可以声明主构造函数和次构造函数。示例代码如下:

import com.a51work6.section2.Person
import java.util.*

class Student(name: String,
age: Int,
birthDate: Date,
val school: String) :Person(name, age, birthDate) { //主构造函数 ①

constructor(name: String, //次构造函数                 
            age: Int,
            school: String) :this(name, age, Date(), school)    //  super(name, age, Date())     ②
            
constructor(name: String, //次构造函数
            school: String) :this(name, 18, school)      //super(name, 18, Date())                ③

}

上述代码第①行是声明Student的主构造函数,主构造函数中val school: String参数,说明会生成属性school,Person(name,
age, birthDate)表达式是调用父类构造函数。代码第②行是声明Student的次构造函数,this(name, age, Date(), school)是调用自己的主构造函数帮助完成初始化,如果将this(name, age, Date(), school)表达式换成super(name, age, Date())则会发生编译错误,super(name, age, Date())是次构造函数中调用父构造函数。代码第③行也是声明Student的次构造函数,this(name, 18, school)是调用自己的代码第②行的次构造函数帮助完成初始化,如果将this(name, 18, school)表达式换成super(name, 18, Date())则会发生编译错误,super(name, 18, Date())是次构造函数中调用父构造函数。
在这里插入图片描述
2.2 使用次构造函数重载
在子类Student中可以不声明主构造函数,可以声明多个次构造函数。示例代码如下:

package com.a51work6.section2.s2

import com.a51work6.section2.Person
import java.util.*

class Student : Person {undefined
// 所在学校
private var school: String? = null

constructor(name: String,
            age: Int,
            birthDate: Date,
            school: String) :super(name, age, birthDate) {this.school = school
	}

constructor(name: String,
            age: Int,
            school: String) : this(name,age, Date(), school) {this.school = school
	}
}

上述代码第①行和第②行都是声明次构造函数,其中代码第①行的次构造函数中super(name, age, birthDate)表达式是调用父构造函数。代码第②行的次构造函数中this(name, age, Date(), school)表达式是调用代码第①行自己的次构造函数。在这里插入图片描述
2.3 使用参数默认值调用构造函数
一个类有多个多个造函数时,多个构造函数之间构成了重载关系,Kotlin从语法角度是支持重载的,但更推荐采用参数默认值方式。
示例代码如下:

package com.a51work6.section2.s3

import com.a51work6.section2.Person
import java.util.*

class Student : Person {undefined
// 所在学校
private var school: String? = null

constructor(name: String,
            age: Int = 18, 
            birthDate: Date = Date(),
            school: String) :super(name, age, birthDate) {
    this.school = school
	}
}

上述代码中只是声明了一个次构造函数,它有4个参数,其中age和birthDate参数提供了默认值。这样声明相当于提供了3个构造函数,调用代码如下:

package com.a51work6.section2.s3

import java.util.*

fun main(args: Array) {undefined
val stu1 = Student(“Tony”,20, Date(), “清华大学”)
val stu2 = Student(“Tony”,birthDate = Date(9823456), school = “清华大学”)
val stu3 = Student(“Tony”,school = “清华大学”)
}

上述代码只有一个次构造函数,事实上也可以只有一个主构造函数,示例代码如下:

package com.a51work6.section2.s3

class Student(name: String,
age:Int = 18,
birthDate: Date = Date(),
val school: String) : Person(name, age, birthDate) {undefined
val info:String
    get() =("Student [name=$name,age=$age,birthDate=$birthDate,school=$school]")
}

3 重写成员属性和函数
3.1 重写成员属性
子类成员属性与父类一样,会重写(Override)父类中的成员属性,也就是屏蔽了父类成员属性。示例代码如下:

package com.a51work6.section3.s1

open class ParentClass {undefined
// x成员属性
open var x = 10}

internal class SubClass : ParentClass() {undefined
// 屏蔽父类x成员属性
override var x = 20fun print() {
    // 访问子类x成员属性
    println("x = " + x)// 访问父类x成员属性
    println("super.x = " +super.x)}
}

调用代码如下:

package com.a51work6.section3.s1

fun main(args: Array) {undefined
//实例化子类SubClass
val pObj =SubClass()
//调用子类print函数
pObj.print()
}

运行结果如下:

x = 20
super.x = 10

上述代码第①行是在ParentClass类声明x成员属性,那么在它的子类SubClass代码第②行也声明了x成员属性,它会屏蔽父类中的x成员属性。那么代码第③行的x是子类中的x成员属性。如果要调用父类中的x成员属性,则需要super关键字,见代码第④行的super.x。
在这里插入图片描述
3.2 重写成员函数
如果子类函数完全与父类函数相同,即:相同的函数名、相同的参数列表和相同的返回类型,只是函数体不同,这称为子类重写(Override)父类函数。
示例代码如下:

package com.a51work6.section3.s2

open class ParentClass {undefined
// x成员属性
open var x: Int = 0open protected fun setValue() { ②
    x = 10}
}
class SubClass : ParentClass() {undefined
// 屏蔽父类x成员属性
override var x: Int = 0public override fun setValue() { // 重写父类函数    ⑤
    // 访问子类对象x成员属性
    x = 20// 调用父类setValue()函数
    super.setValue()}

fun display() {
    // 访问子类对象x成员属性
    println("x = " + x)
    // 访问父类x成员属性
    println("super.x = " +super.x)
	}
}

调用代码如下:

package com.a51work6.section3.s2

fun main(args: Array) {undefined
	//实例化子类SubClass
	val pObj = SubClass()
	//调用setValue函数
	pObj.setValue()
	//调用子类print函数
	pObj.display()
}

上述代码第②行是在ParentClass类声明setValue函数,那么在它的子类SubClass代码第⑤行重写父类中的setValue函数,在声明函数时添加override关键字声明。当在main函数中调用子类setValue函数时,首先在代码第⑥行修改x属性为20。紧接着在代码第⑦行调用父类的setValue函数,在该函数中将x属性修改为10,注意此时修改的属性是子类中x属性(代码第④行声明的属性),而不是父类中的x属性(代码第①行声明的属性),所以最后输出的结果是:

x = 10
super.x = 0

在这里插入图片描述
在这里插入图片描述
4 多态
4.1 多态概念
发生多态要有三个前提条件:
1.继承。多态发生一定要子类和父类之间。
2.重写。子类重写了父类的函数。
3.声明对象类型是父类类型,对象是子类的实例。
父类Figure(几何图形)类有一个onDraw(绘图)函数,Figure(几何图形)它有两个子类Ellipse(椭圆形)和Triangle(三角形),Ellipse和Triangle重写onDraw函数。Ellipse和Triangle都有onDraw函数,但具体实现的方式不同。
在这里插入图片描述
具体代码如下:

package com.a51work6.section4.s1

open class Figure {undefined

	//绘制几何图形函数
	open fun onDraw() {
	    println("绘制Figure...")
	}
}
package com.a51work6.section4.s1

//几何图形椭圆形
class Ellipse : Figure() {undefined
	//绘制几何图形函数
	override fun onDraw() {undefined
	println(“绘制椭圆形…”)
	}
}

package com.a51work6.section4.s1

//几何图形椭圆形
class Ellipse : Figure() {undefined
	//绘制几何图形函数
	override fun onDraw() {undefined
	println(“绘制椭圆形…”)
	}
}

调用代码如下:

package com.a51work6.section4.s1

fun main(args: Array) {undefined
	// f1变量是父类类型,指向父类实例
	val f1 = Figure()         ①
	f1.onDraw()
	
	// f2变量是父类类型,指向子类实例,发生多态
	val f2: Figure = Triangle()          ②   
	
	f2.onDraw()

	// f3变量是父类类型,指向子类实例,发生多态
	val f3: Figure = Ellipse()             ③
	f3.onDraw()
	
	// f4变量是子类类型,指向子类实例
	val f4 = Triangle()      ④
	
	f4.onDraw()
}

上述带代码第②行和第③行是符合多态的三个前提,因此会发生多态。而代码第①行和第④行都不符合,没有发生多态。
运行结果如下:
绘制Figure…
绘制三角形…
绘制椭圆形…
绘制三角形…
从运行结果可知,多态发生时,Kotlin运行时根据引用变量指向的实例调用它的函数,而不是根据引用变量的类型调用。
4.2 使用is和!is进行类型检查
有时候需要在运行时判断一个对象是否属于某个类型,这时可以使用is或!is运算符,语法格式如下:

obj is type // obj对象是type类型实例,则返回true
obj !is type //obj对象不是type类型实例,则返回true

其中obj是一个对象,type是数据类型。
为了介绍引用类型检查,先看一个示例,如图所示的类图,展示了继承层次树,Person类是根类,Student是Person的直接子类,Worker是Person的直接子类。
在这里插入图片描述
继承层次树中具体实现代码如下:

package com.a51work6.section4.s2

open class Person(val name: String, val age: Int) {undefined
	override fun toString(): String {
	    return ("Person [name=$name,age=$age]")
	}
}
package com.a51work6.section4.s2

class Student(name: String, age: Int, private val school:
String) : Person(name, age) {undefined
	override fun toString(): String {undefined
	return(“Student [school=s c h o o l , n a m e = school,name=school,name=name,age=$age])
	}
}
package com.a51work6.section4.s2

class Worker(name: String, age: Int, private val factory:String) : Person(name, age) {undefined
	override fun toString(): String {
	    return ("Worker [factory=$factory,name=$name,age=$age]")
	}
}

调用代码如下:

package com.a51work6.section4.s2

fun main(args: Array) {undefined
	val student1 =Student("Tom", 18, "清华大学")val student2 =Student("Ben", 28, "北京大学")
	val student3 =Student("Tony", 38, "香港大学")val worker1 =Worker("Tom", 18, "钢厂")val worker2 =Worker("Ben", 20, "电厂")val people =arrayOf(student1, student2, student3, worker1, worker2) ⑤

	varstudent Count = 0
	var worker Count= 0
	workerCount++
	} else if (item is Student) { ⑧
	studentCount++
		}
	}
	println(“工人人数:w o r k e r C o u n t , 学 生 人 数 : workerCount,学生人数:workerCount,学生人数:studentCount”)
	println(worker2 !is Worker)println(0 is Int)}

输出结果如下:

工人人数:2,学生人数:3
false
true

上述代码第①行和第②行创建了3个Student实例,代码第③行和第④行创建了两个Worker实例,然后程序把这5个实例放入people数组中。
代码第⑥行使用for循环people数组集合,当从people数组中取出元素时,元素类型是People类型,但是实例不知道是哪个子类(Student和Worker)实例。代码第⑦行item is Worker表达式是判断数组中的元素是否是Worker实例;类似地,第⑧行item is Student表达式是判断数组中的元素是否是Student实例。
代码第⑨行是使用!is判断worker2不是否Worker实例,结果为false。
代码第⑩行是使用is基本数据类型0是否为Int类型实例,可见is和!is也可以用于基本数据类型。

4.3 使用as和as?进行类型转换
数值类型相互转换,引用类型也可以进行转换,但并不是所有的引用类型都能互相转换,只有属于同一棵继承层次树中的引用类型才可以转换。

package com.a51work6.section4.s3

fun main(args: Array) {undefined
	val p1: Person =Student("Tom", 18, "清华大学")
	val p2: Person =Worker("Tom", 18, "钢厂")
	
	val p3 = Person("Tom", 28)
	val p4 = Student("Ben", 40,"清华大学")
	val p5 = Worker("Tony", 28,"钢厂")
}

上述代码创建了5个实例p1、p2、p3、p4和p5,它们的类型都是Person继承层次树中的引用类型,p1和p4是Student实例,p2和p5是Worker实例,p3是Person实例。首先,对象类型转换一定发生在继承的前提下,p1和p2都声明为Person类型,而实例是由Person的子实例化的。
下表归纳了p1、p2、p3、p4和p5这5个实例与Worker、Student和Person这3种类型之间的转换关系。
在这里插入图片描述
引用类型转换有两个方向:将父类引用类型变量转换为子类类型,这种转换称为向下转型(downcast);将子类引用类型变量转换为父类类型,这种转换称为向上转型(upcast)。向下转型需要使用as或as?运算符进行强制转换;而向上转型是自动的,也可以使用as运算符。
在这里插入图片描述
下面通过示例详细说明一下向下转型和向上转型,在main函数中添加如下代码:

// 向上转型
val p41: Person = p4 //as Person ①
val p51 = p5 as Person ②

// 向下转型
val p11= p1 as Student ③
val p21= p2 as Worker ④

val p211 = p2 as? Student //使用as会发生运行时异常 ⑤
val p111 = p1 as? Worker //使用as会发生运行时异常 ⑥
val p311 = p3 as? Student //使用as会发生运行时异常 ⑦

上述代码第①行将p4对象转换为Person类型,p4本质上是Student实例,这是向上转型,这种转换是自动的,可以使用as进行强制类型转换。代码第②行没有声明p51类型,而是将p5对象转换为Person类型,这个过程可以成功。
代码第③行和第④行是向下类型转换,它们的转型都能成功。而代码第⑤、⑥、⑦行转换类型是不兼容的,如果as进行转换会发生运行时异常ClassCastException,所以这里使用了as?进行转换,当然转换的结果都是空值。

5 密封类
果一个类它的子类的个数是有限的,那么在Kotlin中可以把这种父类定义为密封类(Sealed Classes),密封类是一种抽象类,它限定了子类个数。密封类类似于枚举类,
枚举类中每个常量实例只能有一个,而密封类的子类实例可以有多个。
下面通过示例介绍一下密封类使用,在进行数据库操作时,会出现成功和失败两种情况。如果采用密封类设计,代码如下:

package com.a51work6.section5

sealed class Result ①
class Success(val message: String) : Result()class Failure(val error: Error) : Result()fun onResult(result: Result) {undefined
	when (result) {is Success-> println("r e s u l t 输 出 成 功 消 息 : {result}输出成功消息:result输出成功消息:{result.message}")
	is Failure-> println("r e s u l t 输 出 失 败 消 息 : {result}输出失败消息:result输出失败消息:{result.error.message}")
	//else-> 不再需要
		}
	}

	fun main(args: Array) {undefined
	val result1 =Success("数据更新成功")   
	onResult(result1)
	val result2 =Failure(Error(“主键重复,插入数据失败”))
	onResult(result2)
}

上述代码第①行是声明一个密封类Result,使用sealed修饰。密封类本身就是抽象的不需要abstract修饰,一定也是open的,密封类不能实例化。代码第②行和第③行是都是声明密封类的子类,但是Succuess和Failure内部结构是不同的,Succuess有一个字符串属性message,而Failure有一个Error类型属性。
代码第④行使用when结果判定密封类实例,注意不再需要else结构。
在这里插入图片描述
密封类的子类还可以写成嵌套类形式,这是Kotlin1.1之前密封类规范,Kotlin1.1在仍然可以使用这些形式。示例代码如下:

sealed class ContentType {class Text(val body: String) : ContentType()class Image(val url: String, val caption: String) : ContentType()class Audio(val url: String, val duration: Int) : ContentType()}

fun renderCotent(contentType: ContentType): Unit {undefined
	when (contentType){ ⑤
	isContentType.Text -> println(“文本:c o n t e n t T y p e . b o d y " ) ⑥ i s C o n t e n t T y p e . A u d i o − > p r i n t l n ( " 音 频 : {contentType.body}") ⑥ isContentType.Audio -> println("音频:contentType.body")⑥isContentType.Audio−>println("音频:{contentType.duration}秒”) ⑦
	isContentType.Image -> println(“图片:${contentType.caption})}
}

上述代码第①行是声明密封类ContentType,它表示浏览器能够渲染的内容。ContentType内部嵌套三个子类,代码第②行Text是文本子类,代码第③行Image是图片子类,代码第④行Audio是音频子类,这三个子类都有不同的结构和不同的构造函数。
代码第⑤行使用when结构判断ContentType子类实例,代码第⑥行~第⑧行是判断为文本子类实例,注意访问子类时需要添加前缀ContentType。

二、抽象类与接口

1 抽象类
Kotlin语言提供了两种类:一种是具体类;另一种是抽象类。

13.1.1 抽象类概念
在12.4.1节介绍多态时,使用过几何图形类示例,其中Figure(几何图形)类中有一个onDraw(绘图)函数,Figure有两个子类Ellipse(椭圆形)和Triangle(三角形),Ellipse和Triangle重写onDraw函数。
作为父类Figure(几何图形)并不知道在实际使用时有多少个子类,目前有椭圆形和三角形,那么不同的用户需求可能会有矩形或圆形等其他几何图形,而onDraw函数只有确定是哪一个子类后才能具体实现。Figure中的onDraw函数不能具体实现,所以只能是一个抽象函数。在Kotlin中具有抽象函数的类称为“抽象类”,Figure是抽象类,其中的onDraw函数是抽象函数。如图所示类图中Figure是抽象类,Ellipse和Triangle是Figure子类实现Figure的抽象函数onDraw。
在这里插入图片描述
1.2 抽象类声明和实现
在Kotlin中抽象类和抽象函数的修饰符是abstract,声明抽象类Figure示例代码如下:

package com.a51work6.section1

abstract class Figure {//绘制几何图形函数
	abstract fun onDraw() //抽象函数 ②
	abstract val name: String  //抽象属性         ③
	val cname: String = "几何图形" //具体属性   ④
	
	fun display() {//具体函数           ⑤
	    println(name)
	}
}

代码第①行是声明抽象类,在类前面加上abstract修饰符,这里不需要使用open修饰符,默认是open。代码第②行声明抽象函数,函数前面的修饰符也是abstract,也需要不需要使用open修饰符,默认也是open,抽象函数没有函数体。代码第③行的属性是抽象属性,所谓“抽象属性”是没有初始值,没有setter或getter访问器。代码第④行的属性是,所谓“具体属性”它有初始值或者有setter或getter访问器。代码⑤行是具体函数,它有函数体。
在这里插入图片描述
设计抽象类目的就是让子类来实现的,否则抽象就没有任何意义,实现抽象类示例代码如下:

package com.a51work6.section1

//几何图形椭圆形
class Ellipse : Figure() {
	override val name: String ①
	get() = “椭圆形”
	//绘制几何图形函数
	override fun onDraw() {println("绘制椭圆形...")
	}
}
package com.a51work6.section1

//几何图形三角形
class Triangle(override val name: String) : Figure() {// 绘制几何图形函数
	override fun onDraw() {println(“绘制三角形…”)
	}
}

上述代码声明了两个具体类Ellipse和Triangle,它们实现(重写)了抽象类Figure的抽象函数onDraw,见代码第②行和第④行。代码第①行是Ellipse中实现name属性,在父类Figure中name属性是抽象的。代码第③行是实现在构造函数中提供了name属性,从而实现了name属性。比较代码第①行和第③行实现属性name方式有所不同,但是最终效果是一样的。
调用代码如下:

package com.a51work6.section1

fun main(args: Array) {
	// f1变量是父类类型,指向实现类实例,发生多态
	val f1: Figure = Triangle(“三角形”) ①
	f1.onDraw()
	f1.display()// f2变量是父类类型,指向实现类实例,发生多态
	val f2: Figure = Ellipse()
	f2.onDraw()  
	println(f2.cname)}

上述代码中实例化两个具体类Triangle和Ellipse,对象f1和f2是Figure引用类型。代码第①行是实例化Triangle对象,代码第②行是调用抽象类中的具体函数display()。代码第③行是调用抽象类中的具体属性cname。
在这里插入图片描述
2 使用接口
比抽象类更加抽象的是接口,接口中主要应该包含抽象函数和抽象属性,但是根据需要可以有具体函数和属性。
在这里插入图片描述
2.1 接口概念
其实抽象类Figure可以更加彻底,即Figure接口,虽然接口中可以有函数和属性,也有具体函数和属性,但接口不保存状态。将之前的几何图形类改成接口后,类图如图所示。
在这里插入图片描述
2.2 接口声明和实现
在Kotlin中接口的声明使用的关键字是interface,声明接口Figure示例代码如下:

package com.a51work6.section2.s2

interface Figure {//绘制几何图形函数
	fun onDraw() //抽象函数 ②
	val name: String  //抽象属性    ③
	
	val cname: String //具体属性     ④
	    get() = "几何图形"
	    
	fun display() {//具体函数           ⑤
	    println(name)
	}
}

代码第①行是声明Figure接口,声明接口使用interface关键字。代码第②行声明抽象函数,抽象函数没有函数体。代码第③行的属性是抽象属性,抽象属性是没有初始值,没有setter或getter访问器。代码第④行的具体属性,具体属性不能有初始值只能有getter访问器,说明该属性后面没有支持字段。代码⑤行是具体函数,它有函数体。
实现接口Figure示例代码如下:

package com.a51work6.section2.s2

//几何图形椭圆形
class Ellipse : Figure {
	override val name: String
	get() = “椭圆形”
	//绘制几何图形函数
	override fun onDraw() {
	    println("绘制椭圆形...")
	}
}
package com.a51work6.section2.s2

//几何图形三角形
class Triangle(override val name: String) : Figure {
	// 绘制几何图形函数
	override fun onDraw() {
	println(“绘制三角形…”)
	}
}

上述代码声明了两个具体类Ellipse和Triangle,它们实现了接口Figure中的抽象函数onDraw和抽象属性name。
调用代码如下:

package com.a51work6.section2.s2

fun main(args: Array) {
	// f1变量是接口类型,指向实现类实例,发生多态
	val f1: Figure = Triangle(“三角形”)
	f1.onDraw()
	f1.display()
	// f2变量是接口类型,指向实现类实例,发生多态   
	val f2: Figure = Ellipse()
	f2.onDraw()
	println(f2.cname)
}

上述代码中实例化两个具体类Triangle和Ellipse,对象f1和f2是Figure接口引用类型。
在这里插入图片描述
2.3 接口与多继承
在C++语言中一个类可以继承多个父类,但这会有潜在的风险,如果两个父类有相同的函数,那么子类将继承哪一个父类函数呢?这就是C++多继承所导致的冲突问题。
在Kotlin中只允许继承一个类,但可实现多个接口。通过实现多个接口方式满足多继承的设计需求。如果多个接口中即便有相同抽象函数,子类实现它们不会有冲突。
图13-3所示是多继承类图,其中有两个接口InterfaceA和InterfaceB,从类图中可见两个接口中都有一个相同的函数methodB()。AB实现了这两个接口,继承了Any父类。
在这里插入图片描述
接口InterfaceA和InterfaceB代码如下:

package com.a51work6.section2.s3

interface InterfaceA {
	fun methodA()
	fun methodB()
}
package com.a51work6.section2.s3

interface InterfaceB {
	fun methodB()
	fun methodC()
}

从代码中可见两个接口都有两个抽象函数,其中函数methodB()定义完全相同。实现接口InterfaceA和InterfaceB的AB类代码如下:

package com.a51work6.section2.s3

class AB : Any(), InterfaceA, InterfaceB {override fun methodC() {}
	override fun methodA() {}
	override fun methodB() {}}

上述代码第①行是声明AB类,其中继承Any类,实现了两个接口。注意先声明继承父类,并指定调用父类的哪个构造函数,然后再是声明的接口,它们之间使用逗号(,)分隔。在AB类中的代码第②行实现methodB()函数,这个函数即实现了InterfaceA又实现了InterfaceB。
2.4 接口继承
Kotlin语言中允许接口和接口之间继承。由于接口中的函数都是抽象函数,所以继承之后也不需要做什么,因此接口之间的继承要比类之间的继承简单的多。如图13-4所示,其中InterfaceB继承了InterfaceA,在InterfaceB中还重写了InterfaceA中的methodB()函数。ABC是InterfaceB接口的实现类,从图13-4中可见ABC需要实现InterfaceA和InterfaceB接口中的所有函数。
在这里插入图片描述
接口InterfaceA和InterfaceB代码如下:

package com.a51work6.section2.s4

interface InterfaceA {
	fun methodA()
	fun methodB()
}
package com.a51work6.section2.s4

interface InterfaceB : InterfaceA {
	override fun methodB()
	fun methodC()
}
package com.a51work6.section2.s4

class ABC : InterfaceB {
	override fun methodA() {}
	override fun methodB() {}
	override fun methodC() {}
}

InterfaceB继承了InterfaceA,声明时也使用冒号(:)。InterfaceB 中的methodB()重写了InterfaceA,事实上在接口中重写抽象函数,并没有实际意义,因为它们都是抽象的,都是留给子类实现的。
实现接口InterfaceB的ABC类代码如下:

package com.a51work6.section2.s4

class ABC : InterfaceB {
	override fun methodA() {}
	override fun methodB() {}
	override fun methodC() {}
}

ABC类实现了接口InterfaceB,事实上是实现InterfaceA和InterfaceB中所有函数,相当于同时实现InterfaceA和InterfaceB接口。
2.5 接口中具体函数和属性
在Kotlin中接口主要成员是抽象函数和属性,但是也有具体函数和属性。接口中的抽象函数和属性是必须要实现的,而具体函数和属性是可选实现的,根据自己的业务需求选择是否重写它们。
接口中的具体属性和抽象属性在前面已经介绍过了,本节重点介绍在接口中使用具体函数。示例代码如下:
//代码文件:chapter13/src/com/a51work6/section2/s5/InterfaceA.kt

package com.a51work6.section2.s5

interface InterfaceA {
	fun methodA()
	fun methodB(): String
	
	fun methodC(): Int {
	    return 0
	}
	
	fun methodD(): String {
	    return "这是默认函数..."
	}
}	

在接口InterfaceA中声明了两个抽象函数methodA和methodB,以及两个具体函数methodC和methodD,并给出了具体实现。
实现接口示例代码如下:

package com.a51work6.section2.s5

class ABC : InterfaceA {
		override fun methodA() {}
		
		override fun methodB(): String {
		    return "实现methodB函数..."
		}
		
		override fun methodC(): Int {
		    return 500
	}
}

实现接口时接口中原有的抽象函数在实现类中必须实现。抽象函数可以根据需要有选择重写。上述代码中ABC类实现了InterfaceA接口,InterfaceA接口中的两个抽象函数ABC只是重写了methodB。

调用代码如下:

package com.a51work6.section2.s5

fun main(args: Array) {
	//声明接口类型,实例是实现类,发生多态
	val abc = ABC()
	
	// 访问methodB函数
	println(abc.methodB())
	
	// 访问函数methodC
	println(abc.methodC())// 访问函数methodD       ②
	println(abc.methodD())
}

运行结果:

实现methodB函数…
500

这是默认函数…
从运行结果可见,代码第①行调用函数methodC,它是调用类AB中的实现。代码第②行调用函数methodD,是调用接口InterfaceA中的实现。

三、泛型

使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。泛型特性对Kotlin影响最大是在集合中使用泛型。
1 、泛型函数
泛型可以应用于函数声明、属性声明、泛型类和泛型接口。
1.1 声明泛型函数
首先考虑一个问题,怎样声明一个函数来判断两个参数是否相等呢?如果参数是Int类型,则函数声明如下:

private fun isEqualsInt(a: Int, b: Int): Boolean {
	return (a == b)
}

这个函数参数列表是两个Int类型,它只能比较两个Int类型参数是否相等。如果想比较两个Double类型是否相等,可以修改上面声明的函数如下:

private fun isEqualsDouble(a: Double, b: Double): Boolean{
	return (a == b)
}

这个函数参数列表是两个Double类型,它只能比较两个Double类型参数是否相等。如果想比较两个String类型是否相等,可以修改上面声明的函数如下:

private fun isEqualsString(a: String, b: String): Boolean{
	return (a == b)
}

以上分别对3种类型的两个参数进行了比较,声明了类似的3个函数。那么是否可以声明一个函数使之能够比较3种类型呢?合并后的代码:

private fun isEquals(a: T, b: T): Boolean {
	return (a == b)
}

在函数名isEquals前面添加这就是泛型函数了,是声明类型参数,T是类型参数,函数中参数类型也被声明为T,在调用函数时T会用实际的类型替代。
在这里插入图片描述
调用泛型函数代码如下:

fun main(args: Array) {
	println(isEquals(1, 5))
	println(isEquals(1.0, 5.0))
}

isEquals(1, 5)调用函数时类型参数T替换为Int类型,而isEquals(1.0, 5.0)调用函数时类型参数T替换为Double类型。
1.2 多类型参数
泛型函数示例中只是使用了一种类型参数,事实上可以同时声明使用多个类型参数,它们之间用逗号“,”分隔,示例如下:

fun <T, U> addRectangle(a: T, b: U): Boolean {}

类型参数不仅可以声明函数参数类型,还可以声明函数的返回类型,示例代码如下:

fun <T, U> rectangleEquals(a: T, b: U): U {}

1.3 泛型约束
在1.1节声明的fun
isEquals(a: T, b: T): Boolean函数事实上还有一点问题,这是因为并不是所有的类型参数T都具有“可比性”,必须限定T的类型,如果只是数字类型比较可以限定为Number,因为Int和Double等数字类型都继承Number,是Number的子类型。声明类型参数时在T后面添加冒号(:)和限定类型,这种表示方式称为“泛型约束”,泛型约束主要应用于泛型函数和泛型类的声明。
示例代码如下:

package com.a51work6.section1

private fun isEquals(a: T, b: T):Boolean {return (a == b)
}

fun main(args: Array) {
	println(isEquals(1, 5)) //false ②
	println(isEquals(1.0, 1.0)) //true ③
}

上述代码第①行是声明泛型函数,其中是带有约束的类型参数。代码第②行是比较两个Int整数是否相等,代码第③行是比较两个Double浮点数是否相等。
代码第①行的isEquals函数只能比较Number类型的参数,不能比较String等其他数据类型,为此也可以将类型参数限定为Comparable接口类型,所有可比较的对象都实现Comparable接口,Comparable本身也是泛型类型。
修改代码如下:
p

ackage com.a51work6.section1

import java.util.*

fun <T : Comparable> isEquals(a: T, b: T): Boolean {
	return (a == b)
}

fun main(args: Array) {
	println(isEquals(1, 5)) //false
	println(isEquals(1.0, 1.0)) //true
	println(isEquals(“a”,“a”)) //true ①
	val d1 = Date()
	val d2 = Date()
	println(isEquals(d1, d2)) //true ②
}

代码第①行是比较两个字符串是否相等,代码第②行是比较两个日期是否相等。

1.4 可空类型参数
在泛型函数声明中,类型参数没有泛型约束,函数可以接收任何类型的参数,包括可空和非空数据。例如fun isEquals(a: T, b: T): Boolean函数调用时可以传递可空或非空数据,代码如下:

println(isEquals(null, 5)) //false

所有没有泛型约束的类型参数,事实上也是有限定类型的,只不过是Any?,Any?可以任何可空类型的根类,也兼容非空类型。
如果不想接收任何可空类型数据,可以采用Any作为约束类型,Any是任何非空类型的父类,代码如下:

private fun isEquals(a: T, b: T): Boolean{return (a == b)
}

fun main(args: Array) {
	println(isEquals(null, 5)) //编译错误 ②
	println(isEquals(1.0, null)) //编译错误 ③
}

在代码第①行的isEquals函数中声明泛型约束类型限定为Any,所以代码第②行和第③行试图传递空值时发生编译错误。

2、泛型属性
在Kotlin中还可以声明泛型属性,但是这种属性一定是扩展属性,不是能是普通属性
在这里插入图片描述
示例代码如下:

package com.a51work6.section2

val ArrayList.first: T? //获得第一个元素 ①
get() = if (this.size > 1) this[0] else null

val ArrayList.second: T? //获得第二个元素 ②
get() = if (this.size > 2) this[1]else null

fun main(args: Array) {
	val array1 = ArrayList<Int>()//等同于arrayListOf<Int>()    ③
	println(array1.first)   //null
	println(array1.second)  //null
	
	val array2 = arrayListOf ("A","B", "C", "D")println(array2.first)   //A
	println(array2.second)  //B
}

上述代码第①行和第②行是声明ArrayList集合的扩展属性first和second,其中使用了泛型。集合中的元素类型采用类型参数T表示,返回类型是T?表示可能有返回空值的情况。
代码第③行是实例化,Int类型的ArrayList集合,使用ArrayList构造函数创建一个空元素的集合对象。也可以使用arrayListOf()函数创建集合对象。代码是④行是创建String类型ArrayList集合对象,这里使用arrayListOf(“A”,
“B”, “C”, “D”)函数创建并初始化该集合。

3 、泛型类
根据自己的需要也可以自定义泛型类和泛型接口。下面通过一个示例介绍一下泛型类。数据结构中有一种“队列”(queue)数据结构(如图所示),它的特点是遵守“先入先出”(FIFO)规则。
在这里插入图片描述
具体实现代码如下:

package com.a51work6.section3

import java.util.ArrayList

/**

自定义的泛型队列集合
*/
class Queue {// 声明保存队列元素集合items
	private val items:MutableList ②
	
	// init初始化代码中实例化集合items
	init {
		this.items = ArrayList()}
	
	/**
	
	入队函数
	@param item 参数需要入队的元素
	*/
	fun queue(item: T) {this.items.add(item)
	}
	/**
	
	出队函数
	@return 返回出队元素
	*/
	fun dequeue(): T? {return if (items.isEmpty()) {
			null
		} else {
			this.items.removeAt(0)}
	}
	override fun toString(): String {
		return items.toString()
	}
}

上述代码第①行声明了Queue泛型类型的队列,是声明类型参数。代码第②行是声明一个MutableList泛型集合成员属性items,MutableList是可变数组接口,用来保存队列中的元素。代码第③行是init初始化代码,实例化ArrayList对象赋值给items属性。
代码第④行的queue是队列入队函数,其中参数item是要入队的元素,类型参数使用T表示。代码第⑤行的dequeue是出队函数,返回出队的那个元素,返回类型是T表示。在dequeue函数中首先判断集合是否有元素,如果没有元素返回空值;如果有元素则通过第⑥行this.items.remove(0)函数删除队列的第一个元素,并把删除的元素返回,以达到出队的目的。
调用队列示例代码如下:

package com.a51work6.section3

fun main(args: Array) {
	val genericQueue =Queue<String>()          ①
	genericQueue.queue("A")
	genericQueue.queue("C")
	genericQueue.queue("B")
	genericQueue.queue("D")
	//genericQueue.queue(1);//编译错误           ②
	
	println(genericQueue)
	genericQueue.dequeue()println(genericQueue)
}

输出结果如下:

[A, C, B, D]
[C, B, D]

上述代码在使用了刚刚自定义的支持泛型的队列Queue集合。首先在代码第①行实例化Queue对象,通过尖括号指定限定的类型是String,这个队列中只能存放String类型数据。代码第②行试图向队列中添加整数1,则会发生编译错误。
代码第③行出队后操作,通过运行的结果可见,出队后第一个元素"A",会从中队列中删除。
在声明泛型类时也可以有多个类型参数,类似于泛型函数可以使用多个不同的字母声明不同的类型参数。另外,在泛型类中也可以使用泛型约束,如下代码所示:

class Queue {}

4 泛型接口
不仅可以自定义泛型类还可以自定义泛型接口,泛型接口与泛型类声明的方式完全一样。下面将15.3节的示例修改称为队列接口,代码如下:

package com.a51work6.section4

/**

自定义的泛型队列集合
*/
interface IQueue {/**

入队函数
@param item 参数需要入队的元素
*/
fun queue(item: T)/**

出队函数
@return 返回出队元素
*/
fun dequeue(): T?}

上述代码声明了支持泛型的接口。代码第①行声明了IQueue泛型接口,T是类型参数。该接口中声明两个函数,代码第②行的queue函数是入队函数,类型参数使用T表示。代码第③行的dequeue函数是出队函数,返回类型是T表示的类型。
实现接口IQueue具体方式有很多,可以是List(列表结构)、Set(集结构)或Hash(散列结构)等多种不同方式,下面笔者给出一个基于List实现方式,代码如下:

package com.a51work6.section4

import java.util.ArrayList

/**

自定义的泛型队列集合
*/
class ListQueue : IQueue {
	// 声明保存队列元素集合items
	private val items:MutableList
	
	// init代码块初始化是集合items
	init {
		this.items = ArrayList()
	}
	
	/**
	
	入队函数
	@param item
	参数需要入队的元素
	*/
	override fun queue(item: T) {
		this.items.add(item)
	}
	/**
	
	出队函数
	@return 返回出队元素
	*/
	override fun dequeue(): T? {
	return if (items.isEmpty()) {
			null
		} else {
			this.items.removeAt(0)
		}
	}
	override fun toString(): String {
		return items.toString()
	}
}

上述实现代码与Queue类很相似,只是实现了IQueue接口不同。需要注意的实现泛型接口的具体类也应该支持泛型,所以Queue中类型参数名要与IQueue接口中的类型参数名一致。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值