枚舉--讓盜版美國總統wcc給你整明白哈哈


1.為什么要有枚舉

Java中的枚舉其實是一種語法糖,在 JDK 1.5之后出現,用來表示固定且有限個的對象。比如一個季節類有春、夏、秋、冬四個對象;一個星期有星期一到星期日七個對象。這些明顯都是固定的,且有限個。那jdk5之前如果想表示有限個對象,代碼是怎么寫的嘞?這樣寫有什么缺點,最終讓jdk官方人員(大部分是美國人)看不下去了吶,我這里給大家舉一個2020火熱的事件,來體會,話不多說,我wcc直接上代碼:

package com.huawei.subtitle.portal.com;

/*
  為什么需要Enum
 */
public class AmericanFamily {

    public static final String bigSon = "特朗普";
    public static final String smallSon = "拜登";
    public static final String daughter = "奧巴馬";
    //public static final String daughter = "羅斯福";

    public static void main(String[] args) {
        String person = "拜登";
        ourPresident(person);
        System.out.println("--------------------");
        String chineseBoyWccNickName = "特朗普";
        ourPresident(chineseBoyWccNickName);
        System.out.println("你看到了,我是中國小子wcc,可不是AmericanFamily成員");
    }

    public static void ourPresident(String person) {
        if (AmericanFamily.bigSon.equals(person)) {
            System.out.println(" our president 川建國,2020 我挺你");
        }
        if (AmericanFamily.smallSon.equals(person)) {
            System.out.println(" our president 拜登");
        }
        if (AmericanFamily.daughter.equals(person)) {
            System.out.println(" our president 奧巴馬");
        }

         /* 
         如果上面定義了常量羅斯福,這里就要增加個if'判斷',也就是就要改代碼哈 
         if (AmericanFamily.daughter.equals(person)) {
            System.out.println(" our president 奧巴馬");
        }
        */
    }
}


大家看到了吧,結果竟然是中國小子wcc因為昵稱叫特朗普,當美國人將我進行校驗的時候,得到的結果是這是美國總統特朗普哈哈。於是jdk官網人員開始反思,我目前主要是看出了2點:

  • 美國人肯定不干呀最后,jdk官方這些人更是看不下去,這驗證方式不對呀,這家伙都不會美國人,這也通過了校驗。這不行,我得把類型也得校驗住了,最好在編譯期就提示這個有問題,冒牌貨
  • 這個代碼寫的不好呀,隨着時間的更替,美國總統族譜上的人越來越多,每次增加了常量,ourPresident()這個方法就要增加判斷邏輯,這代碼水呀,我可是jdk官方,這得改。

為了讓編譯器能自動檢查某個值在枚舉的集合內,並且,不同用途的枚舉需要不同的類型來標記,不能混用,我們可以使用enum來定義枚舉類,於是終於在jdk1.5版本,Enum枚舉出來了,接下來我們看下怎么使用,以及它的原理。

2.Enum定義和常用方法

一般的定義形式:
在定義枚舉類型時我們使用的關鍵字是enum,與class關鍵字類似,enum 枚舉名{ 枚舉值表 }; 在枚舉值表中應羅列出所有可用值。這些值也稱為枚舉元素。例如:

enum Color { 
    RED, GREEN, BLUE; 
} 

枚舉類型Color中定義了顏色的值,這里要注意,值一般是大寫的字母,多個值之間以逗號分隔。同時我們應該知道的是枚舉類型可以像類(class)類型一樣,定義為一個單獨的文件,當然也可以定義在其他類內部,更重要的是枚舉常量在類型安全性和便捷性都很有保證,如果出現類型問題編譯器也會提示我們改進,但務必記住枚舉表示的類型其取值是必須有限的,也就是說每個值都是可以枚舉出來的,比如上述描述的一周共有七天。
使用枚舉直接用枚舉名.枚舉值即可,例如Color.RED

enum Color {
    RED, GREEN, BLUE;
}

public class Test {
    // 執行輸出結果
    public static void main(String[] args) {
        Color c1 = Color.RED;
        System.out.println(c1);
    }
}

2.1Enum類常見的方法:

我們使用enum關鍵字(注意是小寫的),創建對應的枚舉類時,默認是繼承了Enum類的,而Enum類里自帶了一些方法並且進行了實現,大家可以看下Enum源碼。

(1)  ordinal()方法: 返回枚舉值在枚舉類種的順序。這個順序根據枚舉值聲明的順序而定。(順序是以0開始的,即0,1,2......)
          Color.RED.ordinal();  //返回結果:0
          Color.BLUE.ordinal();  //返回結果:2
(2)  compareTo()方法: Enum實現了java.lang.Comparable接口,因此可以比較對象與指定對象的順序。Enum中的compareTo返回的是兩個枚舉值的順序之差。當然,前提是兩個枚舉值必須屬於同一個枚舉類,否則會拋出ClassCastException()異常。(具體可見源代碼)
          Color.RED.compareTo(Color.BLUE);  //返回結果 -2,即(0-2)
(3)  values()方法: 靜態方法,返回一個包含全部枚舉值的數組。
          Color[] colors=Color.values();
          for(Color c:colors){
             System.out.print(c+","); 
          }//返回結果:RED,GREEN,BLUE
(4)  toString()方法: 返回枚舉常量的名稱。和.name一樣的效果
          Color c=Color.RED;
          System.out.println(c);//返回結果: RED
(5)  valueOf()方法: 這個方法和toString方法是相對應的,返回帶指定名稱的指定枚舉類型的枚舉常量。
          Color.valueOf("BLUE");   //返回結果: Color.BLUE
(6)  equals()方法: 比較兩個枚舉類對象的引用是否相同,每一個枚舉值其實都是一個類的實例,這個接下來我會講下。

因為這些api比較簡單,就不寫例子一一舉例了,我們把美國總統這個例子改寫下,讓大家體會下使用枚舉后有么有將我們第一部分的問題給解決:

public enum AmericanFamily {

    teRuPu,baiDeng,obama,wccHaHa;

    public static void ourPresident(AmericanFamily person) {
        //大家看這塊代碼是不是以后都不用管改了哈
        for (AmericanFamily human:AmericanFamily.values()){
            if (human.equals(person)){
                System.out.println("this is our president" +" "+ person);
            }
        }
    }

    public static void enumMethodTest(){
        AmericanFamily teRuPu = AmericanFamily.teRuPu;
        int ordinal = teRuPu.ordinal();
        int i = teRuPu.compareTo(AmericanFamily.wccHaHa);
        boolean same = teRuPu.equals(AmericanFamily.wccHaHa);
        System.out.println(teRuPu);
        System.out.println(ordinal);
        System.out.println(i);
        System.out.println(same);
    }

    public static void main(String[] args) {
        AmericanFamily person = AmericanFamily.teRuPu;
        ourPresident(person);
        System.out.println("--------------------");
        String chineseBoyWccNickName = "特朗普";
        System.out.println("你看到了,我是中國小子wcc,可不是AmericanFamily成員,強行使用,我異常了");
        // ourPresident(chineseBoyWccNickName);編譯器檢查就通不過了
        // 不兼容的類型: java.lang.String無法轉換為com.huawei.subtitle.portal.com.AmericanFamily
        ourPresident(AmericanFamily.wccHaHa);
        System.out.println("--------------------");
        enumMethodTest();
    }
}

3.枚舉的原理

我們來看下,我們簡單的創建一個枚舉類,Java底層為我們做了什么,為了搞反編譯,試了很多,因為Java的發展,很多反編譯軟件可能不支持Java的新的語法特性,或者支持不友好:
這里僅附上我使用的反編譯工具下載地址:jad反編譯下載


**直接使用jad命令反編譯,具體如下:**
- 1. 打開命令,將當前位置指向xjad所在目錄
- 2. 使用Jad -sjava xxx.class
即可在同目錄下生成xxx.java的反編譯后的文件


public enum Day {
    MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}

這里直接展示反編譯的結果,具體使用不同工具反編譯的差別,請查看我另一篇文章--暫時起名反編譯工具使用吧嘻嘻。話不多說,附上反編譯后代碼,從反編譯代碼中看這個類的真實樣子:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   Day.java

package com.huawei.subtitle.portal;


public final class Day extends Enum {

    public static Day[] values() {
        return (Day[])$VALUES.clone();
    }

    public static Day valueOf(String s) {
        return (Day)Enum.valueOf(Day, s);
    }

    private Day(String s, int i) {
        super(s, i);
    }

    public static final Day MONDAY;
    public static final Day TUESDAY;
    public static final Day WEDNESDAY;
    public static final Day THURSDAY;
    public static final Day FRIDAY;
    public static final Day SATURDAY;
    public static final Day SUNDAY;
    private static final Day $VALUES[];

    static {
        MONDAY = new Day("MONDAY", 0);
        TUESDAY = new Day("TUESDAY", 1);
        WEDNESDAY = new Day("WEDNESDAY", 2);
        THURSDAY = new Day("THURSDAY", 3);
        FRIDAY = new Day("FRIDAY", 4);
        SATURDAY = new Day("SATURDAY", 5);
        SUNDAY = new Day("SUNDAY", 6);
        $VALUES = (new Day[] {
            MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
        });
    }
}

首先我們從第一行

  • 可以知道enum定義的枚舉本身也是一個class,和我們常見的類使用上沒有差別
  • 這個類默認繼承了Enum接口,由於Java的單繼承,所以enum不能再繼承別的類,同時由於繼承,也就有了Enum接口中的已經實現的方法。
  • 這個類被final修飾,也就是太監類,沒有子類,不允許繼承

接下來我們看成員變量和static代碼塊這塊:

  • static靜態代碼塊的特性大家應該都知道哈,這里會隨着類的加載使用而執行,且僅執行一次,為我們創建了Day這個對象的實例,共計7個,每一個枚舉值都是一個對象的實例,這點要時刻記住,后面會用到
  • 通過私有構造器,並調用父構造器初始化name和ordinal屬性,name值默認就是我們定義的枚舉名稱的字符串形式,如"Monday"
  • VALUS常量是一個Day類型數組,也是在靜態代碼塊中進行的初始化,數組中的值為對應的枚舉類對象

到此我們也就明白了,使用關鍵字enum定義的枚舉類型,在編譯期后,也將轉換成為一個實實在在的類,而在該類中,會存在每個在枚舉類型中定義好變量的對應實例對象,如上述的MONDAY枚舉類型對應public static final Day MONDAY;,同時編譯器會為該類創建兩個方法,分別是values()和valueOf()。ok~,到此相信我們對枚舉的實現原理也比較清晰啦。接下來我們來看下枚舉的高級用法:

4.枚舉的高級用法

向enum類添加方法與自定義屬性和構造函數 重新定義一個日期枚舉類,帶有desc成員變量描述該日期的對於中文描述,同時定義一個getDesc方法,返回中文描述內容,自定義私有構造函數,在聲明枚舉實例時傳入對應的中文描述,代碼如下:

public enum DayDesc {
    MONDAY("星期一", 1),
    TUESDAY("星期二", 2),
    WEDNESDAY("星期三", 3),
    THURSDAY("星期四", 4),
    FRIDAY("星期五", 5),
    SATURDAY("星期六", 6),
    SUNDAY("星期日", 7);

    private String desc;//文字描述
    private Integer code; //對應的代碼

    /**
     * 私有構造,防止被外部調用,默認就是私有構造,且只允許私有構造
     *
     * @param desc
     */
    private DayDesc(String desc, Integer code) {
        this.desc = desc;
        this.code = code;
    }

    /**
     * 定義方法,返回描述,跟常規類的定義沒區別
     *
     * @return
     */
    public String getDesc() {
        return desc;
    }

    /**
     * 定義方法,返回代碼,跟常規類的定義沒區別
     *
     * @return
     */
    public int getCode() {
        return code;
    }

    public static void main(String[] args) {
        for (DayDesc day : DayDesc.values()) {
            System.out.println("name:" + day.name() +
                    ",desc:" + day.getDesc());
        }
    }
}

輸出結果:


枚舉類中定義抽象方法,由於每個枚舉值都是一個對象實例,在枚舉類中定義的抽象方法,會由每個對應的枚舉對象去實現,在第一個枚舉值后面打上{},報紅后alt+enter即可實現方法。先上代碼,給大家看看

public enum Human {
    MEN{
        @Override
        public void say() {
            System.out.println("男:我可以約你出來看月亮么");
        }
    },WOMEN {
        @Override
        public void say() {
            System.out.println("女:好噠,月亮不睡我們不睡");
        }
    };

    public abstract void say();

    public static void main(String[] args) {
        Human.MEN.say();
        Human.WOMEN.say();
    }
}

探索:可能大家還是有疑問哈,這個就簡單的使用Idea內嵌的反編譯工具叫啥flower給大家看下,大概就知道原理了:
首先執行javac Human.java 對該類進行編譯,我們會發現得到了三個class文件:

直接用Idea內置的反編譯,默認已經嵌入,大家什么都不用干,直接打開.class文件即可。代碼如下:

package com.huawei.subtitle.portal;

public enum Human {
    MEN {
        public void say() {
            System.out.println("男:我可以約你出來看月亮么");
        }
    },
    WOMEN {
        public void say() {
            System.out.println("女:好噠,月亮不睡我們不睡");
        }
    };

    private Human() {
    }

    public abstract void say();

    public static void main(String[] var0) {
        MEN.say();
        WOMEN.say();
    }
}
---
enum Human$1 {
    Human$1() {
    }

    public void say() {
        System.out.println("男:我可以約你出來看月亮么");
    }
}

---
enum Human$2 {
    Human$2() {
    }

    public void say() {
        System.out.println("女:好噠,月亮不睡我們不睡");
    }
}

首先我們看到由於我們定義了抽象方法say(),所以Human這個類是抽象的,要不違背了Java基礎有抽象方法的類肯定是抽象類,然后兩個枚舉對象,分別實現了Human這個類並重寫了say()方法,看到這里大家應該懂了吧。好吧,今天就寫到這里吧,我有點累了,准備去公司樓下健身房鍛煉下。
有什么問題留言即可,基本每天都會登錄我的博客。誰讓我笨但是勤勞嘞嘻嘻


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM