本文收录于 kotlin入门潜修专题系列,欢迎学习交流。
创作不易,如有转载,还请备注。
接口
kotlin中的接口和java 8非常相似,可以定义抽象方法也可以定义方法实现,定义接口的关键字是interface。示例如下(请仔细阅读代码注释,很多情形基本都包含了):
interface MyInterface {
val val1: Int//正确,接口中的所有属性都默认为abstract
abstract val val2 : Int//正确,这里显示定义了abstract,实际上是多余的
var var1: Int//同上
abstract var var2 : Int//同上
private var var3: Int//错误,private与默认的abstract修饰符冲突(val也一样)
private var var4: Int = 1//错误,接口中的变量(val也一样)都无法初始化
val var1: Int//正确,可以提供get实现
get() = 1
var var2: Int//错误,接口中不能包含后备字段field
get() {
field = 1
}
fun m1()//正确,接口中的所有没有实现的方法都默认为abstract
abstract fun m3()//正确,但abstract是多余的
private fun m5()//错误,接口中没有body的方法默认是abstract的,这个字段与private是冲突的,无法同时存在
fun m2() {//正确,非抽象方法,因为这里提供了实现
println("m2 method")
}
private fun m4() {//正确,非抽象方法可以使用private修饰
var1 = 2
}
}
上面代码中已经对接口中的各个情况做了很好的说明,这里不在展开说明。
接口的实现
kotlin不再像java那样,提供了implements关键字来实现,而是同继承一样,使用冒号(:)即可:
//接口的定义
interface MyInterface {
fun m1()//注意,m1默认是abstract的
fun m2() {//m2不在是abstract,这里提供了默认实现
println("m2 method")
}
}
//实现接口
class InterfaceTest : MyInterface{
override fun m1() {//这里因为接口中的m1方法是abstract的,所以子类必须要实现,m2方法就没有必要,因为接口已经有默认实现
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
接口继承
kotlin中的接口是可以继承接口的,这点和java一样,java也允许一个接口继承(extends)另一个接口(注意不是实现implements)。kotlin中接口继承接口示例如下:
//第一个接口MyInterface
interface MyInterface {
fun m1()//抽象方法
fun m2()//抽象方法
fun m3() {//有默认实现
println("m2 method")
}
}
//第二个接口,继承了MyInterface接口,并实现了m1
interface MyInterface2 : MyInterface {
override fun m1() {//注意这里实现了m1方法
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
//具体的实现类,实现了MyInterface2接口
class InterfaceTest : MyInterface2{
override fun m2() {//这里必须要实现m2方法
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
上面代码中,InterfaceTest必须要实现m2方法,而没有实现m1方法,这是为什么?
因为在MyInterface2接口中已经复写了m1方法,提供了其默认实现,也就是此时m1已经不是abstract方法了,所以InterfaceTest就没有必要进行强制实现了。而m2方法MyInterface并没有提供实现,MyInterface2接口也没有进行实现,所以InterfaceTest必须要实现。
那么为什么MyInterface2可以不实现m2而InterfaceTest必须要实现呢?
因为MyInterface2本身是个接口,可以提供abstract方法,而InterfaceTest已经是个具体的类,因此它必须要实现父接口中未实现的方法,除非InterfaceTest为抽象类(即用abstract修饰)。对于属性的继承规则和方法基本一致。
复写冲突
复写冲突,其实在文章kotlin入门潜修之类和对象篇—继承
的类复写规则中已经介绍过了,只不过现在变成了接口,简单介绍如下。
所谓复写冲突,是指在一个子类中,实现了多个父接口,而不同的父接口又拥有相同的方法签名,这就给子类实现造成了冲突,子类无法确认调用那个父接口的实现。
复写冲突示例如下
//interface1接口
interface Interface1 {
fun m1() {//注意m1方法已经有默认实现,理论上不再强制子类实现
println("m1")
}
fun m2()//会强制子类实现该方法
}
//inteface2接口
interface Interface2 {
fun m1() {//注意m1方法已经有默认实现,理论上不再强制子类实现
println("m1")
}
fun m2() {//注意m2方法也已经有默认实现,理论上不再强制子类实现
println("m2")
}
}
//实现类
//这里InterfaceTest同时实现了Interface1, Interface2 两个接口
class InterfaceTest : Interface1, Interface2 {
override fun m2() {//注意这里必须要实现m2方法,是因为Interface1中m2方法没有被实现。
super.m2()//这里的super调用的实际上就是Interface2中方法,Interface1并没有该方法实现。
}
override fun m1() {//这里必须要实现m1方法,是因为虽然Interface1和Interface2中都已经实现了m1,但是这给子类实现带来了冲突,如果不实现就无法确认该调用Inteface1中的m1实现还是应该调用Intreface2中的实现,故kotlin强制子类实现m1方法
super<Interface1>.m1()//通过这种形式来指定调用那个父接口的方法,这里调用Interface1接口中的m1方法
super<Interface2>.m1()//这里调用Interface2接口中的m1方法
}
}
抽象类和接口对比
这里我们对抽象类和接口进行下对比,因为二者实在太过于相似。抽象类我们在kotlin入门潜修之类和对象篇—继承中已有所阐述,这里姑且总结下二者的差异,朋友可以自行验证。
接口中属性默认都是abstract的,而抽象类中的属性默认是非abstract,需要显示定义为abstract
接口可以包含abstract方法,也可包含非abstract方法,如果方法没有方法体则默认是abstract的;抽象类中同样可以包含abstract方法以及非abstract方法,如果抽象类中的方法没有方法体则必须使用abstract显示声明。
接口中,属性是无法进行初始化,但是抽象类中的非abstract方法可以进行初始化。
一个class只要含有abstract的属性或者方法,该类就必须被声明为抽象类,即要使用abstract修饰。
一个class可以实现多个接口,但是一个class只能继承一个类。
接口和抽象类的使用场景
接口和抽象类都是kotlin(java也是)中实现多态的重要一部分。在应用开发的过程中,合理的利用接口和抽象类,有利于我们设计出高内聚低耦合的系统,以让系统有更好的扩展性、健壮性。二者的使用场景总结如下(实际上这也是很多其他面向对象语言的特性):
一般情况下我们不会在接口中定义属性,这样做基本没有太大意义,更多的是定义行为(即方法),如果需要用到属性则建议使用抽象类
由于kotlin中是单继承的,所以抽象类有天生的弊端,即其子类无法再继承其他的类;而接口则可以实现多个,因此如果抽象类中都是抽象的方法则建议使用接口替换。
从面向对象的角度来看,类是对某一个对象的属性和行为的抽象,而接口更多的是增加一些功能行为。因此,如果子类想扩展功能行为则应考虑使用接口,否则应使用抽象类。
抽象类实际上是基于当前类进行的一层抽象,更多的是表达子类共有的行为,而接口如3中所言是功能性增强。二者产生的背景是不一样的,一个是基于现有的抽象,一个是基于现有的扩充。因此,也可以从这个角度出发,选择到底是用抽象类还是接口。
具体采用哪种实现,可根据实际场景进行选择。