當(dāng)Java枚舉遇到位掩碼,原來(lái)還能這么玩?

相信大家都用過(guò) Fastjson,阿里的一個(gè)開(kāi)源 JSON 庫(kù),在阿里系的開(kāi)源項(xiàng)目里應(yīng)用的非常廣泛。雖然有時(shí)候也被戲稱(chēng)“沉的快”,但 Fastjson 從功能豐富度、易用性、源碼設(shè)計(jì)角度來(lái)看,都是一款十分優(yōu)秀的工具庫(kù)。


image

在使用 Fastjson 時(shí),經(jīng)常會(huì)配置一些枚舉參數(shù),比如日期格式、格式化輸出、NULL值格式啊等等,就像下面這樣配置:

String jsonStr = JSON.toJSONString(obj, 
                      SerializerFeature.WriteDateUseDateFormat,
                      SerializerFeature.PrettyFormat,
                      SerializerFeature.WriteNullStringAsEmpty);

// JSON.toJSONString
public static String toJSONString(Object object, SerializerFeature... features);

這種配置方式用起來(lái)那是相當(dāng)爽,要什么輸出配置,利用 JAVA 的可變長(zhǎng)度參數(shù)(varargs)直接在方法后面動(dòng)態(tài)追加就行;要是再加上 import static,那么連 SerializerFeature 都不用寫(xiě)了。

不過(guò)細(xì)想一下,這種動(dòng)態(tài)傳的參數(shù)數(shù)組,F(xiàn)astjson 在接受后怎么知道我們具體傳了哪些參數(shù)?接受時(shí)遍歷數(shù)組,每次 equals 對(duì)比嗎?比如這樣:

// 寫(xiě)三個(gè) for 循環(huán)的原因是大概率下,不同 feature 處理時(shí)機(jī)不同,所以不能在一個(gè) for 循環(huán)內(nèi)處理

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
    }
}

這樣也太不“優(yōu)雅”了,每次還需要遍歷,白白浪費(fèi)性能!或者只用一個(gè)循環(huán),弄幾個(gè)變量存儲(chǔ)這幾個(gè) boolean 值呢:

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;
    }
}

這樣比上面要好一點(diǎn)了,但還是需要循環(huán)判斷,而且對(duì)每個(gè) Feature 都要額外增加一個(gè)變量來(lái)存儲(chǔ),一樣不太“優(yōu)雅”。

在 Fastjson 中使用了一種很巧妙的方式來(lái)處理這個(gè)動(dòng)態(tài)的枚舉參數(shù)

枚舉中的序數(shù)(ordinal)

在正式介紹之前,需要先了解枚舉中的一個(gè)概念- 序數(shù)(ordinal),每個(gè)枚舉類(lèi)都會(huì)有一個(gè) ordinal 屬性,這個(gè)ordinal 代表的是當(dāng)前枚舉值在枚舉類(lèi)中的序號(hào)。比如下面這個(gè)枚舉類(lèi)里,F(xiàn)_A /F_B /F_C /F_D 四個(gè)枚舉值的序數(shù)依次為 0/1/2/3

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

通過(guò) ordinal() 方法,就可以獲取枚舉實(shí)例的序數(shù)值,比如Feature.F_A.ordinal()

Fastjson 中的妙用

了解了枚舉序數(shù)之后,現(xiàn)在來(lái)看看 Fastjson 中是怎么個(gè)玩法。在 SerializerFeature 的源碼中有一個(gè) **mask(掩碼) **,這個(gè) mask 的值為 1 << ordinal

枚舉中的位掩碼 - Mask

public enum SerializerFeature {
   WriteDateUseDateFormat,
   PrettyFormat,
   WriteNullStringAsEmpty
    ...

    SerializerFeature(){
        mask = (1 << ordinal());
    }

    public final int mask;

    ...
}

在位運(yùn)算中掩碼 mask 的作用一般是為了保持/更改/刪除某(些)位的值,有張圖非常形象(這個(gè)圖可以簡(jiǎn)單的理解為,白色像素代表1,黑色像素代表0,按為與后,為1的像素位才會(huì)顯示):


image

那在 SerializerFeature 中,WriteDateUseDateFormat, PrettyFormat, WriteNullStringAsEmpty 三個(gè)值得序數(shù)分別為 0/1/2,左移后他們對(duì)應(yīng)的二進(jìn)制位如下:

0 0 0 1 WriteDateUseDateFormat
0 0 1 0 PrettyFormat
0 1 0 0 WriteNullStringAsEmpty

...
1 0 0 0

這里的做法很巧妙,用 1 左移序數(shù)個(gè)位,就可以得到一個(gè)序數(shù)位為 1 的數(shù)字,比如序數(shù)為 1 ,那么第0位就是1,序數(shù)為3,那么第4為就是1,以此類(lèi)推,這樣枚舉中每個(gè)值的 mask 里為 1 的位都會(huì)不同

多個(gè)配置的處理單看這個(gè)位掩碼還是覺(jué)得沒(méi)啥用,來(lái)看看實(shí)戰(zhàn)吧?,F(xiàn)在定義一個(gè)初始為 0 的 features 變量,用來(lái)存儲(chǔ)所有的 feature

int features = 0;

利用位或(OR)對(duì) features 和 mask 進(jìn)行運(yùn)算

features |= SerializerFeature.WriteDateUseDateFormat.getMask();

      0 0 0 0 [input(features)]
(|)   0 0 0 1 [mask(feature mask)]
-------------
      0 0 0 1 [output(new features)]

位或運(yùn)算后的 features 為 0 0 0 1,第 0 位上變成了 1 ,代表第 1 位的枚舉值(WriteDateUseDateFormat)被啟用了,接著繼續(xù)對(duì) PrettyFormat 也執(zhí)行位或,

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

此時(shí) features 為 0 0 1 1,第 2 位 上也變成了 1,代表第 2 位的枚舉值(PrettyFormat)也被啟用了

判斷是否配置

有了 features 的值,還是需要一個(gè)簡(jiǎn)單的判斷方法,來(lái)檢查某個(gè)枚舉值是否被設(shè)置:

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

用 features 和 某個(gè) Feature 的掩碼做位與后,就可以得出一個(gè)某位為 1 的數(shù)字。位與運(yùn)算中只有上下兩個(gè)位都為 1 ,返回的位才會(huì)為 1,那么只要返回的結(jié)果位內(nèi)包含任何一個(gè) 1 ,這個(gè)數(shù)就不會(huì)為 0 ;所以只要這個(gè)結(jié)果不為 0 ,就可以說(shuō)明這個(gè) Feature 已經(jīng)被設(shè)置了。

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

比如上面這個(gè)例子中,當(dāng)前 features 為 0 0 1 1,和 PrettyFormat 的 mask 做位與后,就可以得出 0 0 1 0 ,結(jié)果不為 0 ,所以 PrettyFormat 已經(jīng)被設(shè)置了

完整示例

// 存儲(chǔ)所有配置的 Feature
int features = 0;

// 每添加一個(gè) Feature, 就拿 features 和 當(dāng)前 Feature 的掩碼做位或運(yùn)算
features |= SerializerFeature.WriteDateUseDateFormat.getMask();
features |= SerializerFeature.PrettyFormat.getMask();

// 再通過(guò)位與運(yùn)算的結(jié)果,就可以判斷某個(gè) 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);

//output
writeDateUseDateFormatEnabled: true
prettyFormatEnabled: true
writeNullStringAsEmpty: false

總結(jié)

不止是 Fastjson,Jackson 中對(duì) Feature 的處理也是基于這個(gè)枚舉序數(shù)+位掩碼的邏輯,二者實(shí)現(xiàn)一模一樣,算是一種主流的做法吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容