前言:不要当父母需要你的时候,除了泪水,一无所有;不要当孩子需要你的时候,除了惭愧一无所有;不要当自己回首过去的时候,除了蹉跎一无所有。
一、概述
Kotlin 中的接口可以包含抽象方法的声明,也可以包含方法实现。接口与抽象类的区别在于接口不能存储状态。它们可以具有属性,但是这些属性必须是抽象的或者提供访问器实现。同时在 Kotlin1.4版本开始支持接口的SAM转换了。
Kotlin 中接口与 Java8 类似,使用 interface
关键字定义接口,允许方法有默认实现:
interface MyInterface {
fun share() //未实现,默认 abstract 修饰
fun invite() {//已实现,不是 abstract 修饰
println("invite()")//可选方法体
}
}
接口中未提供实现的方法默认是 abstract
修饰的,提供实现的则不是 abstract
修饰的。子类必须重写抽象成员。
二、接口的使用
2.1 实现接口
一个类或者对象可以实现一个或者多个接口。Kotlin 中没有像 Java 那样提供 implements
关键字实现,而是通过冒号:
来表示,后面接需要实现的接口名称。
class Student : MyInterface {
override fun share() {//该方法是 abstract 修饰的,所以子类必须实现,而 invite() 有默认实现,不是必须重写的
//TODO
println("share()")
}
}
//调用
var student = Student()
student.share()
student.invite()
打印数据如下:
share()
invite()
abstract
修饰的方法在子类中是必须要重写的,当然没有abstract
修饰的方法你也可以重写提供自己的实现。
2.2 接口属性
你可以在接口中声明属性,在接口中声明的属性可以是抽象的,也可以为访问器提供实现。接口中声明的属性不能有后备字段 field
,所以接口中声明的访问器不能引用后备字段。
interface MyInterface {
abstract val name: String //默认 abstract 修饰
val type: Int
//提供 get 实现,但是不能有后备字段 field,不是 abstract 修饰
get() = 10
// var age: Int //报错,声明的属性不能有后备字段 field
// get() = field
// var sex: String = "女性"//接口中的属性不允许被初始化
}
class Student : MyInterface {
override val name: String //abstract修饰的属性必须重写
get() = "Android"
}
接口中的属性默认是抽象的,不允许被初始化值,接口不会保存属性值,实现接口时必须重写属性。
2.3 接口继承
Kotlin 中的接口是可以继承的,这点和 Java 类似。既可以为成员提供实现,又可以声明新的成员(函数和属性)。所以,实现这种接口的类只需要重写尚未实现的成员即可。
//基类接口
interface BaseInterface {
val name: String //声明一个属性,默认抽象
fun send1() //声明一个方法,默认抽象
fun send2() {//声明一个方法,提供默认实现
println("send2()")
}
}
//子类接口:继承 BaseInterface,实现了属性 name
interface ChildInterface : BaseInterface {
val childName: String //声明一个属性,默认抽象
override val name: String //实现 BaseInterface 接口中的 name 属性
get() = "Android"
}
//具体实现类:实现了 ChildInterface 接口
class People : ChildInterface {
override val childName: String //接口 ChildInterface 中尚未实现的
get() = "Kotlin"
override fun send1() {//接口 BaseInterface 中尚未实现的
println("send1()")
}
}
可以看到,类 People 中必须实现了 childName
,而没有实现 name
,为什么?
因为 ChildInterface 接口已经重写了 name
,所以属性 name
已经不是抽象的了,那么类 People中就不必要实现了;而 childName
在 ChildInterface 中没有提供实现,仍是抽象的,所以在 People 中必须重写,并提供实现。
那么为什么类 People 中必须实现了 send1()
,而没有实现 send2()
?
同理,因为 send1()
在 BaseInterface 中没有提供实现,在ChildInterface 中也没有提供实现,是抽象的,所有在 People 中必须重写,并提供实现。而 send2()
在 BaseInterface 中提供了实现,是不是抽象的,所有不是必须重写的。
为什么 ChildInterface 可以不实现 send1()
而类 People 中必须实现?
因为 ChildInterface 本身是个接口,可以提供 abstract
成员, send1()
是抽象的,而 People 是个具体实现类,它必须实现父接口中未实现的方法和属性,除非 People 也是个抽象类。
总的来说:实现类必须重写尚未实现的函数和属性。
2.4 接口重写冲突
所谓的接口重写冲突和前面讲解的类继承方法冲突是相似的。指在一个子类中,实现了多个父接口,而不同的父接口有相同的方法名称,这就会给子类实现时造成冲突,无法确认调用的是哪个父类的实现。
interface A {
fun foo() {//已有默认实现,不会强制子类实现
println("A:foo()")
}
fun bar()//没有默认实现,会强制子类实现
}
interface B {
fun foo() {//已有默认实现,不会强制子类实现
println("B:foo()")
}
fun bar() {//已有默认实现,不会强制子类实现
println("B:bar()")
}
}
//实现类,实现A接口
class C : A {
override fun bar() {//A 中 bar() 没有实现,仍是抽象的,需要重写,并提供实现
println("C:bar()")
}
}
//实现类,同时实现 A, B 两个接口
class D : A, B {
//强制重写 bar(),因为 A 中的 bar() 没有实现
override fun bar() {
super<B>.bar() //实际上调用的是 B 中的 bar(),因为 A 中 bar()没有实现,仍是抽象的,不能被访问
}
//虽然接口 A 和 B 中的 foo() 方法都提供了默认实现,但是名字相同给子类 D 带来了冲突,无法确认调用的是 A 中的 foo() 实现还是 B 中的 foo() 实现,所以 Kotlin 强制子类重写
override fun foo() {
super<A>.foo()//调用 A 中的 foo()
super<B>.foo()//调用 B 中的 foo()
}
}
接口A,B都声明了函数 foo()
和 bar()
,它们都默认实现了 foo()
,但是只有接口B中的 bar()
提供了默认实现,接口A中的 bar()
没有提供默认实现,默认为抽象。如果我们在接口A派生一个具体实现类C,那么必须重写 bar()
并提供一个实现。如上面的 class C。
但是,如果从接口A和B派生一个具体实现类D,需要实现多个接口继承的所有方法,并指定类D应该如何确切地实现它们。当如果有多个相同方法,则必须重写该方法,使用 super<父类名>
选择性调用父类的实现。如上面的 class D。
虽然接口A和B中的 foo()
方法都提供了默认实现,但是名字相同给子类D带来了冲突,无法确认调用的是A中的 foo()
实现还是B中的 foo()
实现,所以Kotlin强制子类重写 foo()
。因为A中的 bar()
没有提供默认实现,也需要强制重写。
//调用
val d = D()
d.foo()
d.bar()
val c = C()
c.foo()
打印数据如下:
A:foo()
B:foo()
B:bar()
C:bar()
三、函数接口
只有一个抽象方法的接口称为函数接口或者称为单个抽象方法(SAM)接口。函数接口可以有几个非抽象成员,但是只能由一个抽象成员。
要在 Kotlin 中声明一个函数接口,使用 fun
修饰符修饰接口:
//函数接口使用fun关键字修饰接口
fun interface KRunnable {
fun invoke()
}
3.1 SAM转换
在 Kotlin1.4版本开始,对于函数接口,您可以使用SAM转换,通过使用 Lambda 表达式,使代码更简洁可读。
有人会问什么是‘SAM转换’?
SAM 转换,即 Single Abstract Method Conversions,就是对于只有单个非默认抽象方法接口的转换,对于符合条件的接口称为 SAM Type
,在 Kotlin 中可以直接使用 Lambda 表达式来表示,前提是 Lambda 所表示的函数类型能够与接口中的方法相匹配。
你可以使用 Lambda 表达式,而不是手动创建实现函数接口的类。通过SAM转换,Kotlin 可以将其签名与接口的单一方法的签名相匹配的任何 Lambda 表达式转换为实现该接口的类的实例。
举个例子,下面这个函数接口:
fun interface IntAction {
fun check(int: Int): Boolean
}
SAM转换前这个接口的实现方式:
//创建一个实现接口的类实例
var isEvent = object : IntAction {
override fun check(int: Int): Boolean {
return int % 2 == 0
}
}
通过利用 Kotlin 的SAM转换,你可以编写以下等价代码:
var isEvent = IntAction { it % 2 == 0 }
一个简单的 Lambda 表达式简化了很多不必要的代码:
//函数接口
fun interface IntAction {
fun check(int: Int): Boolean
}
//Lambda 表达式的接口实现类
var isEvent2 = IntAction { it % 2 == 0 }
fun main() {
println("8 是否能被 2 整除:${isEvent.check(8)}")
}
打印数据如下:
8 是否能被 2 整除:true
当然,你也可以对 Java 接口使用SAM转换。
3.2 函数接口和类型别名
函数接口和类型别名用于不同的目的。类型别名只是现有类型的名称——它们不会创建新类型,而函数接口可以。
类型别名只能有一个成员,而函数接口可以有多个非抽象成员和一个抽象成员。函数接口还可以实现和扩展其他接口。
综上所述,函数接口比类型别名更灵活,提供的功能也更多。
源码地址:https://github.com/FollowExcellence/KotlinDemo-master
点关注,不迷路
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。
我是suming,感谢各位的支持和认可,您的点赞、评论、收藏【一键三连】就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!