我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

详细聊聊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反模式接口常量内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

详细聊聊Vue中的MVVM模式原理

MVVM旨在利用WPF中的数据绑定函数,通过从视图层中几乎删除所有GUI代码(代码隐藏),更好地促进视图层开发与模式其余部分的分离,这篇文章主要给大家介绍了关于Vue.js中MVVM的相关资料,需要的朋友可以参考下
2023-03-03

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录