1. 概述

本文将深入探讨 Kotlin 中接口的定义与实现方式。

我们还会研究一个类如何同时实现多个接口。这种情况容易引发冲突,而 Kotlin 提供了明确的机制来解决这类问题——这是日常开发中常见的“踩坑”点之一,务必掌握清楚✅。

2. Kotlin 中的接口

在面向对象编程中,接口(Interface) 是一种契约或规范,用于约束类的行为。它可以包含抽象方法、具体实现的方法,以及属性声明。Kotlin 的接口设计借鉴了 Java,但语法更简洁,并支持默认实现等现代特性。

2.1. 定义接口

先从最简单的接口开始:

interface SimpleInterface

这是一个空接口,也被称为 标记接口(Marker Interface) ——常用于运行时类型识别,比如 Serializable

接下来为接口添加方法:

interface SimpleInterface {
    fun firstMethod(): String
    fun secondMethod(): String {
        return "Hello, World!"
    }
}

这里我们定义了两个方法:

  • firstMethod():抽象方法,子类必须实现 ❌
  • secondMethod():带有默认实现的具体方法 ✅

再加入属性支持:

interface SimpleInterface {    
    val firstProp: String
    val secondProp: String
        get() = "Second Property"
    fun firstMethod(): String
    fun secondMethod(): String {
        return "Hello, from: $secondProp"
    }
}

说明:

  • firstProp 是抽象属性,无初始值,需由实现类提供
  • secondProp 提供了 getter 实现,相当于只读属性的默认值

⚠️ 注意:接口中的属性不能持有状态,因此以下写法是非法的:

interface SimpleInterface {
    val firstProp: String = "First Property" // 编译错误!
}

因为这会暗示存储字段的存在,而接口不允许维护实例状态。

2.2. 实现接口

定义好接口后,来看如何在类中实现它:

class SimpleClass : SimpleInterface {
    override val firstProp: String = "First Property"
    override fun firstMethod(): String {
        return "Hello, from: $firstProp"
    }    
}

关键点:

  • 只需实现抽象成员(如 firstPropfirstMethod
  • 已有默认实现的成员可选择性地重写 ✅

如果需要覆盖所有成员(包括已有实现的),可以这样做:

class SimpleClass : SimpleInterface {
    override val firstProp: String = "First Property"
    override val secondProp: String
        get() = "Second Property, Overridden!"
    
    override fun firstMethod(): String {
        return "Hello, from: $firstProp"
    }
    override fun secondMethod(): String {
        return "Hello, from: $secondProp$firstProp"
    }
}

此时 secondPropsecondMethod 都被显式重写,原接口的默认逻辑不再生效。

2.3. 通过委托实现接口

委托模式(Delegation Pattern) 是一种替代继承的设计模式,强调“组合优于继承”。与其他语言不同,Kotlin 原生支持接口委托,语法非常简洁。

先看基础接口和实现类:

interface MyInterface {
    fun someMethod(): String
}

class MyClass : MyInterface {
    override fun someMethod(): String = "Hello, World!"
}

现在我们可以创建一个通过委托实现接口的派生类:

class MyDerivedClass(myInterface: MyInterface) : MyInterface by myInterface

解释:

  • 构造函数接收一个 MyInterface 实例作为委托对象
  • by myInterface 表示该类的方法调用自动转发给这个委托实例

使用示例:

val myClass = MyClass()
println(MyDerivedClass(myClass).someMethod()) // 输出: Hello, World!

✅ 优势:无需手动转发每个方法,Kotlin 自动生成代理代码,极大减少样板代码。

2.4. 忽略部分接口方法的实现

有时我们只想实现接口中的某些方法,其余使用默认行为。Kotlin 提供两种解决方案:

方案一:接口内提供默认实现(推荐)

如果你控制接口源码,直接加默认体即可:

interface MyAdapter {
    fun onFocus() { /* 默认为空 */ }
    fun onClick()
    fun onDrag() { /* 默认为空 */ }
}

class MyAdapterImpl : MyAdapter {
    override fun onClick() {
        println("Clicked!")
    }
}

✅ 结果:onFocusonDrag 自动使用默认实现,无需重写。

方案二:中间适配接口(适用于第三方库)

当无法修改原始接口时,可通过继承并添加默认实现来封装:

interface MyAdapter { 
    fun onFocus() 
    fun onClick() 
    fun onDrag()
}

interface MyOnClickAdapter : MyAdapter {
    override fun onFocus() { /* 默认为空 */ }
    override fun onDrag() { /* 默认为空 */ }
}

class MyOnClickAdapterImpl : MyOnClickAdapter {
    override fun onClick() {
        println("Clicked!")
    }
}

这种方式类似于 Java 的 MouseAdapter 模式,在事件监听场景下特别实用。

3. 多重继承与冲突处理

Kotlin 允许类实现多个接口,这带来了灵活性,但也引入了“菱形继承问题”(Diamond Problem)。不过 Kotlin 通过强制显式重写解决了这一难题。

3.1. 实现多个接口

定义两个含有相同方法签名的接口:

interface FirstInterface {
    fun someMethod(): String
    fun anotherMethod(): String {
        return "Hello, from anotherMethod in FirstInterface"
    }
}

interface SecondInterface {
    fun someMethod(): String {
        return "Hello, from someMethod in SecondInterface"
    }
    fun anotherMethod(): String {
        return "Hello, from anotherMethod in SecondInterface"
    }
}

然后让一个类同时实现这两个接口:

class SomeClass : FirstInterface, SecondInterface {
    override fun someMethod(): String {
        return "Hello, from someMethod in SomeClass"
    }
    override fun anotherMethod(): String {
        return "Hello, from anotherMethod in SomeClass"
    }
}

虽然语法简单,但背后隐藏着方法调用优先级的问题 ⚠️。

3.2. 解决方法冲突

当多个父接口提供了同名且带默认实现的方法时,Kotlin 要求子类必须显式重写该方法,以避免歧义。

例如上面的 anotherMethod() 在两个接口中都有默认实现,若 SomeClass 不重写,则编译器无法决定应调用哪一个。

而对于 someMethod()

  • FirstInterface 中是抽象的
  • SecondInterface 中有默认实现

即便如此,Kotlin 仍要求必须重写 someMethod(),因为它是从多个来源继承的抽象方法。

✅ 总结规则:

所有继承自多个接口的成员(无论是否抽象),只要存在潜在冲突或未完全实现,都必须在子类中显式 override

3.3. 菱形问题及其解决

典型的“菱形问题”结构如下:

interface BaseInterface {
    fun someMethod(): String
}

interface FirstChildInterface : BaseInterface {    
    override fun someMethod(): String {
        return "Hello, from someMethod in FirstChildInterface"
    }
}

interface SecondChildInterface : BaseInterface {
    override fun someMethod(): String {
        return "Hello, from someMethod in SecondChildInterface"
    }
}

class ChildClass : FirstChildInterface, SecondChildInterface {
    override fun someMethod(): String {
        return super<SecondChildInterface>.someMethod()
    }
}

在这个例子中:

  • BaseInterface 定义抽象方法
  • 两个子接口分别实现了该方法
  • ChildClass 同时继承两者

由于冲突存在,ChildClass 必须重写 someMethod()。但有趣的是,你可以通过 super<SpecificInterface> 显式指定调用哪个父接口的实现。

✅ 这就是 Kotlin 的解决方案:强制显式决策 + 支持限定调用,彻底杜绝模糊行为。

4. 接口 vs 抽象类

Kotlin 中的抽象类也不能被实例化,可用于定义部分实现,其子类需完成剩余抽象成员。

4.1. 主要区别

特性 接口(Interface) 抽象类(Abstract Class)
多重继承 ✅ 支持实现多个接口 ❌ 只能继承一个抽象类
状态保持 ❌ 属性不能保存状态 ✅ 可拥有带 backing field 的属性
构造函数 ❌ 不支持构造参数 ✅ 支持构造函数
默认实现 ✅ 支持默认方法 ✅ 支持具体方法

4.2. 使用建议

遵循以下原则做技术选型:

  • 优先使用接口 来定义能力契约(capability contract),比如 Drawable, Clickable
  • 使用抽象类 当你需要共享代码逻辑和状态时,尤其是涉及构造函数或字段初始化的场景

💡 简单记忆:接口描述“能做什么”,抽象类描述“是什么的一部分”。

5. 与 Java 接口对比

自从 Java 8 引入 default 方法后,Java 接口的能力已接近 Kotlin 接口。主要差异集中在语法层面:

对比项 Kotlin Java
重写关键字 必须使用 override 标记 无需特殊关键字
默认方法 支持,默认即允许 Java 8+ 才支持 default 方法
属性支持 接口可声明属性(含 getter) 仅支持静态常量(public static final)

📌 尤其注意:Kotlin 要求实现接口方法时必须加上 override 关键字,这是编译期检查的重要保障,有助于避免意外覆盖。

更多 Java 8 接口新特性详见 Java 8 新特性详解

6. 总结

本文系统讲解了 Kotlin 接口中:

  • 如何定义与实现接口 ✅
  • 多接口继承带来的冲突及解决方式 ✅
  • 委托机制带来的代码复用优势 ✅
  • 与抽象类的本质区别与适用场景 ✅
  • 与 Java 接口的异同点 ✅

掌握这些内容,不仅能写出更清晰的契约代码,还能有效规避多重继承带来的陷阱。

文中所有示例代码均已上传至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-lang-oop


原始标题:Guide to Kotlin Interfaces