当Java枚举遇到位掩码,还能这么玩?

沙海 2021年6月24日01:16:04Java评论28字数 5399阅读17分59秒阅读模式
摘要

智能摘要

智能摘要文章源自JAVA秀-https://www.javaxiu.com/34864.html

从功能丰富度、易用性、源码设计角度来看,都是一款十分优秀的工具库。这样也太不"优雅"了,每次还需要遍历,白白浪费性能!用features和某个Feature的掩码做位与后,就可以得出一个某位为1的数字。不止是Fastjson,Jackson中对Feature的处理也是基于这个枚举序数+位掩码的逻辑,二者实现一模一样,算是一种主流的做法吧。最近面试BATJ,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。文章源自JAVA秀-https://www.javaxiu.com/34864.html

原文约 2024 | 图片 3 | 建议阅读 5 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/34864.html

当Java枚举遇到位掩码,还能这么玩?

Java学习者社区 文章源自JAVA秀-https://www.javaxiu.com/34864.html

当Java枚举遇到位掩码,还能这么玩?文章源自JAVA秀-https://www.javaxiu.com/34864.html

相信大家都用过 Fastjson,阿里的一个开源 JSON 库,在阿里系的开源项目里应用的非常广泛。虽然有时候也被戏称“沉的快”,但 Fastjson 从功能丰富度、易用性、源码设计角度来看,都是一款十分优秀的工具库。文章源自JAVA秀-https://www.javaxiu.com/34864.html

在使用 Fastjson 时,经常会配置一些枚举参数,比如日期格式、格式化输出、NULL值格式啊等等,就像下面这样配置:文章源自JAVA秀-https://www.javaxiu.com/34864.html

String jsonStr = JSON.toJSONString(obj,                       SerializerFeature.WriteDateUseDateFormat,                      SerializerFeature.PrettyFormat,                      SerializerFeature.WriteNullStringAsEmpty);// JSON.toJSONStringpublic static String toJSONString(Object object, SerializerFeature... features);

这种配置方式用起来那是相当爽,要什么输出配置,利用 JAVA 的可变长度参数(varargs)直接在方法后面动态追加就行;要是再加上 import static,那么连 SerializerFeature 都不用写了。文章源自JAVA秀-https://www.javaxiu.com/34864.html

不过细想一下,这种动态传的参数数组,Fastjson 在接受后怎么知道我们具体传了哪些参数?接受时遍历数组,每次 equals 对比吗?比如这样:文章源自JAVA秀-https://www.javaxiu.com/34864.html

// 写三个 for 循环的原因是大概率下,不同 feature 处理时机不同,所以不能在一个 for 循环内处理for (SerializerFeature feature : features) {    if(feature.equals(SerializerFeature.WriteDateUseDateFormat)){        // solve WriteDateUseDateFormat    }}for (SerializerFeature feature : features) {    if(feature.equals(SerializerFeature.PrettyFormat)){        // solve PrettyFormat    }}for (SerializerFeature feature : features) {    if(feature.equals(SerializerFeature.WriteNullStringAsEmpty)){        // solve WriteNullStringAsEmpty    }}

这样也太不“优雅”了,每次还需要遍历,白白浪费性能!或者只用一个循环,弄几个变量存储这几个 boolean 值呢:文章源自JAVA秀-https://www.javaxiu.com/34864.html

boolean writeDateUseDateFormatEnable = false;boolean PrettyFormatEnable = false;boolean WriteNullStringAsEmptyEnable = false;for (SerializerFeature feature : features) {    if(feature.equals(SerializerFeature.WriteDateUseDateFormat)){    writeDateUseDateFormatEnable = true;    }    if(feature.equals(SerializerFeature.PrettyFormat)){    PrettyFormatEnable = true;    }    if(feature.equals(SerializerFeature.WriteNullStringAsEmpty)){    WriteNullStringAsEmptyEnable = true;    }}

这样比上面要好一点了,但还是需要循环判断,而且对每个 Feature 都要额外增加一个变量来存储,一样不太“优雅”。文章源自JAVA秀-https://www.javaxiu.com/34864.html

在 Fastjson 中使用了一种很巧妙的方式来处理这个动态的枚举参数文章源自JAVA秀-https://www.javaxiu.com/34864.html

枚举中的序数(ordinal)

在正式介绍之前,需要先了解枚举中的一个概念- 序数(ordinal),每个枚举类都会有一个 ordinal 属性,这个ordinal 代表的是当前枚举值在枚举类中的序号。比如下面这个枚举类里,F_A /F_B /F_C /F_D 四个枚举值的序数依次为 0/1/2/3文章源自JAVA秀-https://www.javaxiu.com/34864.html

public enum Feature {  F_A, // ordinal 0  F_B, // ordinal 1  F_C, // ordinal 2  F_D, // ordinal 3  ;}

通过 ordinal() 方法,就可以获取枚举实例的序数值,比如Feature._F_A_.ordinal()文章源自JAVA秀-https://www.javaxiu.com/34864.html

Fastjson 中的妙用

了解了枚举序数之后,现在来看看 Fastjson 中是怎么个玩法。在 SerializerFeature 的源码中有一个 **mask(掩码) **,这个 mask 的值为 1 << ordinal文章源自JAVA秀-https://www.javaxiu.com/34864.html

枚举中的位掩码 - Mask

public enum SerializerFeature {   WriteDateUseDateFormat,   PrettyFormat,   WriteNullStringAsEmpty    ...    SerializerFeature(){        mask = (1 << ordinal());    }    public final int mask;    ...}

在位运算中掩码 mask 的作用一般是为了保持/更改/删除某(些)位的值,有张图非常形象(这个图可以简单的理解为,白色像素代表1,黑色像素代表0,按为与后,为1的像素位才会显示):文章源自JAVA秀-https://www.javaxiu.com/34864.html

当Java枚举遇到位掩码,还能这么玩?文章源自JAVA秀-https://www.javaxiu.com/34864.html

那在 SerializerFeature 中,WriteDateUseDateFormat, PrettyFormat, WriteNullStringAsEmpty 三个值得序数分别为 0/1/2,左移后他们对应的二进制位如下:文章源自JAVA秀-https://www.javaxiu.com/34864.html

0 0 0 1 WriteDateUseDateFormat0 0 1 0 PrettyFormat0 1 0 0 WriteNullStringAsEmpty...1 0 0 0

这里的做法很巧妙,用 1 左移序数个位,就可以得到一个序数位为 1 的数字,比如序数为 1 ,那么第0位就是1,序数为3,那么第4为就是1,以此类推,这样枚举中每个值的 mask 里为 1 的位都会不同文章源自JAVA秀-https://www.javaxiu.com/34864.html

多个配置的处理

单看这个位掩码还是觉得没啥用,来看看实战吧。现在定义一个初始为 0 的 features 变量,用来存储所有的 feature文章源自JAVA秀-https://www.javaxiu.com/34864.html

int features = 0;

利用位或(OR)对 features 和 mask 进行运算文章源自JAVA秀-https://www.javaxiu.com/34864.html

features |= SerializerFeature.WriteDateUseDateFormat.getMask();      0 0 0 0 [input(features)](|)   0 0 0 1 [mask(feature mask)]-------------      0 0 0 1 [output(new features)]

位或运算后的 features 为 0 0 0 1,第 0 位上变成了 1 ,代表第 1 位的枚举值(WriteDateUseDateFormat)被启用了,接着继续对 PrettyFormat 也执行位或,文章源自JAVA秀-https://www.javaxiu.com/34864.html

features |= SerializerFeature.PrettyFormat.getMask();
      0 0 0 1 [input(features)](|)   0 0 1 0 [mask(feature mask)]-------------      0 0 1 1 [output(new features)]

此时 features 为 0 0 1 1,第 2 位 上也变成了 1,代表第 2 位的枚举值(PrettyFormat)也被启用了文章源自JAVA秀-https://www.javaxiu.com/34864.html

判断是否配置

有了 features 的值,还是需要一个简单的判断方法,来检查某个枚举值是否被设置:文章源自JAVA秀-https://www.javaxiu.com/34864.html

public static boolean isEnabled(int features, SerializerFeature feature) {    return (features & feature.mask) != 0;}

用 features 和 某个 Feature 的掩码位与后,就可以得出一个某位为 1 的数字。位与运算中只有上下两个位都为 1 ,返回的位才会为 1,那么只要返回的结果位内包含任何一个 1 ,这个数就不会为 0 ;所以只要这个结果不为 0 ,就可以说明这个 Feature 已经被设置了。文章源自JAVA秀-https://www.javaxiu.com/34864.html

      0 0 1 1 [input(features)](&)   0 0 1 0 [mask(PrettyFormat)]-------------      0 0 1 0 [output(new features)]

比如上面这个例子中,当前 features 为 0 0 1 1,和 PrettyFormat 的 mask 做位与后,就可以得出 0 0 1 0 ,结果不为 0 ,所以 PrettyFormat 已经被设置了文章源自JAVA秀-https://www.javaxiu.com/34864.html

完整示例

// 存储所有配置的 Featureint features = 0;// 每添加一个 Feature, 就拿 features 和 当前 Feature 的掩码做位或运算features |= SerializerFeature.WriteDateUseDateFormat.getMask();features |= SerializerFeature.PrettyFormat.getMask();// 再通过位与运算的结果,就可以判断某个 Feature 是否配置boolean writeDateUseDateFormatEnabled = SerializerFeature.isEnabled(features,SerializerFeature.WriteDateUseDateFormat);boolean prettyFormatEnabled = SerializerFeature.isEnabled(features,SerializerFeature.PrettyFormat);boolean writeNullStringAsEmpty = SerializerFeature.isEnabled(features,SerializerFeature.WriteNullStringAsEmpty);System.out.println("writeDateUseDateFormatEnabled: "+writeDateUseDateFormatEnabled);System.out.println("prettyFormatEnabled: "+prettyFormatEnabled);System.out.println("writeNullStringAsEmpty: "+writeNullStringAsEmpty);//outputwriteDateUseDateFormatEnabled: trueprettyFormatEnabled: truewriteNullStringAsEmpty: false

总结

不止是 Fastjson,Jackson 中对 Feature 的处理也是基于这个枚举序数+位掩码的逻辑,二者实现一模一样,算是一种主流的做法吧。文章源自JAVA秀-https://www.javaxiu.com/34864.html

来源 | https://juejin.cn/post/6971567246774632462文章源自JAVA秀-https://www.javaxiu.com/34864.html

推荐阅读• 糟糕程序员的编程风格。。。• 创业公司技术总监,去上市公司面试,结果凉了!• 国产最强开源 API 网关,没有之一,不接受任何反驳!• SpringBean默认是单例的,高并发情况下,如何保证并发安全?最近面试BATJ,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。文章源自JAVA秀-https://www.javaxiu.com/34864.html

谢谢支持哟 (*^__^*)文章源自JAVA秀-https://www.javaxiu.com/34864.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定