Kotlin 密封类和密封接口


Kotlin 中存在 sealed 关键字可以修饰 class 和 interface ,表示密封类和接口。主要是为了限制类的继承结构以达到对继承进行控制的目的。

在编译时期,密封类的子类已经明确声明在代码中,不会变以后生成其他子类,以此来限制类的继承结构。 这样其他模块无法实现你定义的密封类,每一个密封类实例的类型是有限的类型集合。密封接口及其实现也是如此:一旦编译了具有密封接口的模块,就不会出现新的实现。

枚举类的区别

在某种意义上,与枚举类类似:枚举类型的值的集合也会受到限制,但每个枚举常量仅作为单个实例存在,而密封类的子类可以有多个实例,每个实例都拥有自己的状态。

密封类本身是抽象的,不能直接实例化,可以有抽象成员。

子类的位置

密封类的直接子类和接口必须生命在同一个包名下,他们可能是顶级或者嵌套在密封类内部,可以是内部的 interfaceclassobject 声明。

子类的可见性不受限制,密封类的子类必须具有合适的命名,他们不能是本地对象或匿名对象。

枚举类不可以继承自密封类(和其他类一样),但它们可以实现密封接口。

1.4 版本 interface 不可用 sealed 关键字修饰,但 1.5 支持了密封接口。

Sealed Interface

某些情况下,我们虽然对外暴露了 interface,但是并不希望外界去实现它。此时就可以通过 Sealed Interface 去实现。

枚举类也可以实现 Sealed 接口,枚举类实现 Sealed Interface 的作用是突破单继承限制。举例说明,下面是一个枚举类,和它的反编译后的代码:

enum class JvmLang {
    Java, Kotlin, Scala
}

// 反编译
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 以外的其他类,但有时候我们又需要将其进行分类,例如分为 高级语言 和 机器语言:

sealed interface Language

enum class HighLevelLang : Language {
    Java, Kotlin, CPP
}

enum class MachineLang : Language {
    ARM, X86
}

object AssemblyLang : Language

通过密封类突破单类型的限制

我们都知道 Java 语言中,继承关系只能有一个父类。下面是一个密封类的例子:

JVM 的语言有三种,包括 Java、Kotlin 和 Groovy

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

可编译语言除了  Java、Kotlin 和 Groovy 外,还有很多其他语言:

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

事实上,JVM 语言都是可编译语言,按照这个关系,应该有以下继承关系来进行分类:

// JvmLang 实际上是编译语言的一种
sealed class JvmLang : CompiledLang

object Java : JvmLang()
object Kotlin : JvmLang()
object Groovy : JvmLang()

object Cpp : CompiledLang()

通过这个例子不难发现,  Java 使用密封类来实现继承关系,既可以作为 JvmLang 的一种,也可以当作 CompiledLang 的一个对象。

这种继承关系无法使用枚举类来实现,因为使用 enum 定义 JvmLang 反编译后,它是 final 的,不可以被继承。

public final class JvmLang extends Enum

和 When 语句的关系

不管是枚举类还是密封类,都限制了有限集合的范围,自然也都可以配合 when 语句进行使用。密封类的优点也是配合 when 使用从而体现出来的:

fun log(e: Error) = when(e) {
    is FileReadError -> { println("Error while reading file ${e.file}") }
    is DatabaseError -> { println("Error while reading from database ${e.source}") }
    is RuntimeError ->  { println("Runtime error") }
    // the `else` clause is not required because all the cases are covered
}

使用 when 配合密封类来验证是否涵盖所有情况,则无需在语句中添加  else  子句。但是,这仅在您when用作表达式(使用结果)而不是用作语句时才有效:

需要注意的是,如果跨平台使用(定义 expect 关键字修饰的密封类时),因为会跨不同的平台,为了确保兼容性和稳定性,else 分支是必要的,因为实际实现(actual 修饰的 API)的代码逻辑是未知的。

总结

密封类限制了类的继承结构,确保了在编译时期就能明确知道一个子类型的集合。相较于枚举类,它的优势在于对继承关系的处理,枚举类不可继承,密封类可以,还可以实现不同的接口。在配合 when 语句使用时,用于检索是否涵盖全部情况,则无需添加 else 语句。但在特殊情况下还是要确保添加 else 语句的。


原文始发于微信公众号(八千里路山与海):Kotlin 密封类和密封接口

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84993.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!