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

本文详细探讨了Kotlin中接口的使用,包括接口的基本概念、属性、继承、重写冲突及函数接口等内容,展示了如何在Kotlin中实现接口,解决接口重写冲突,并介绍了函数接口及其SAM转换的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:不要当父母需要你的时候,除了泪水,一无所有;不要当孩子需要你的时候,除了惭愧一无所有;不要当自己回首过去的时候,除了蹉跎一无所有。

一、概述

  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!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值