詳細(xì)聊聊JDK中的反模式接口常量
前言
在實(shí)際開發(fā)過程中,經(jīng)常會需要定義一個文件,用于存儲一些常量,這些常量設(shè)計為靜態(tài)公共常量(使用 public static final
修飾)。這個時候就出現(xiàn)兩種選擇:
在接口中定義常量,比如 JDK 1.1 中的
java.io.ObjectStreamConstans
接口;在類中定義常量,比如 JDK 1.7 中的
java.nio.charset.StandardCharsets
;
這兩種方式都能夠達(dá)到要求:存儲常量、無需實(shí)例化。下面分情況討論下兩種方式孰優(yōu)孰劣。
常量接口
首先從代碼的角度分析,接口中定義的變量都必須是常量,即默認(rèn)使用 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; }
從直觀的感受,接口寫起來方便多了。第二個問題:因?yàn)轭愔袑懙淖址冉涌诙?,所以編譯之后文件大小也是類文件比接口文件大。第三個問題:在JVM加載過程中,接口沒有類提供的額外特種(如重載、方法的動態(tài)綁定等),所以接口加載比類快。分析到此,似乎沒有什么理由不用接口定義常量了。但是,BUT,這種做法卻是一種嚴(yán)重的反模式行為。引用《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.
翻譯出來就是:
常量接口模式是對接口的不良使用。類在內(nèi)部使用某些常量,這純粹是實(shí)現(xiàn)細(xì)節(jié)。實(shí)現(xiàn)常量接口會導(dǎo)致把這樣的實(shí)現(xiàn)細(xì)節(jié)泄露到該類的導(dǎo)出API中。類實(shí)現(xiàn)常量接口,這對于類的用戶來講并沒有什么價值。實(shí)際上,這樣做反而會使他們更加糊涂。更糟糕的是,它代表了一種承諾:如果在將來的發(fā)行版本中,這個類被修改了,它不再需要使用這些常量了,它依然必須實(shí)現(xiàn)這個接口,以確保兼容性。如果非final類實(shí)現(xiàn)了常量接口,它的所有子類的命名空間也會被接口中的常量所“污染”。
這樣說來就很明白透徹了:
接口是不能阻止被實(shí)現(xiàn)或繼承的,也就是說子接口或?qū)崿F(xiàn)中是能夠覆蓋掉常量的定義,這樣通過父、子接口(或?qū)崿F(xiàn)) 去引用常量是可能不一致的;
同樣的,由于被實(shí)現(xiàn)或繼承,造成在繼承樹中可以用大量的接口、類或?qū)嵗ヒ猛粋€常量,從而造成接口中定義的常量污染了命名空間;
接口暗含的意思是:它是需被實(shí)現(xiàn)的,代表著一種類型,它的公有成員是要被暴露的API,但是在接口中定義的常量還算不上API。
綜上所述:使用接口定義常量,是一種不可取的行為。JDK中定義的接口常量(例如java.io.ObjectStreamConstans
)應(yīng)該算是反面教材。
類接口
既然使用接口第一常量不可取,那就只能通過類定義常量了。雖然在JAVA中類不能夠多繼承,但是子類也能夠“污染”父類定義常量的命名空間。所以為了常量不可變,需要將常量類定義為final
的,然后再徹底點(diǎn)就是再定義個private
的構(gòu)造函數(shù)。就像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
中,為了阻止各種形式的實(shí)例化,甚至在構(gòu)造函數(shù)中拋出錯誤,也是做個夠徹底的了。
枚舉類型
但是,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; }
因?yàn)槎x的性別類型實(shí)際是int,如果手賤寫成m.setGender(3)
也是沒有錯誤的,那3
又是什么鬼?是不是還要有4
、5
、6
、7
?那這種常量定義就失去價值了。對于這種可以歸類的常量,最好的常量定義方法應(yīng)該就是枚舉了:
public?enum?Gender?{ ????MALE,? ????FEMALE }
根據(jù)編輯的字節(jié)碼,Gender實(shí)際是:
public?final?class?Gender?extends?java.lang.Enum?{ ????public?static?final?Gender?MALE; ????public?static?final?Gender?FEMALE; }
這樣對于接受 Gender 類型參數(shù)的方法就只能傳入 MALE 或 FEMALE 了,不再有其他選項,這就是枚舉的意義。在后來的JDK中,也出現(xiàn)了像java.nio.file.StandardOpenOption
等的枚舉定義。而且枚舉的定義也不只局限于這種,還有很多其他復(fù)雜定義,可以適用各種情況,以后再慢慢討論。
結(jié)束語
定義常量不要使用接口常量,要在類中定義,最好是final類,并且定義private的構(gòu)造方法,如果常量可以進(jìn)行歸類,最好使用枚舉定義:枚舉 > 類 > 接口。
到此這篇關(guān)于JDK中反模式接口常量的文章就介紹到這了,更多相關(guān)JDK反模式接口常量內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?cloud如何實(shí)現(xiàn)FeignClient指定Zone調(diào)用
這篇文章主要介紹了Spring?cloud如何實(shí)現(xiàn)FeignClient指定Zone調(diào)用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03解決springboot+thymeleaf視圖映射報錯There?was?an?unexpected?erro
這篇文章主要介紹了解決springboot+thymeleaf視圖映射報錯There?was?an?unexpected?error?(type=Not?Found,?status=404)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12SpringMVC 方法四種類型返回值總結(jié)(你用過幾種)
這篇文章主要介紹了SpringMVC 方法四種類型返回值總結(jié)(你用過幾種),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05java判斷某個點(diǎn)是否在所畫多邊形/圓形內(nèi)
這篇文章主要為大家詳細(xì)介紹了java判斷某個點(diǎn)是否在所畫多邊形或圓形內(nèi)的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05Spring @Value 設(shè)置默認(rèn)值的實(shí)現(xiàn)
這篇文章主要介紹了Spring @Value 設(shè)置默認(rèn)值的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09