深入了解Kotlin密封接口的强大功能

news2024/11/30 8:43:12

深入了解Kotlin密封接口的强大功能

Kotlin密封接口
当 Kotlin 首次引入时,开发者迅速爱上了它的强大语言特性,其中包括密封类。然而,有一件事似乎还缺失了:密封接口。当时,Kotlin 编译器无法保证在 Java 代码中无法实现接口,这使得在 Kotlin 中实现密封接口变得困难。

但是时代已经改变,现在从 Kotlin 1.5 和 Java 15 开始,密封接口终于可用了。使用密封接口,开发者可以创建更健壮且类型安全的 API,就像使用密封类一样。在本博客文章中,我们将深入探讨 Kotlin 密封接口的基础知识,探讨它们如何帮助您构建更好的代码。我们将涵盖从密封接口的基础知识到高级技术和最佳实践的所有内容,因此准备好掌握这个强大的新功能吧!

Kotlin 密封接口的基础知识

与密封类一样,密封接口提供了一种定义类型的封闭层次结构的方式,其中所有可能的子类型在编译时都是已知的。这使得创建更健壮且类型安全的 API 成为可能,同时也确保了覆盖了所有可能的用例。

要在 Kotlin 中创建一个密封接口,可以在 interface 关键字之前使用 sealed 修饰符。以下是一个示例:

sealed interface Shape {
    fun draw()
}

这样创建了一个名为Shape的密封接口,其中只有一个draw()方法。请注意,密封接口可以有抽象方法,就像常规接口一样。密封接口只能由在同一文件或同一包中声明的类或对象来实现。

现在,让我们看看如何在实践中使用密封接口。这是一个示例:

sealed interface Shape {
    fun area(): Double
}

class Circle(val radius: Double) : Shape {
    override fun area() = Math.PI * radius * radius
}

class Rectangle(val width: Double, val height: Double) : Shape {
    override fun area() = width * height
}

fun calculateArea(shape: Shape): Double {
    return shape.area()
}

在此示例中,我们定义了一个带有一个抽象方法 area() 的密封接口 Shape。我们随后定义了两个实现了 Shape 接口的类:CircleRectangle。最后,我们定义了一个名为 calculateArea() 的函数,它接受一个类型为 Shape 的参数,并返回该形状的面积。

由于 Shape 接口是密封的,我们不能在当前文件或包之外实现它。这意味着只有 CircleRectangle类可以实现 Shape 接口。

当我们想要定义一组相关接口,这些接口只能被特定的一组类或对象实现时,密封接口尤其有用。例如,我们可以定义一个名为 Serializable 的密封接口,它只能由设计为可序列化的类实现。

密封接口的子类型

要创建密封接口的子类型,可以像使用密封类一样,在类关键字之前使用 sealed 修饰符。这是一个示例:

sealed interface Shape {
    fun draw()
}

sealed class Circle : Shape {
    override fun draw() {
        println("Drawing a circle")
    }
}

sealed class Square : Shape {
    override fun draw() {
        println("Drawing a square")
    }
}

class RoundedSquare : Square() {
    override fun draw() {
        println("Drawing a rounded square")
    }
}
fun drawShape(shape: Shape) {
    when(shape) {
        is Circle -> shape.draw()
        is Square -> shape.draw()
        is RoundedSquare -> shape.draw()
    }
}

这个例子创建了两个实现Shape接口的密封类Circle和Square,以及一个继承自Square的非密封类RoundedSquare。请注意,由于RoundedSquare没有任何直接子类型,因此它不是一个密封类。

模式匹配中使用密封类接口

使用When表达式处理密封接口(和密封类)的主要好处之一是,它们可以用于提供详尽的模式匹配。以下是一个示例:

fun drawShape(shape: Shape) {
    when(shape) {
        is Circle -> shape.draw()
        is Square -> shape.draw()
        is RoundedSquare -> shape.draw()
    }
}

该函数将一个 Shape 作为参数,并使用 when 表达式根据形状的子类型调用适当的draw()方法。请注意,由于 Shape 是一个密封接口,因此 when 表达式是全面的,这意味着所有可能的子类型都已被覆盖。
sealed class VS sealed Interface

高级技术和最佳实践

虽然密封接口为创建类型安全的 API 提供了强大的工具,但在使用它们时有一些高级技术和最佳实践需要注意。

接口委托

密封接口可用的一种技术是接口委托。这涉及创建一个实现密封接口的单独类,然后将调用适当方法的委托给另一个对象。以下是一个示例:

sealed interface Shape {
    fun draw()
}

class CircleDrawer : Shape {
    override fun draw() {
        println("Drawing a circle")
    }
}

class SquareDrawer : Shape {
    override fun draw() {
        println("Drawing a square")
    }
}

class DrawingTool(private val shape: Shape) : Shape by shape {
    fun draw() {
        shape.draw()
        // additional drawing logic here
    }
}

在这个例子中,我们创建了两个实现了Shape接口的类CircleDrawerSquareDrawer。然后我们创建了一个类DrawingTool,它以Shape为参数并将对draw()方法的调用委托给该形状。请注意,DrawingTool还包括在绘制形状后执行的额外绘制逻辑。

避免子类化

在使用密封接口时需要牢记的另一个最佳实践是尽可能避免子类化。虽然密封接口可以用于创建封闭的子类型层次结构,但通常最好使用组合而不是继承来实现相同的效果。

例如,请考虑以下密封接口层次结构:

sealed interface Shape {
    fun draw()
}

sealed class Circle : Shape {
    override fun draw() {
        println("Drawing a circle")
    }
}

sealed class Square : Shape {
    override fun draw() {
        println("Drawing a square")
    }
}

class RoundedSquare : Square() {
    override fun draw() {
        println("Drawing a rounded square")
    }
}

虽然这个层级结构是封闭和类型安全的,但如果你需要添加新的类型或行为,它也可能不够灵活。相反,你可以使用组合来达到同样的效果:

sealed interface Shape {
    fun draw()
}

class CircleDrawer : (Circle) -> Unit {
    override fun invoke(circle: Circle) {
        println("Drawing a circle")
    }
}

class SquareDrawer : (Square) -> Unit {
    override fun invoke(square: Square) {
        println("Drawing a square")
    }
}

class RoundedSquareDrawer : (RoundedSquare) -> Unit {
    override fun invoke(roundedSquare: RoundedSquare) {
        println("Drawing a rounded square")
    }
}

class DrawingTool(private val drawer: (Shape) -> Unit) {
    fun draw(shape: Shape) {
        drawer(shape)
        // additional drawing logic here
    }
}

在这个例子中,我们创建了不同的形状类,以及一个DrawingTool类,该类接受一个知道如何绘制形状的函数。这种方法比使用封闭的子类型层次结构更加灵活,因为它允许您添加新的形状或行为而不必修改现有的代码。

扩展封闭接口

最后值得注意的是,封闭接口可以像常规接口一样进行扩展。如果您需要向封闭接口添加新的行为而不破坏现有代码,则这可能很有用。下面是一个示例:

sealed interface Shape {
    fun draw()
}

interface FillableShape : Shape {
    fun fill()
}

sealed class Circle : Shape {
    override fun draw() {
        println("Drawing a circle")
    }
}

class FilledCircle : Circle(), FillableShape {
    override fun fill() {
        println("Filling a circle")
    }
}

在这个示例中,我们扩展了Shape接口,添加了一个新的FillableShape接口,其中包括一个fill()方法。然后我们创建了一个新的FilledCircle类,它继承了Circle并实现了FillableShape。这样我们就可以在Shape层次结构中添加一个新的行为(fill()),而不会破坏现有的代码。
Sealed class Vs Interface

封闭类 vs 封闭接口

封闭类和封闭接口都是Kotlin语言的特性,提供了一种限制变量或函数参数可能类型的方法。但是,两者之间存在一些重要的区别。

封闭类是一种可以被有限数量的子类扩展的类。当我们将一个类声明为sealed时,它意味着该类的所有可能子类必须在与封闭类本身相同的文件中声明。这使得可以在when表达式中使用封闭类的子类,确保处理所有可能的情况。

下面是一个封闭类的示例:

sealed class Vehicle {
    abstract fun accelerate()
}

class Car : Vehicle() {
    override fun accelerate() {
        println("The car is accelerating")
    }
}

class Bicycle : Vehicle() {
    override fun accelerate() {
        println("The bicycle is accelerating")
    }
}

在这个例子中,我们声明了一个名为Vehicle的密封类。我们还定义了两个Vehicle的子类:Car和Bicycle。由于Vehicle是密封的,任何其他可能的Vehicle子类也必须在同一个文件中声明。

另一方面,密封接口是一个可以由有限数量的类或对象实现的接口。当我们声明一个接口为密封接口时,它意味着该接口的所有可能实现都必须在与密封接口本身相同的文件或相同的包中声明。

以下是一个密封接口的示例:

sealed interface Vehicle {
    fun accelerate()
}

class Car : Vehicle {
    override fun accelerate() {
        println("The car is accelerating")
    }
}

object Bicycle : Vehicle {
    override fun accelerate() {
        println("The bicycle is accelerating")
    }
}

在这个例子中,我们声明了一个叫做Vehicle的密封类。我们还定义了Vehicle的两个子类:CarBicycle。由于Vehicle是密封的,任何其他可能的Vehicle子类也必须在同一个文件中声明。

另一方面,密封接口是一种可以由有限数量的类或对象实现的接口。当我们声明一个接口为密封接口时,意味着该接口的所有可能实现都必须在同一个文件或同一个包中声明。

密封类和密封接口之间的一个重要区别是,密封类可以拥有状态和行为,而密封接口只能拥有行为。这意味着密封类可以拥有属性、方法和构造函数,而密封接口只能拥有抽象方法。

另一个区别是,密封类可以被常规类或其他密封类扩展,而密封接口只能被类或对象实现。密封类还可以拥有一个子类层次结构,而密封接口只能拥有一个扁平的实现列表。

优势

  1. 类型安全:密封接口允许您定义一个封闭的子类型层次结构,这可以确保所有可能的用例都被覆盖。这有助于在编译时捕获错误,而不是运行时,使您的代码更健壮、更易于维护。

  2. 灵活性:密封接口可以用于定义复杂的子类型层次结构,同时仍然允许您添加新的类型或行为,而不会破坏现有的代码。这使得您能够更容易地随着时间的推移发展您的代码,而不必进行大规模的更改。

  3. 改进的API设计:通过使用密封接口,您可以创建更直观、更富表现力的API,更好地反映您正在工作的领域。这有助于使您的代码更易于阅读和理解,特别是对于可能不熟悉您的代码库的其他开发人员。

缺点

学习曲线:尽管密封接口是一项强大的功能,但正确理解和使用它们可能会有些困难。特别是,如果您不习惯使用类型层次结构,可能需要一些时间才能熟悉使用密封接口。
复杂性:随着代码库的增长和复杂化,使用密封接口可能会变得更加困难。如果您拥有大量的子类型或者需要在显著的方式下修改层次结构,这一点尤其正确。
性能:因为密封接口在运行时使用类型检查来确保类型安全,所以与其他方法(例如使用枚举)相比,它们可能会对性能产生影响。但是,对于大多数应用程序来说,这种影响通常是可以忽略不计的。

结论

密封接口是 Kotlin 中一项强大的新功能,它提供了一种类型安全的方式来定义封闭的类型层次结构。通过使用密封接口,您可以创建更为强健和灵活的 API,同时确保所有可能的用例都被涵盖。请记住使用接口委托,避免子类化,并在适当的情况下考虑扩展密封接口,以充分利用这个强大的新功能!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/527904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Dart 3.0 语法新特性 | Records 记录类型 (元组)

theme: cyanosis 终于,终于,终于, Dart 支持元组了! 官方称之为 Records 特性,所以入乡随俗,以后中文称之为 记录类型 。官方 对它的介绍 是: Records are an anonymous, immutable, aggregate type. 记录…

I3C仿真:PGY I3C-EX-PD使用

简述 本文所使用的I3C仿真软件是由Prodigy Technovations Pvt. Ltd公司所研发的MIPI PGY-I3C-EX-PD I3C仿真设备,这款设备搭载了配套软件,专门用于模拟I3C设备,它可以实现模拟Master、SLAVE,同时也支持模拟I2C Slave&#xff0c…

sklearn中的特征工程(过滤法、嵌入法和包装法)

目录 ​编辑特征工程的第一步:理解业务 Filter过滤法 ​编辑方差过滤 ​编辑- 相关性过滤 - 卡方过滤 - F检验 - 互信息法 ​编辑嵌入法(Embedded) 包装法(Wrapper) 特征工程的第一步:理解业务 如…

公司大数据CDH技术选型升级为EMR集群的技术调研

大数据技术栈现状 大数据技术整体设计图 当前大数据各组件版本 ZooKeeper 3.4.5 Spark 2.4.0 Hue 4.3.0 Hive 2.1.1 Hbase 2.1.4 Hadoop 3.0.0 Kafka 2.2.1 Phoenix 5.0.0-cdh6.2.0 Dolphinscheduler 3.0.0 Yarn 3.0.0-cdh6.3.2 Logstash 7.7.0 Kibana 7.7.0 Elasticsearch 7…

反涉网犯罪研究 | 电商平台自动收货代码审计

0x00 免责声明 本文仅限于学习讨论与反诈知识的分享,不得违反当地国家的法律法规。对于传播、利用文章中提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本文作者不为此承担任何责任,一旦造成后果请自行承担…

信息发布系统在医院体检中心的运用

随着生活水平条件的提高,大家的健康意识更加强,重视体检的人数也越来越多,因此体检中心,医院体检门诊中心人流量都很大,健康市场空间前景大,各种医疗健康机构快速发展,市场竞争激烈,…

Linux——互斥和同步(二)

目录 信号量 读写信号量 互斥量 RCU机制 虚拟串口驱动加入互斥 完成量 习题 信号量 前面所讨论的锁机制都有一个限制,那就是在锁获得期间不能调用调度器,即不能引起进程切换。但是内核中有很多函数都可能会触发对调度器的调用(在中断的…

Win10笔记本开机黑屏出现白色错误英文无法启动怎么办?

Win10笔记本开机黑屏出现白色错误英文无法启动怎么办?有用户电脑正常开机之后,出现了问题,系统无法正常的启动,出现一些英文错误代码。那么遇到这个情况怎么去进行解决呢?一起来看看以下的解决方法分享吧。 准备工作&a…

C语言数据结构注意点-线性表

目录 关于指针 LinkList L和LinkList *L的区别 初始化注意点 scanf()的操作 顺序表相关操作符号的确定 关于指针 ①指针和指针变量是两个不同的概念,但要注意的是,通常我们叙述时会把指针变量简称为指针。 ②指针变量其实是一个变量&…

FL Studio21中文完整版All Plugins Edition及切换教程

说到制作电音的软件,coco玛奇朵一定会把FL Studio放到第一个来讲。水果是一款为了电子音乐而生的的宿主软件。水果,独特的节拍音序器组件和通道机架与混音台模块打造的编曲“块”的思路。是极为适合于电子音乐的编排。而且随着水果版本不断地升级&#x…

Vite的基本介绍以及优劣势(一文读懂vite)?

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、Vite是什么?二、为什么选Vite?1、现实的问题2、缓慢的服务器启动3、缓慢的更新 三、Vite的优势?四、Vite的劣势?五、Vite以…

深入理解双亲委派机制

一、双亲委派机制委派流程 双亲委派机制,就是JVM虚拟机加载类的时候,会优先委派上级类加载器进行类加载。 1、如果上级类加载器能找到这个类,那就由上级类加载器加载,并且对下级共享,反之不共享。 2、如果上级类加载…

【#ifndef, #define, 和 #endif】

前言 学习AFNetWoring源码的时候,在AFN的h借接口文件又看到了这几个宏定义,学习记录一下。 作用 #ifndef, #define, 和 #endif是C/CPP的预处理指令,常常用来条件编译和防止头文件重复包含。 简介 #ifndef 它是if not define的简写&…

SpringBoot 使用 Sa-Token 完成注解鉴权功能

注解鉴权 —— 优雅的将鉴权与业务代码分离。本篇我们将介绍在 Sa-Token 中如何通过注解完成权限校验。 Sa-Token 是一个轻量级 java 权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。 Gitee 开源地址&#xff1…

生信步骤|EffectorP批量预测病原物效应子

EffectorP软件利用机器学习原理,通过事先收集已知的效应子制备训练集,从而实现病原真菌和卵菌的效应子预测[1]。 EffectorP发展史[2]: 1.0版本最初在16年发表于NEW PHYTOLOGIST,实现了机器学习初步预测效应子。 2.0版本在18年发表…

OPPO官宣:哲库解散,哲库是 OPPO 旗下的芯片厂,类似华为海思的角色,有近 3000 名员工

大家好,我是二哥呀。 这两天,互联网最大的声音之一就是,OPPO 将终止芯片业务,相信大多数小伙伴和二哥一样,第一眼看到这则消息的时候,震惊的同时并惋惜! ZEKU 是 OPPO 旗下的芯片厂&#xff0…

Java面试知识点(全)-JVM面试知识点

Java面试知识点(全) 导航: https://nanxiang.blog.csdn.net/article/details/130640392 注:随时更新 JVM内存结构 内存结构 Java虚拟机在运行程序时会把其自动管理的内存划分为以上几个区域,每个区域都有的用途以及创建销毁的时机&#xf…

【JavaScript】手写Promise

🐱 个人主页:不叫猫先生 🙋‍♂️ 作者简介:2022年度博客之星前端领域TOP 2,前端领域优质作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步,一起加油呀! &am…

五. AMS实践,Hook启动未注册的Activity

Activity任务栈解析 正常情况下我们的app中的所有Activity都是运行在同一个任务栈(ActivityStack)里面的,也就是我们app的build.gradle文件中的applicationId那个任务栈. 如何实现Activity运行在不同的任务栈呢? 需要在Intent启动这个Activity的时候,给这个intent赋值,设置代…

【KVM虚拟化】· 存储池、存储卷

目录 🍁虚拟磁盘文件 🍂基于文件系统的KVM存储 🍂基于设备的KVM存储 🍁使用KVM存储池 🍂存储池概念 🍁virsh中存储池命令 🍁virsh中存储卷命令 🍁命令实例 🍂创建存储池 …