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.coroutinesJob

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 Languageenum 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. 多继承的密封类

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

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

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

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 不能同时继承自 CompiledLangJvmLang ,所以无法在两个密封类中复用,需要重复定义。

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

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

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

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

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

sealed interface CompiledLang
sealed interface InterpretedLang
sealed interface FunctionalLang
sealed interface JvmLang : CompiledLangobject 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对外提供服务时,也可以更多地虑使用密封接口防止被外部滥用,可以预见密封接口的应用场景会越来越多。

Kotlin1.5 新特性之 Sealed Interface(密封接口)相关推荐

  1. java8新特性(3)--- 函数式接口

    java8新特性(3)- 函数式接口 有且仅有一个抽象方法 package com.common.jdk8;import java.util.Arrays; import java.util.List ...

  2. java compare 返回值_关于Java你不知道的那些事之Java8新特性[Lambda表达式和函数式接口]...

    前言 为什么要用Lambda表达式? Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码,将代码像数据一样传递,这样可以写出更简洁.更灵活的代码,作为一个更紧凑的代码风 ...

  3. Java8 新特性 -- Lambda表达式:函数式接口、方法的默认实现和静态方法、方法引用、注解、类型推测、Optional类、Stream类、调用JavaScript、Base64

    文章目录 1. Lambda表达式 1.1 Lambda表达式语法 1.2 Lambda表达式示例 1.3 说明:函数式接口 2. 方法的默认实现和静态方法 3. 方法引用 3.1 方法引用示例 4. ...

  4. Java8新特性学习(lambda,函数式接口,stream,Optional)

    一. Lambda Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁.更灵活的代码.作为一种更紧凑的代码风格,使Jav ...

  5. 【JDK8 新特性2】JDK 8 接口默认方法/静态方法

    目录 1.接口默认方法 1.1 接口默认方法的定义格式 1.2 接口默认方法的使用 1.2.1 实现类直接调用接口默认方法 1.2.2 实现类重写接口默认方法 2.接口静态方法 2.1 接口静态方法的 ...

  6. 【Java 8 新特性】Java 8 时间接口示例:MonthDay、Month、OffsetDateTime 和 OffsetTime

    Java 8 时间接口示例:MonthDay.Month.OffsetDateTime 和 OffsetTime java.time.MonthDay java.time.Month java.tim ...

  7. Java8-19新特性一览 ,认识全新的前沿技术

    文章目录 Java8-19新特性一览 ,认识全新的前沿技术 前言 你的收获 Java发展趋势 准备工作 新特性 1.接口private 1).说明 2).案例 3).注意 2.类型推断 1).说明 2 ...

  8. Java8新特性系列(Lambda)

    上期我们分析了Java8中Interface的相关新特性,其中包括函数式接口,可以在调用时,使用一个Lambda表达式作为参数,那么我们就来谈谈Java8中的Lambda表达式吧. 定义 Lambda ...

  9. jdk8 接口新特性

    jdk8 接口新特性 概述: jdk8之前接口是规则的集合体,方法只有抽象方法. jdk8版本开始不光光有抽象方法同时增加了实体方法. 新加实体方法: 默认方法 静态方法 默认方法 概述: ​ 被关键 ...

最新文章

  1. cuDNN 功能模块解析
  2. Django视图之介绍、项目准备、URL、路由命名、reverse反解析和Postman测试
  3. 【转】Go 语言教程(2)——表达式
  4. PAT甲级1154 Vertex Coloring :[C++题解]图论、模拟、结构体存边
  5. 数字图像处理实验(15):PROJECT 06-02,Pseudo-Color Image Processing
  6. _tcscpy_s函数引发的问题
  7. [改善Java代码]在接口中不要存在实现代码
  8. [转] 让Visual Studio生成Release版本的可执行文件
  9. FreeRTOS任务挂起和恢复
  10. linux 磁盘分配 简书,linux 磁盘分区
  11. Java夺命21连问!(附答案)
  12. oracle学习日志---返回RemoteOperationException: ERROR: Wrong password for user-错误的用户名密码-的错误的解决办法...
  13. M2Det-一种使用新的特征金字塔方式的单阶段目标检测器(论文笔记)
  14. LoadRunner执行压力测试
  15. ps 命令 快捷键 知识点汇总
  16. 考研英语近义词与反义词·四
  17. 码code | 拒绝996,不用服务器也能高效开发小游戏
  18. Scala入门小纸条(3)
  19. centos6.5 MailScanner+ Spamassassin垃圾邮件过滤器+clamav 杀毒软件
  20. 逃离贼船?新方舟需要我们去创造。

热门文章

  1. 元宇宙 vs. 数字孪生:技术演化的视角
  2. 毕业设计之外文翻译(工具篇)
  3. General information (1)
  4. 使用Monkey对apk做稳定性测试
  5. Rhel 7 /Centos 7配置快速启动栏启动以Firefox为例
  6. Linux citra存档位置,3ds游戏存档在哪个文件夹
  7. Hadoop即将过时了吗?
  8. android activity pause,关于android:onPause()和onStop()在Activity中
  9. 自己动手做个小游戏(2)
  10. C语言查找奥运五环色的位置