美文网首页
Kotlin 特色 sealed 关键字

Kotlin 特色 sealed 关键字

作者: 愿天深海 | 来源:发表于2024-05-29 16:05 被阅读0次

    sealed 意为密封的,可修饰类 class 和接口 interface,用来表示受限的继承结构。

    Sealed Class

    介绍

    sealed class,密封类,密封类是一种特殊的抽象类,用于限制可以继承它的子类。

    密封类具备最重要的一个特点:

    • 其子类可以出现在定义 sealed class 的不同文件中,但不允许出现在与之不同的 module 中,且需要保证 package 一致。

    这样既可以避免 sealed class 文件过于庞大,又可以确保第三方库无法扩展你定义的 sealed class,达到限制类的扩展目的。事实上在早期版本中,只允许在 sealed class 内部或定义的同文件内扩展子类,这些限制在 Kotlin 1.5 中被逐步放开。

    sealed class 还具有如下特点或限制:

    1. sealed class 是抽象类,可以拥有抽象方法,无法直接实例化。
    2. sealed class 的构造函数只能拥有两种可见性:默认情况下是 protected,还可以指定成 private,但不允许指定成 public。
    3. sealed class 子类可扩展局部以及匿名类以外的任意类型子类,包括普通 class、data classobject、sealed class 等,子类信息在编译期可知。
    4. sealed class 的实例,可配合 when 表达式进行判断,当所有类型覆盖后可以省略 else 分支。
    5. 当 sealed class 子类没有指定构造方法或定义任意属性的时候,建议定义成单例 object,因为即便实例化成多个实例,互相之间没有状态的区别。在 Kotlin 1.9 版本新增 data object 用于取代 object 的用法,编译器会提示“'sealed' sub-object can be converted to 'data object' ”,toString() 不会打印 HashCode 等无用信息让输出更有意义。

    使用

    可以在 sealed class 内部定义子类:

    sealed class Language {
        //定义在内部
        data object English : Language()
        data class French(val str: String) : Language()
        class German(str: String) : Language()
    }
    

    还可以在外部定义子类:

    sealed class Language {
        //定义在内部
        data object English : Language()
        data class French(val str: String) : Language()
        class German(str: String) : Language()
    }
    
    //定义在同一文件中
    data object Chinese : Language()
    data class Japanese(val str: String) : Language()
    

    此外还可以定义在同包名不同一文件下

    //定义在同包名不同一文件下
    data class Korean(val str: String) : Language()
    

    对于不同类型的扩展子类,when 表达式的判断亦不同:

    • 判断 sealed class 内部子类类型自然需要指定父类前缀
    • 判断 sealed class 外部子类类型自然无需指定前缀
    • object class 的话可以直接进行实例判断,也可以用 is 关键字判断类型匹配
    • 普通 class 类型的话则必须使用 is 关键字判断类型匹配
        fun demo(language: Language) {
            when (language) {
                Language.English -> {}
                is Language.French -> {}
                is Language.German -> {}
                Chinese -> {}
                is Japanese -> {}
                is Korean -> {}
            }
        }
    

    我们知道Kotlin代码最终会编译成Java字节码的,让我们来看一下,上述的Kotlin代码反编译之后是怎么样的:

    public abstract class Language {
        public /* synthetic */ Language(DefaultConstructorMarker defaultConstructorMarker) {
            this();
        }
    
        private Language() {
        }
    
        // subclass:data object
        public static final class English extends Language {
            public static final English INSTANCE = new English();
    
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (!(obj instanceof English)) {
                    return false;
                }
                English english = (English) obj;
                return true;
            }
    
            public int hashCode() {
                return -765073291;
            }
    
            public String toString() {
                return "English";
            }
    
            private English() {
                super(null);
            }
        }
    
        // subclass:data class
        public static final class French extends Language {
            private final String str;
    
            public static /* synthetic */ French copy$default(French french, String str2, int i, Object obj) {
                if ((i & 1) != 0) {
                    str2 = french.str;
                }
                return french.copy(str2);
            }
    
            public final String component1() {
                return this.str;
            }
    
            public final French copy(String str2) {
                Intrinsics.checkNotNullParameter(str2, "str");
                return new French(str2);
            }
    
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                return (obj instanceof French) && Intrinsics.areEqual(this.str, ((French) obj).str);
            }
    
            public int hashCode() {
                return this.str.hashCode();
            }
    
            public String toString() {
                return "French(str=" + this.str + ')';
            }
    
            public French(String str2) {
                super(null);
                Intrinsics.checkNotNullParameter(str2, "str");
                this.str = str2;
            }
    
            public final String getStr() {
                return this.str;
            }
        }
    
        // subclass:class
        public static final class German extends Language {
            public German(String str) {
                super(null);
                Intrinsics.checkNotNullParameter(str, "str");
            }
        }
    }
    

    可以看到 sealed class 本身被编译为 abstract class,其内部子类按类型有所不同:

    • data object 类型在 class 内部集成了静态的 INSTANCE 实例,同时还有equalstoString 以及 hashCode 函数。
    • data class 类型在 class 内部集成了属性的 getequalscopytoString 以及 hashCode 函数。
    • class 类型仍是普通的 class。

    而外部子类和同包名不同一文件下的子类则自然是定义在 Language 抽象类外部,内容也是跟上面一样的。

    Sealed Interface

    sealed interface,密封接口,和 sealed class 有几乎一样的特点。此外,还有额外的优势,可以帮助密封类、枚举类等类实现多继承和扩展性,比如搭配枚举,以处理更复杂的分类逻辑。

    举例:Flappy Bird 游戏的过程中会产生很多 Action 来触发数据的计算以推动 UI 刷新以及游戏的进程,Action 可以用 enum class 来管理。

    其中有些 Action 是关联的,有些则没有关联、不是同一层级。但是 enum class 默认扩展自 Enum 类,无法再嵌套 enum。

    这将导致层级混乱、阅读性不佳,甚至有的时候功能相近的时候还得特意取个不同的名称。

     enum class Action {
         Tick,
         // GameAction
         Start, Exit, Restart,
         // BirdAction
         Up, Down, HitGround, HitPipe, CrossedPipe,
         // PipeAction
         Move, Reset,
         // RoadAction
         // 防止和 Pipe 的 Action 重名导致编译出错,
         // 将功能差不多的 Road 移动和重置 Action 定义加上了前缀
         RoadMove, RoadReset
     }
     
     fun dispatch(action: Action) {
         when (action) {
             Action.Tick -> {}
     
             Action.Start -> {}
             Action.Exit -> {}
             Action.Restart -> {}
     
             Action.Up -> {}
             Action.Down -> {}
             Action.HitGround -> {}
             Action.HitPipe -> {}
             Action.CrossedPipe -> {}
     
             Action.Move -> {}
             Action.Reset -> {}
     
             Action.RoadMove -> {}
             Action.RoadReset -> {}
         }
     }
    

    借助 sealed interface 我们可以给抽出 interface,并将 enum 进行层级拆分。更加清晰、亦不用担心重名。

     sealed interface Action
     
     enum class GameAction : Action {
         Start, Exit, Restart
     }
     
     enum class BirdAction : Action {
         Up, Down, HitGround, HitPipe, CrossedPipe
     }
     
     enum class PipeAction : Action {
         Move, Reset
     }
     
     enum class RoadAction : Action {
         Move, Reset
     }
     
     object Tick: Action
    

    使用的时候就可以对抽成的 Action 进行嵌套判断:

     fun dispatch(action: Action) {
         when (action) {
             Tick -> {}
             
             is GameAction -> {
                 when (action) {
                     GameAction.Start -> {}
                     GameAction.Exit -> {}
                     GameAction.Restart -> {}
                 }
             }
             is BirdAction -> {
                 when (action) {
                     BirdAction.Up -> {}
                     BirdAction.Down -> {}
                     else -> {}
                 }
             }
             is PipeAction -> {
                 when (action) {
                     PipeAction.Move -> {}
                     PipeAction.Reset -> {}
                 }
             }
             is RoadAction -> {
                 when (action) {
                     RoadAction.Move -> {}
                     RoadAction.Reset -> {}
                 }
             }
         }
     }
    

    sealed 与 enum 的区别

    • enum:enum 只是一个值(常量),每个 enum 常量只能以单例的形式存在。
    • sealed:sealed可以是一个值(定义成 data object 不携带数据),还可以是一个有状态的值(定义成 data class 携带数据)。sealed class 子类可以拥有多个实例,不受限制,每个均可以拥有自己的状态。
    • enum class 不能扩展自 sealed class 以及其他任何 Class,但可以实现 sealed interface,正如上面 Action 的举例。

    相关文章

      网友评论

          本文标题:Kotlin 特色 sealed 关键字

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