详细聊聊JDK中的反模式接口常量

目录
  • 前言
  • 常量接口
  • 类接口
  • 枚举类型
  • 结束语

前言

在实际开发过程中,经常会需要定义一个文件,用于存储一些常量,这些常量设计为静态公共常量(使用 public static final 修饰)。这个时候就出现两种选择:

  • 在接口中定义常量,比如 JDK 1.1 中的 java.io.ObjectStreamConstans 接口;
  • 在类中定义常量,比如 JDK 1.7 中的 java.nio.charset.StandardCharsets

这两种方式都能够达到要求:存储常量、无需实例化。下面分情况讨论下两种方式孰优孰劣。

常量接口

首先从代码的角度分析,接口中定义的变量都必须是常量,即默认使用 public static final 修饰。也就是说,在写代码的时候直接写成下面这样:

public interface ObjectStreamConstants {
    short STREAM_MAGIC = (short)0xaced;
    short STREAM_VERSION = 5;
}

但是在类中就必须乖乖的写成下面这种样子:

public final class ObjectStreamConstants {
    public static final short STREAM_MAGIC = (short)0xaced;
    public static final short STREAM_VERSION = 5;
}

从直观的感受,接口写起来方便多了。第二个问题:因为类中写的字符比接口多,所以编译之后文件大小也是类文件比接口文件大。第三个问题:在JVM加载过程中,接口没有类提供的额外特种(如重载、方法的动态绑定等),所以接口加载比类快。分析到此,似乎没有什么理由不用接口定义常量了。但是,BUT,这种做法却是一种严重的反模式行为。引用《Effective Java》中的一段描述:

The constant interface pattern is a poor use of interfaces. That a class uses some constants internally is an implementation detail. Implementing a constant interface causes this implementation detail to leak into the class's exported API. It is of no consequence to the users of a class that the class implements a constant interface. In fact, it may even confuse them. Worse, it represents a commitment: if in a future release the class is modified so that it no longer needs to use the constants, it still must implement the interface to ensure binary compatibility. If a nonfinal class implements a constant interface, all of its subclasses will have their namespaces polluted by the constants in the interface.

翻译出来就是:

常量接口模式是对接口的不良使用。类在内部使用某些常量,这纯粹是实现细节。实现常量接口会导致把这样的实现细节泄露到该类的导出API中。类实现常量接口,这对于类的用户来讲并没有什么价值。实际上,这样做反而会使他们更加糊涂。更糟糕的是,它代表了一种承诺:如果在将来的发行版本中,这个类被修改了,它不再需要使用这些常量了,它依然必须实现这个接口,以确保兼容性。如果非final类实现了常量接口,它的所有子类的命名空间也会被接口中的常量所“污染”。

这样说来就很明白透彻了:

  • 接口是不能阻止被实现或继承的,也就是说子接口或实现中是能够覆盖掉常量的定义,这样通过父、子接口(或实现) 去引用常量是可能不一致的;
  • 同样的,由于被实现或继承,造成在继承树中可以用大量的接口、类或实例去引用同一个常量,从而造成接口中定义的常量污染了命名空间;
  • 接口暗含的意思是:它是需被实现的,代表着一种类型,它的公有成员是要被暴露的API,但是在接口中定义的常量还算不上API。

综上所述:使用接口定义常量,是一种不可取的行为。JDK中定义的接口常量(例如java.io.ObjectStreamConstans)应该算是反面教材。

类接口

既然使用接口第一常量不可取,那就只能通过类定义常量了。虽然在JAVA中类不能够多继承,但是子类也能够“污染”父类定义常量的命名空间。所以为了常量不可变,需要将常量类定义为final的,然后再彻底点就是再定义个private的构造函数。就像java.nio.charset.StandardCharsets一样:

public final class StandardCharsets {
    private StandardCharsets() {
        throw new AssertionError("No java.nio.charset.StandardCharsets instances for you!");
    }
    public static final Charset US_ASCII = Charset.forName("US-ASCII");
    public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
    public static final Charset UTF_8 = Charset.forName("UTF-8");
}

java.nio.charset.StandardCharsets中,为了阻止各种形式的实例化,甚至在构造函数中抛出错误,也是做个够彻底的了。

枚举类型

但是,BUT,还有一种情况,比如常量中定义性别:男、女,使用上面的类常量,需要写成:

public final class Gender {
    private Gender() {
        throw new AssertionError("No x.y.z.Gender instances for you!");
    }
    public static final int MALE = 1;
    public static final int FEMALE = 0;
}

因为定义的性别类型实际是int,如果手贱写成m.setGender(3)也是没有错误的,那3又是什么鬼?是不是还要有4567?那这种常量定义就失去价值了。对于这种可以归类的常量,最好的常量定义方法应该就是枚举了:

public enum Gender {
    MALE, 
    FEMALE
}

根据编辑的字节码,Gender实际是:

public final class Gender extends java.lang.Enum {
    public static final Gender MALE;
    public static final Gender FEMALE;
}

这样对于接受 Gender 类型参数的方法就只能传入 MALE 或 FEMALE 了,不再有其他选项,这就是枚举的意义。在后来的JDK中,也出现了像java.nio.file.StandardOpenOption等的枚举定义。而且枚举的定义也不只局限于这种,还有很多其他复杂定义,可以适用各种情况,以后再慢慢讨论。

结束语

定义常量不要使用接口常量,要在类中定义,最好是final类,并且定义private的构造方法,如果常量可以进行归类,最好使用枚举定义:枚举 > 类 > 接口。

到此这篇关于JDK中反模式接口常量的文章就介绍到这了,更多相关JDK反模式接口常量内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详细聊聊JDK中的反模式接口常量

    目录 前言 常量接口 类接口 枚举类型 结束语 前言 在实际开发过程中,经常会需要定义一个文件,用于存储一些常量,这些常量设计为静态公共常量(使用 public static final 修饰).这个时候就出现两种选择: 在接口中定义常量,比如 JDK 1.1 中的 java.io.ObjectStreamConstans 接口: 在类中定义常量,比如 JDK 1.7 中的 java.nio.charset.StandardCharsets: 这两种方式都能够达到要求:存储常量.无需实例化.下面

  • 一起详细聊聊C#中的Visitor模式

    目录 写在前面 模式演进 举个例子 使用了Tpye-Switch的版本 尝试使用重载的版本 单分派与双分派 Visitor模式 总结 写在前面 Visitor模式在日常工作中出场比较少,如果统计大家不熟悉的模式,那么它榜上有名的可能性非常大.使用频率少,再加上很多文章提到Visitor模式都着重于它克服语言单分派的特点上面,而对何时应该使用这个模式及这个模式是怎么一点点演讲出来的提之甚少,造成很多人对这个模式有种雾里看花的感觉,今天跟着老胡,我们一起来一点点揭开它的面纱吧. 模式演进 举个例子

  • Python编程中的反模式实例分析

    本文实例讲述了Python编程中的反模式.分享给大家供大家参考.具体分析如下: Python是时下最热门的编程语言之一了.简洁而富有表达力的语法,两三行代码往往就能解决十来行C代码才能解决的问题:丰富的标准库和第三方库,大大节约了开发时间,使它成为那些对性能没有严苛要求的开发任务的首选:强大而活跃的社区,齐全的文档,也使很多编程的初学者选择了它作为自己的第一门编程语言.甚至有国外的报道称,Python已经成为了美国顶尖大学里最受欢迎的编程入门教学语言. 要学好一门编程语言实属不易,在初学阶段,就

  • Java超详细讲解设计模式中的命令模式

    目录 介绍 实现 个人理解:把一个类里的多个命令分离出来,每个类里放一个命令,实现解耦合,一个类只对应一个功能,在使用命令时由另一个类来统一管理所有命令. 缺点:如果功能多了就会导致创建的类的数量过多 命令模式(Command Pattern)是⼀种数据驱动的设计模式,它属于行为型模式.请求以命令的形式包裹在对象中,并传给调⽤对象.调⽤对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执⾏命令. 介绍 意图:将⼀个请求封装成⼀个对象,从⽽使您可以⽤不同的请求对客户进⾏参数化.

  • 详细聊聊SpringBoot中动态切换数据源的方法

    其实这个表示有点不太对,应该是 Druid 动态切换数据源的方法,只是应用在了 springboot 框架中,准备代码准备了半天,之前在一次数据库迁移中使用了,发现 Druid 还是很强大的,用来做动态数据源切换很方便. 首先这里的场景跟我原来用的有点点区别,在项目中使用的是通过配置中心控制数据源切换,统一切换,而这里的例子多加了个可以根据接口注解配置 第一部分是最核心的,如何基于 Spring JDBC 和 Druid 来实现数据源切换,是继承了org.springframework.jdbc

  • 详细聊聊MySQL中的LIMIT语句

    目录 问题 server层和存储引擎层 那LIMIT是什么鬼? 怎么办? 吐个槽 最近有多个小伙伴在答疑群里问了小孩子关于LIMIT的一个问题,下边我来大致描述一下这个问题. 问题 为了故事的顺利发展,我们得先有个表: CREATE TABLE t ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, key1 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), KEY idx_key1 (key1

  • 详细聊聊Mybatis中万能的Map

    目录 万能的Map demo map 实现add user map 实现通过id查询 多个参数可以使用Map进行传参 总结 万能的Map 假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们需要考虑使用Map 简单来说,map你用什么参数就写什么参数,而实体类需要写所有参数. map不需要名称完全对应,通过键的映射取值,实体类必须要求和实体类中属性名字一样 map传递参数,直接在sql中取出key即可 [parameterType="map"] 对象传递参数,直接在sql中取对

  • 详细聊聊sql中exists和not exists用法

    目录 exists: exists 和in 的区别 not exists详细介绍: 附案例分析 总结 之所以要说这个问题,是因为项目中用到了not exists,但两者写的语句只有一点差别,结果一个有问题了,一个没问题.具体问题下面详细说明,先来看看exists如何应用. exists: 强调的是是否有返回集,不需知道具体返回的是什么,比如: SELECT * FROM customer WHERE not EXISTS ( SELECT 0 FROM customer_goods WHERE

  • 详细聊聊TypeScript中unknown与any的区别

    目录 前言 1. unknown vs any 2. unknown 和 any 的心智模式 3.总结 总结 前言 我们知道 any 类型的变量可以被赋给任何值. let myVar: any = 0; myVar = '1'; myVar = false; TypeScript 指南并不鼓励使用 any,因为使用它就会丢掉类型限制--而需要类型限制也是我们选择 TypeScript 的一个原因,所以就是有点背道而驰. TypeScript(3.0 及以上版本)还提供了一种类似于 any 的特殊

  • 详细聊聊MySQL中慢SQL优化的方向

    目录 前言 SQL语句优化 记录慢查询SQL 如何修改配置 查看慢查询日志 查看SQL执行计划 如何使用 SQL编写优化 为何要对慢SQL进行治理 总结 前言 影响一个系统的运行速度的原因有很多,是多方面的,甚至可能是偶然性的,或前端,或后端,或数据库,或中间件,或服务器,或网络等等等等,真正的去定位一个问题需要对系统有一定的认知,可以根据自身的判断去缩小问题范围. 今天不说其他的优化,单独把数据库的优化拿出来说几个优化方向. 跟系统的优化方向一样,数据库的优化,同样也是多方面的,其中涵盖着SQ

随机推荐