首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 8之后的新特性(九):密封类与接口 Sealed Classes and Interfaces

Java 8之后的新特性(九):密封类与接口 Sealed Classes and Interfaces

作者头像
御剑
发布2022-06-07 19:15:37
9460
发布2022-06-07 19:15:37
举报
文章被收录于专栏:微言码道微言码道微言码道

这周,我会讲到Java 8之后的一个非常重要的特性,就是密封类与接口。

这个特性并不是让代码更简洁的一个点,它是让Java的设计更健壮的一个特性。如果你希望在一些特别的场景下,设计出更健壮的程序。那密封类 Sealed Class就是你不可错过的一个特性。

从继承说起

Java是一门面向对象的语言,这个是我们众所周知的,而面向对象的语言的三大重要特性就是封装继承多态

而在实际的场景中,我们经常会用上抽象与继承这个面向对象的特性。

子类可以继承父类,从而编写子类独特的属性与行为,任何依赖父类的业务,子类都可以替换掉它,这就是里氏替换原则。

在绝大多数情况下,这种继承的设计是非常有价值的。

除了少数情况以外。

限制继承的需求

举个实际场景的例子来说,在一些业务需求中,我们需要继承,但又期望限制继承的能力。

是不是听起来有点矛盾?

以星期为例,一周有七天,大多数场景下,我们都可能会有enum来实现这个。

代码可能是这样:

public enum Week {
    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}

我相信绝大多数会这样来处理这个模型点。但枚举这种类型的使用能力有限,在简单的场景中这样定义并无问题。

但假设我们是在一个游戏的业务中,星期是游戏中一个很重要的概念,在业务上:

  • • 不同的星期中,游戏展现的世界是有分别的,也就是游戏的世界是一个与星期有关联的世界。
  • • 不同的星期中,游戏有各自不同的特色任务,也就是如果你期望参与某种任务,可能就得在指定的星期中来登陆游戏进行游玩。

显而易见,就上述我描述的这个业务来说,使用枚举,并不是最佳实现方式。

也许我们使用抽象与继承可能会更合适。

定义一个Week的抽象父类

public abstract class Week {
    /**
    * 返回指定星期具有的独特的世界
    **/
    abstract World weekWorld();

    /**
    * 返回指定星期具有的特色任务
    **/
    abstract List<Task> specialWeekTask();
}

实现星期,以星期一为例

/**
 * 这是星期一的世界
 */
public class Monday extends Week {

    @Override
    World weekWorld() {
        return new MondayWorld();
    }

    @Override
    List<Task> specialTasks() {
        return List.of(new MondyTask());
    }
}

如上代码所述,我们使用继承来实现这个业务,抽象了一个Week的父类,然后我们再各自实现不同的星期,并指定各种的世界特别任务

在面向对象的语言中,这样的设计非常正常与常见。应该是Java程序员的家常便饭才是。

这就是继承。

如何限制继承

继续这个游戏的世界,我们假设游戏中一个星期有七天,与现实一样。从星期一至星期日。不同的星期的世界如上述业务代码所述有所不同。

那从设计层面的问题就出来了:

如何限制一周只有七天

在枚举中,只要我们定义好,源码没有开放允许修改,那一周就是七天,谁也改变不了。

但如果是在继承的场景中,则完全不一样了。

假设考虑我们的游戏是不同的团队在合作,另一个团队创新式的定义了一个星期八

/**
 * 第八天,这是一个特别的日期
 */
public class EightDay extends Week {

    @Override
    World weekWorld() {
        return new TheEightWorld();
    }

    @Override
    List<Task> specialTasks() {
        return List.of(new EightDayTask());
    }
}

从代码上来说,这是完全没有问题的。

但代码的没有问题不代表实际也允许这样。

考虑以下这些实际会发生的业务场景,我们就会发现Java的抽象与继承,在过往避免不了这种自己扩展定义子类以覆盖父类行为的编码行为。

  • • 一个SPI扩展模式,插件扩展人使用继承覆盖与突破掉SPI原有的行为
  • • 不同团队合作时,因沟通不顺畅或理解不一致时,另一个团队定义了一个不被期望的子类,引发系统业务上的错误

比如在公司中,约束业务中,关键数据都要加密,并且公司级别提供了通用抽象加密接口及几种不同的加密实现,供实际团队挑选使用?结果有团队觉得加密没必要,自己实现了一个非加密的子类,绕过了公司层级的限制,在代码上并无问题,也难以被发现。

那怎么避免这样的场景?

如果你使用的是Java 8,除了用枚举或final class以外,只能依赖沟通与实际的非代码的约束来解决这种问题。

这就是密封类与接口要解决的问题。

密封类与接口

密封类是这样一种概念,它在允许抽象与继承的基础之上,添加约束限制。

密封类或接口,允许你对于可实现的类或可继承的类进行约束,以防止类继承或实现被突破

还是以代码来展示更为直接。

密封类

//使用sealed关键字表明这是一个密封类
public abstract sealed class Week
        //使用permits关键字来约束允许的子类或实现
        permits Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday {

    abstract World weekWorld();

    abstract List<Task> specialTasks();
}

sealed class最先是在JDK 15做为预览特性引入,而后在JDK 17中,做为正式的特性被引入。

定义一个密封类或密封接口的原则是:

  • • 使用sealed关键字来修饰class,表明这是一个密封类
  • • 使用permits关键字来对可实现的类或子类进行约束

记住,类或接口,都可以使用sealed关键字。

sealed继承

要注意的是,继承或实现sealed class的子类,对于它本身,就是否进一步限制继承,同样有不同的策略。

final修饰

如果子类使用final来修饰,表明此子类申明自己不再允许被继承

selaed修饰

进一步约束子类又允许哪些子类来继承

no-sealed修饰

不限制继承,允许被随意继承

这三种策略,是基于类的继承而言,就sealed interface来说,实现只能是final。

这样,基于sealed的特性,你可以随心所欲定义出整个继承树的约束能力与限制。在一些特殊的业务场景中是非常有价值的。

另外,关于sealed class这个特性,在Kotlin,TypeScript中都有,理念与实现都非常相似,就不重复介绍了。

END

关于sealed class,概念上大家知道这么多就可以了。当然,关于更多语法上的细节,还是建议参照OpenJDK官网的说明来进一步了解。

事实上,每一个Java的版本,及其新特性,JDK官网都对这些点做了详细的说明与解释。我始终一再强调,关于具体的术的东西,官网永远是你最先访问的地方,不要舍近求远的找什么书,看什么博客,最先阅读的一定是官网提供的文档。

而我对于技术的文章,风格更多的是讲知其所以然,而不是知其然,我会更关注,为什么需要这个,它解决了过往什么问题,其它语言又是如何做的?

对于技术,知其所以然,比知其然更重要。

关于Java 8之后的新特性,这些是我认为从Java 9至Java 17中值得程序员关注的一些特性,因为这些特性如果你使用了新的Java,是可以很容易用上的。

还有一些类似什么JShell,Vector Api等一些特性,我个人感觉大多程序员很难用上,就没有聊这些了,但这不表示只有我讲的这么几个特性。

下一篇,本系列的终结篇:26岁的Java,为什么仍然是不可撼动的王者

关于我

我是御剑,一个致力于追求,实践与传播编码之道的程序员。

访问微言码道(https://taoofcoding.tech)以阅读更多我写的文章;

访问myddd(https://myddd.org)以了解我在维护的全栈式领域驱动开源框架。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微言码道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 从继承说起
  • 限制继承的需求
  • 如何限制继承
  • 密封类与接口
  • sealed继承
  • END
  • 关于我
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档