作者 | AndroidPub       责编 | 张红月

出品 | CSDN(ID:CSDNnews)

Kotlin 1.5 引入了密封接口(Sealed Interface),这与密封类(Sealed Class)有什么区别呢? 聊密封接口之前先回顾一下密封类的进化史。

密封类的进化史

密封类可以约束子类的类型,相当于强化版的枚举,相对于枚举更加灵活:

  • Enum Class:每个枚举都是枚举类的实例,可以直接使用

  • Sealed Class:密封类约束的子类只是一个类型,你可以为不同子类定义方法和属性,并对齐动态实例化

Kotlin 1.0

早期 Kotlin 1.0 中的密封类,子类型必须是密封类的内部类:

//编程语言
sealed class ProgrammingLang {object Assembly : ProgrammingLang()class Java(ver: String) : ProgrammingLang()class JavaScript(ver: String) : ProgrammingLang()
}
这可以防止在在不编译密封类的前提下为其创建新的派生类。任何派生类的添加都必须重新编译密封类本身,外部调用方能时刻同步所有的子类类型,确保 when 语句的合法:
//获取指定语言的排名
val ranking = when (val item: ProgrammingLang = getProgramLang()) {Assembly -> TODO()is Java -> TODO()is JavaScript -> TODO()
}

另一个潜在的好处是子类必须连同父类名字一起出现,例如 ProgrammingLang.Java,这有助于明确其namespace。

Kotlin 1.1

Kotlin 1.1 取消了子类必须在密封类内部定义的约束,密封类的子类可以声明在文件的 Top-Level。但是为了保证编译的同步,仍然需要在同一文件内。

sealed class ProgrammingLangobject Assembly : ProgrammingLang()
class Java(ver: String) : ProgrammingLang()
class JavaScript(ver: String) : ProgrammingLang()

Kotlin 1.5

到了Kotlin 1.5,约束进一步放宽,允许子类定义在不同的文件中,只要保证子类和父类在同一个 Gradle module 且是同一个包名下即可。在一个 module 可以保证整个所有文件同时参与编译,仍然可以保证编译的同步。

// Lang.kt
sealed class ProgrammingLang// Compiled.kt
class Java(ver: String) : ProgrammingLang()
class Cpp(ver: String) : ProgrammingLang()// Interpreted.kt
class JavaScript(ver: String) : ProgrammingLang()
class Lua(ver: String) : ProgrammingLang()// LowLevel.kt
object Assembly : ProgrammingLang()

放宽约束后,有利于子类按文件归类,同时,较长的子类拆分为单独文件也便于阅读。

如果违反了同Module、同包名的限制,编译会报错:

e: Inheritance of sealed classes or interfaces from different module is prohibited

e: Inheritor of sealed class or interface must be in package where base class is declared

密封接口 Sealed Interface

Kotlin 1.5 除了进一步放宽了对密封类的使用限制,还引入了密封接口。

通常引入接口最主要的目的无非就是对外隐藏实现,但是1.5的密封类已经可以通过分割文件隐藏子类了,密封接口存在的意义是什么?

在以下几个场景中密封接口可以弥补密封类的不足:

1. "final" 的 interface

有时,我们虽然对外暴露了interface,但是并不希望外界去实现它。比如kotlinx.coroutines 的 Job

public interface Job : CoroutineContext.Element {...public fun start(): Boolean...public fun cancel(): Unit...
}

Job 作为一个接口,外界可以对它任意实现,但显然这不是 kotlinx.coroutines 希望出现的。因为未来随着协程功能的迭代,Job 中的共有属性和方法或许会出现变化和增减,如果外部有其派生类很容易出现二进制兼容问题。

如果把 Job 定义为一个密封接口,就可以很好地避免上述问题。

可以大胆猜测,未来某版本的协程中 Job 会以密封接口的形式出现。我们在自己的 library 中也可以考虑使用密封接口避免暴露的接口被随意实现。

2. “可嵌套”的枚举

枚举和密封类功能上很相近,除了文章开头介绍的一些区别外,还有一个容易被忽略的点就是枚举类无法继承其他类。

枚举类的本质都是 Enum 的子类:

enum class JvmLang {Java, Kotlin, Scala
}

反编译 class 后会发现,JvmLang 继承自 Enum。

public final class JvmLang extends Enum{private JvmLang(String s,int i){super(s,i);}public static final JvmLang Java;public static final JvmLang Kotlin;public static final JvmLang Scala;...static{Java = new Action("Java",0);Kotlin = new Action("Kotlin",1);Scala = new Action("Scala",2);}
}

由于单继承的限制,枚举类无法继承 Enum 以外的其他 Class:

e: Enum class cannot inherit from classes

但有时候,、我们又需要枚举能实现嵌套以处理更复杂的分类逻辑。此时密封接口就成了唯一选择

sealed interface Language
enum class HighLevelLang : Language {Java, Kotlin, CPP
}
enum class MachineLang : Language {ARM, X86
}
object AssemblyLang : Language

如上,我们通过密封接口实际上定义了一组“可嵌套”的枚举。

之后就可以通过多级 when 语句进行分类处理了:

 when (lang) {is Machine ->when (lang) {MachineLang.ARM -> TODO()MachineLang.X86 -> TODO()}is HighLevel ->when (lang) {HighLevelLang.CPP -> TODO()HighLevelLang.Java -> TODO()HighLevelLang.Kotlin -> TODO()}else -> TODO()}

3. 多继承的密封类

前两个密封接口的使用场景和密封类没有太多关系, 但其实密封接口也可以扩大密封类的使用场景:

image.png

比如上图中对编程语言的分类,就很难用单继承的密封类进行描述。

比如,当我们像下面这样定义密封类时

sealed class JvmLang {object Java : JvmLang()object Kotlin : JvmLang()object Groovy : JvmLang()
}sealed class CompiledLang {object Java : CompiledLang()object Kotlin : CompiledLang()object Groovy : CompiledLang()object Cpp : CompiledLang()
}

Java 不能同时继承自 CompiledLang 与 JvmLang ,所以无法在两个密封类中复用,需要重复定义。

此时可能有人会说,密封类是可以被继承的,可以让 JvmLang 继承 CompiledLang

sealed class JvmLang : CompiledLang
object Java : JvmLang()
object Kotlin : JvmLang()
object Groovy : JvmLang()
object Cpp : CompiledLang()

如上,Java 同时是 CompiledLang 和 JvmLang 的子类,且没有违反单继承结构。

但这只是因为 Java 的语言特性还不够“复杂”罢了。

Groovy 除了是一个编译性语言,同时具有解释性语言的特性,可以同时归类为CompiledLang 和 InterpretedLang, 此时单继承结构很难维系,需要解除接口实现多继承:

sealed interface CompiledLang
sealed interface InterpretedLang
sealed interface FunctionalLang
sealed interface JvmLang : CompiledLang
object Java : JvmLang
object Kotlin : JvmLang, FunctionalLang
object Groovy : JvmLang, FunctionalLang, InterpretedLang
object JavaScript: InterpretedLang
object Cpp : CompiledLang, FunctionalLang//编程语言的市场份额
fun shareOfCompiledLang(lang: CompiledLang) = when(lang) {Java -> TODO()Kotlin -> TODO()Groovy -> TODO()Cpp -> TODO()
}fun shareOfInterpretedLang(lang: InterpretedLang) = when(lang) {JavaScript -> TODO()Groovy -> TODO()
}

无论处理 InterpretedLang 还是 CompiledLang, Groovy只需要定义一次。

当然,为了更清晰的显示每种 Lang 的所有属性,可以将 interface 之间的继承关系下放:

sealed interface CompiledLang
sealed interface InterpretedLang
sealed interface FunctionalLang
sealed interface JvmLangobject Java : JvmLang, CompiledLang
object Kotlin : JvmLang, CompiledLang, FunctionalLang
object Groovy : JvmLang, CompiledLang, FunctionalLang, InterpretedLang
object JavaScript: InterpretedLang
object Cpp : CompiledLang, FunctionalLang

与 Java 的兼容性

JDK15 开始,Java 也引入了密封类和密封接口,所以 JDK15 以上,Kotlin 和 Java 之间的密封类和密封接口可以比较好的映射和互操作。

即使在 JDK15 以下,由于密封类在字节码中的构造函数加了 prevate 修饰,可以防止 Java 代码的继承。

//kotlin
sealed class ProgrammingLang//java
class Java extends ProgrammingLang

当试图在 Java 侧继承密封类 ProgrammingLang 时,编译器报错如下:

e: There is no default constructor available in 'ProgrammingLang' Java class cannot be a part of Kotlin sealed hierarchy

但是对于密封接口,JDK15 以下,Java 代码可以随意实现,这个需要特别注意

还好 JetBrains 宣布在IDE层面会给与警告,如果使用 IntelliJ IDEA 系列的 IDE,当 Java侧实现密封接口时同样会给出编译报错:

e: Java class cannot be a part of Kotlin sealed hierarchy

不管怎样,还是建议尽量少在 Java 中访问带有 Kotlin 语法特性的相关代码。

总结

Kotlin 1.5 引进了密封接口,为开发者带来如下便利:

  1. 定义“final”的interface

  2. 定义“可嵌套”的枚举

  3. 使得密封类可以多继承

未来,空密封类(没有成员定义)应该尽量使用密封接口替代;此外,自定义 Library 可以使用密封接口对外提供 API 以提高安全性。

可以预见密封接口的应用场景会越来越多。

☞一封来自 1985 年程序员的辞职信☞从追赶到超越,中国开源正在蜕变!
☞乔布斯居然是这样面试我的,你能挺到哪一步?

Kotlin 1.5 新特性:密封接口有啥用?相关推荐

  1. Kotlin 1.2 新特性

    点击关注异步图书,置顶公众号 每天与你分享IT好书 技术干货 职场知识 ​ 在Kotlin 1.1中,团队正式发布了JavaScript目标,允许开发者将Kotlin代码编译为JS并在浏览器中运行.在 ...

  2. [译]C#8.0中一个使接口更加灵活的新特性-默认接口实现

    9月份的时候,微软宣布正式发布C#8.0,作为.NET Core 3.0发行版的一部分.C#8.0的新特性之一就是默认接口实现.在本文中,我们将一起来聊聊默认接口实现. 众所周知,对现有应用程序的接口 ...

  3. JDK8中的新特性——函数式接口

    JDK8 简介 概述 Java 8由Oracle从2014年3月18日发布,此版本是自Java 5(发布于2004年)之后的一个重量级版本,也是java发展史上的一个里程碑式的版本.这个版本在JVM. ...

  4. 学习笔记之-java8的新特性-函数式接口,lambda表达式,方法引用,Stream API,Optional类

    1.Lambda表达式 用匿名内部类的方法去创建多线程1.new Thread2.参数传递new Runnable3.重写run方法4.在run方法中去设置线程任务5.调用start问题:我们最终目标 ...

  5. java8新特性_乐字节-Java8新特性-函数式接口

    上一篇小乐带大家学过 Java8新特性-Lambda表达式,那什么时候可以使用Lambda?通常Lambda表达式是用在函数式接口上使用的.从Java8开始引入了函数式接口,其说明比较简单:函数式接口 ...

  6. jdk8新特性(接口新特性、lambda表达式、方法引用、函数式接口、Stream流)和单例设计模式

    1.单例设计模式 1.概念: 设计模式:使用固有的流程或方式设计出来的类接口.枚举等元素 2.设计原则: 1.私有化构造方法[private.protected] 2.类中创建最终对象[唯一不能被赋值 ...

  7. 【JavaSE】JDK新特性(二)————接口新特性,函数式接口(Suppier,Comsumer,Predicate,Function)

    文章目录 1.接口新特性 1.1 接口组成更新概述 1.2 接口中的默认方法 1.3 接口中的静态方法 1.4 接口中的私有方法 2. 函数式接口 2.1 函数式接口概述 2.2 函数式接口作为方法的 ...

  8. java 接口的静态方法_Java8新特性:接口的默认方法与接口的静态方法

    默认方法允许接口方法定义默认实现,子类方法不必须实现此方法而就可以拥有该方法及实现.如下: public interface DefaultFuncInter { int getInt(); defa ...

  9. java新特性-函数式接口-作为方法参数-作为方法的返回值-常用函数式接口-Supplier-Consumer-Predicate-Function

    文章目录 函数式接口 概念 函数式接口作为方法参数 函数式接口作为方法的返回值 常用函数式接口 Supplier接口 常用函数式接口 Consumer 函数式接口之 Predicate接口 常用接口之 ...

最新文章

  1. python property装饰器原理,Python @property装饰器不起作用
  2. 297. Serialize and Deserialize Binary Tree
  3. 疯狂软件2月3日Android就业班课程详细
  4. 计算机科学研究生规划,2019计算机考研备考:计算机科学与技术研究方向及复习规划...
  5. Appium+Python移动端(Android)自动化测试环境搭建原来没有那么难!+ 带你实战去
  6. ECSHOP隐藏帮助中心文章页的评论功能方法
  7. 定个小目标,炒股咯....
  8. 中国电信到美国的几条海缆线路图
  9. ModBus TCP/IP协议
  10. python爬人人贷代码视频_利用python爬取人人贷网的数据
  11. 计算机地址栏搜索记录怎么删除,怎么删除网址?如何删除浏览器地址栏的网址历史记录和搜索记录...
  12. 教你用3DMAX打造个性鲜明卡通角色
  13. 程序员小妙招:只需一个代码!就能删除C盘垃圾,释放几十G
  14. 【游戏开发实战】2D游戏摄像机镜头跟随,屏幕边缘限制镜头移动(使用Cinemachine组件)
  15. 利用python编程实现音频剪辑
  16. 手机屏幕常见故障_手机屏幕失灵怎么回事 手机屏幕失灵解决办法
  17. VMware下的Ubuntu连接无线网络解决方案
  18. 【组合数学】指数生成函数 ( 指数生成函数概念 | 排列数指数生成函数 = 组合数普通生成函数 | 指数生成函数示例 )
  19. 最新在线客服系统源码软件代码+自动回复+管理后台
  20. 独特的电子邮箱地址-LeetCode练习(Java实现)

热门文章

  1. ESP32开发板开源啦 ESP32-IOT-KIT全开源物联网开发板
  2. MDK、keil复制中文注释乱码
  3. 【编程珠玑】第十一章 排序 (插入排序和快速排序的深度优化)
  4. 特征工程系列:特征预处理(下)
  5. 《无线网络技术教程第二版》阅读笔记(一)
  6. 软件工程结对项目:四则运算web
  7. Spring Boot 集成 Redis 实现缓存机制
  8. 第十五回(二):文会内战平分秋色 树下阔论使坏心焦【林大帅作品】
  9. 【转】【重要】破除“系统学习”的情结
  10. 谈谈遵守公司作战纪律