扩展枚举功能的两种方法
前言
在上一篇文章中,我解釋了如何以及為什么在Java代碼中使用enums而不是switch/case控制結構。 在這里,我將展示如何擴展現有enums功能。
介紹
Java enum是一種編譯器魔術。 在字節碼中,任何enum都表示為擴展抽象類java.lang.Enum并具有幾個靜態成員的類。 因此,枚舉不能擴展任何其他類或枚舉:沒有多重繼承。
類也不能擴展枚舉。 此限制由編譯器強制執行。
這是一個簡單的enum :
enum Color {red, green, blue}此類嘗試擴展它:
SubColor class extends Color {}這是嘗試編譯類SubColor的結果:
$ javac SubColor.java SubColor.java: 1 : error: cannot inherit from final Color SubColor class extends Color {} ^ SubColor.java: 1 : error: enum types are not extensible SubColor class extends Color {} ^ 2 errorsEnum既不能擴展也不能擴展。 那么,如何擴展其功能呢? 關鍵字是“功能”。 Enum可以實現方法。 例如,枚舉Color可以聲明抽象方法draw() ,每個成員都可以重寫它:
enum Color {red { @Override public void draw() { } },green { @Override public void draw() { } },blue { @Override public void draw() { } },;public abstract void draw(); } 在此說明該技術的流行用法。 不幸的是,不可能總是在枚舉本身中實現方法,因為:本文針對此問題提出了以下解決方案。
鏡像枚舉
我們不能修改枚舉顏色嗎? 沒問題! 讓我們創建具有與Color完全相同的元素的枚舉DrawableColor。 這個新的枚舉將實現我們的方法draw(): enum DrawableColor {red { @Override public void draw() { } },green { @Override public void draw() { } },blue { @Override public void draw() { } },;public abstract void draw(); }這個枚舉是源枚舉Color的一種反映,即Color是它的鏡像 。但是如何使用新的枚舉? 我們所有的代碼都使用Color而不是DrawableColor 。 實現此過渡的最簡單方法是使用內置的枚舉方法name()和valueOf(),如下所示:
Color color = ... DrawableColor.valueOf(color.name()).draw();由于name()方法是最終方法,不能被覆蓋,并且valueOf()由編譯器生成,因此這些方法始終相互配合,因此在此不會出現功能問題。 這種過渡的性能也很好:方法name()甚至不創建新的String而是返回預初始化的String(請參見java.lang.Enum源代碼)。 方法valueOf()是使用Map實現的,因此其復雜度為O(1)。
上面的代碼包含明顯的問題。 如果更改了源枚舉Color,則輔助枚舉DrawableColor將不知道這一事實,因此具有name()和valueOf()的技巧將在運行時失敗。 我們不希望這種情況發生。 但是如何防止可能的故障?
我們必須讓DrawableColor知道其鏡像是Color,并且最好在編譯時或至少在單元測試階段強制執行此操作。 在這里,我們建議在單元測試執行期間進行驗證。 Enum可以實現時所執行的靜態初始化enum中的任何代碼被提及。 這實際上意味著,如果靜態初始化程序驗證枚舉DrawableColor是否適合Color,則足以執行以下測試,以確保代碼不會在生產環境中被破壞:
@Test public void drawableColorFitsMirror {DrawableColor.values(); }靜態初始化器只需要比較DrawableColor和Color元素,如果不匹配則拋出異常。 該代碼很簡單,可以針對每種特定情況編寫。 幸運的是,名為enumus的簡單開放源代碼庫已經實現了此功能,因此任務變得微不足道:
enum DrawableColor {....static {Mirror.of(Color.class);} }而已。 如果源枚舉和DrawableColor不再適合,則測試將失敗。 實用程序類Mirror其他方法有2個參數:必須包含2個枚舉的類。 可以從代碼中的任何位置調用此版本,而不僅僅是從必須經過驗證的枚舉中調用。
枚舉地圖
我們是否真的必須定義僅包含一種方法的實現的另一個枚舉? 實際上,我們不必這樣做。 這是一個替代解決方案。 讓我們定義接口抽屜如下:
public interface Drawer {void draw(); }現在讓我們在枚舉元素和接口Drawer的實現之間創建映射:
Map<Color, Drawer> drawers = new EnumMap<>(Color.class) {{put(red, new Drawer() { @Override public void draw();});put(green, new Drawer() { @Override public void draw();})put(blue, new Drawer() { @Override public void draw();}) }}用法很簡單:
drawers.get(color).draw();這里選擇EnumMap作為Map實現,以獲得更好的性能。 Map保證每個枚舉元素僅出現一次。 但是,它不能保證每個enum元素都有相應的條目。 但是檢查映射的大小等于enum元素的數量就足夠了:
drawers.size() == Color.values().length
枚舉還建議在這種情況下方便實用。 如果地圖不適合Color,則以下代碼將引發IllegalStateException及其描述性消息:
從單元測試執行的代碼中調用驗證器很重要。 在這種情況下,基于地圖的解決方案對于將來對源枚舉的修改是安全的。
EnumMap和Java 8功能接口
實際上,我們不必定義特殊的接口來擴展
枚舉功能。 從版本8開始,我們可以使用JDK提供的功能接口之一( Function,BiFunction,Consumer,BiConsumer,
Supplieretc Function,BiFunction,Consumer,BiConsumer,
Supplieretc Function,BiFunction,Consumer,BiConsumer,
Supplieretc 。)選擇取決于必須發送給功能的參數。 例如,可以使用Supplier代替上一個示例中定義的Drawable :
該映射的用法與上一個示例非常相似:
該地圖可以與存儲以下實例的地圖完全一樣地進行驗證:
可繪制。
結論
本文顯示了如果我們在其中添加一些邏輯,那么Java enums有多么強大。 它還演示了兩種擴展語言enums功能的方法,盡管存在語言限制。 本文向用戶介紹了名為enumus的開放源代碼庫,該庫提供了一些有用的實用程序,可幫助更輕松地操作enums 。
翻譯自: https://www.javacodegeeks.com/2019/03/two-ways-extend-enum-functionality.html
總結
以上是生活随笔為你收集整理的扩展枚举功能的两种方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安兔兔公布iPhone 15 Pro M
- 下一篇: 谷歌做最后一搏:欲推翻 26 亿美元的