美文网首页
kotlin系列知识卡片 - 密封类

kotlin系列知识卡片 - 密封类

作者: 头秃到底 | 来源:发表于2024-03-03 18:03 被阅读0次

    前言

    随着kotlin在Android日常开发中越来越频繁地使用,笔者觉得有必要对kotlin的一些特性做一些原理的梳理,所谓知其然知其所以然,才能更好让kotlin服务于我们的业务开发,所以会做一个kotlin系列,每个知识点简短,像个小卡片一样,便于理解和快速回顾。所有的源码为:Kotlin 标准库版本为1.9.10。

    密封类

    密封类(sealed class)是一种特殊的类,它的继承有更多的限制,密封类的字类数量是有限的,在编译时其所有的子类都是已知的,定义密封类的模块和包之外不得出现其他子类,编译时可以检查所有可能的字类。对比于枚举类,密封类和枚举都是类型有限个,但密封类只是类型有限,其可有多个实例,而枚举类则是常量,仅作为单个实例存在。

    在不同的kotlin版本对密封类的使用有一些限制:

    • Kotlin 1.0: 密封类的子类必须位于与密封类相同的文件中。不可在其他文件中被定义。
    • Kotlin 1.1及更高版本:允许子类位于其他文件中,提高了代码的重用性和拓展性;子类可为嵌套类、顶层类、内部类

    使用

    密封接口/类及其子类

    • 场景一:单例对象+自定义实例
    sealed interface IDirection {
        data class Custom(val dir: Int): IDirection
        object Top: IDirection
        object Left: IDirection
        object Right: IDirection
        object Bottom: IDirection
    }
    
    sealed class Type(val v: Int) {
        data object Type1: Type(1)
        data object Type2: Type(2)
        data object Type3: Type(3)
        data object Type4: Type(4)
    }
    
    

    数据的解析处理

    private fun test(dir: IDirection) {
        when (dir) {
            is IDirection.Top -> { ... }
            is IDirection.Left -> { ... }
            is IDirection.Right -> { ... }
            is IDirection.Bottom -> { ... }
            is IDirection.Custom -> { ... }
        }
    }
    
    

    多层嵌套密封类

    密封类数据结构定义

    sealed interface IMsg {
        sealed interface Page: IMsg {
            object Init: Page
            data class StateChange(val state: Int): Page
        }
        
        sealed interface User: IMsg  {
            object Login: User
            object Logout: User
        }
    }
    
    

    多层级数据kotlin的解析处理

    private fun test(msg: IMsg) {
        when (msg) {
            is IMsg.Page -> { dispatchPageMsg(it) }
            is IMsg.User -> { dispatchUserMsg(it) }
        }
    }
    
    /**
     * 处理IMsg.Page类型数据
     */
    private fun dispatchPageMsg(msg: IMsg.Page) {
        when (msg) {
            is IMsg.Page.Init -> { ... }
            is IMsg.Page.StateChange -> { ... }
        }
    }
    
    /**
     * 处理IMsg.User类型数据
     */
    private fun dispatchUserMsg(msg: IMsg.User) {
        when (msg) {
            is IMsg.User.Login -> { ... }
            is IMsg.User.Logout -> { ... }
        }
    }
    
    

    原理

    单例类型的密封类对象

    我们以【使用】部分提到的IDirectionIDirection.Top密封单例对象为例,反编译得到代码如下:

    • 定义一个INSTANCE作为Top的实例引用;
    • 将构造函数设置为私有构造,防止外部创建实例
    • 静态代码块初始化Top对象实例,静态代码块在类加载时就自动执行,确保INSTANCE对象在类载入时就完成初始化,且仅被初始化一次
    @Metadata(...)
    public static final class Top implements IDirection {
       @NotNull
       public static final Top INSTANCE;
    
       private Top() {
       }
    
       static {
          Top var0 = new Top();
          INSTANCE = var0;
       }
    }
    
    

    可实例化的密封类

    我们以【使用】部分提到的IDirectionIDirection.Custom密封单例对象为例,反编译得到代码如下:

    从反编译后的源码中,我们可以看到代码中除了Custom类本身的构造及参数外,还默认实现了copy、toString、hashCode、equals等方法以支持kotlin的数据类的功能,因为kotlin的数据类支持解构声明,Custom中有一个dir参数,所以编译器生成了一个component1的函数方法。

    public static final class Custom implements IDirection {
       private final int dir;
    
       public final int getDir() {
          return this.dir;
       }
    
       public Custom(int dir) {
          this.dir = dir;
       }
    
       public final int component1() {
          return this.dir;
       }
    
       @NotNull
       public final Custom copy(int dir) {
          return new Custom(dir);
       }
    
       // $FF: synthetic method
       public static Custom copy$default(Custom var0, int var1, int var2, Object var3) {
          if ((var2 & 1) != 0) {
             var1 = var0.dir;
          }
    
          return var0.copy(var1);
       }
    
       @NotNull
       public String toString() {
          return "Custom(dir=" + this.dir + ")";
       }
    
       public int hashCode() {
          return Integer.hashCode(this.dir);
       }
    
       public boolean equals(@Nullable Object var1) {
          if (this != var1) {
             if (var1 instanceof Custom) {
                Custom var2 = (Custom)var1;
                if (this.dir == var2.dir) {
                   return true;
                }
             }
    
             return false;
          } else {
             return true;
          }
       }
    }
    
    

    密封类的拓展受限规则如何实现?

    密封接口的子类必须在与密封接口相同的模块和包中定义,所以在其他模块和包中是无法扩展密封接口的。但从反编译的代码中可以看到,密封接口编译器会将其编译为一个公共(public)的接口,从理论上是可以被外部访问和拓展的,但 Kotlin 编译器在编译密封接口时,会为其生成一个特殊的元数据(metadata),用于记录密封接口的限制信息。这个元数据会在编译时被 Kotlin 编译器检查,以确保密封接口的子类只能在与密封接口相同的模块和包中定义。

    d1 字段中,\u0086\b 表示这是一个密封接口

    @Metadata(
       mv = {1, 9, 0},
       k = 1,
       d1 = {"\u0000&\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\b\n\u0002\b\u0006\n\u0002\u0010\u000b\n\u0000\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0000\b\u0086\b\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\t\u0010\u0007\u001a\u00020\u0003HÆ\u0003J\u0013\u0010\b\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u0003HÆ\u0001J\u0013\u0010\t\u001a\u00020\n2\b\u0010\u000b\u001a\u0004\u0018\u00010\fHÖ\u0003J\t\u0010\r\u001a\u00020\u0003HÖ\u0001J\t\u0010\u000e\u001a\u00020\u000fHÖ\u0001R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006¨\u0006\u0010"},
       d2 = {"LIDirection$Custom;", "LIDirection;", "dir", "", "(I)V", "getDir", "()I", "component1", "copy", "equals", "", "other", "", "hashCode", "toString", "", "calendarview_debug"}
    )
    public static final class Custom implements IDirection {
    
    

    相关文章

      网友评论

          本文标题:kotlin系列知识卡片 - 密封类

          本文链接:https://www.haomeiwen.com/subject/zmtlzdtx.html