Kotlin专题「十」(接口与函数接口(Functional (SAM) interfaces))

前言:不要当父母需要你的时候,除了泪水,一无所有;不要当孩子需要你的时候,除了惭愧一无所有;不要当自己回首过去的时候,除了蹉跎一无所有。
一、概述 ??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() ?
【Kotlin专题「十」(接口与函数接口(Functional (SAM) interfaces))】同理,因为 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.bar() //实际上调用的是 B 中的 bar(),因为 A 中 bar()没有实现,仍是抽象的,不能被访问 }//虽然接口 A 和 B 中的 foo() 方法都提供了默认实现,但是名字相同给子类 D 带来了冲突,无法确认调用的是 A 中的 foo() 实现还是 B 中的 foo() 实现,所以 Kotlin 强制子类重写 override fun foo() { super.foo()//调用 A 中的 foo() super.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

请尊重原创者版权,转载请标明出处:https://blog.csdn.net/m0_37796683/article/details/107964620 谢谢!

    推荐阅读