Day21
1.枚舉的引入
需求:編寫季節類(Season),該類只有四個對象(spring,summer,autumn,winter)
概念:枚舉(enum)全稱為 enumeration, 是 JDK 1.5 中引入的新特性。
public enum Color{//默認添加 public static finalRED,GREEN,BLUE;
}
本質:盡管枚舉看起來像是一種新的數據類型,實際上,枚舉就是一種受限制的類,并且具有自己的方法。創建自己的enum類時,這個類繼承自 java.lang.Enum。
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable{...
}
特點
- 枚舉就是一個受限制的類,默認繼承Enum
- 枚舉的第一行必須定義該枚舉類型的對象
- 枚舉類型對象默認添加: public static final 類型
- 枚舉沒有繼承明確類(自定義枚舉類默認繼承Enum,Enum默認繼承Object)
- 枚舉類不能被繼承
- 枚舉里可以有構造方法、成員方法、靜態方法、抽象方法
- 枚舉可以實現接口
- 枚舉里沒有定義方法,可以在最后一個對象后面加逗號、分號或什么都不加
優勢
增強代碼可讀性
枚舉型可直接與數據庫交互
switch語句優勢
編譯優勢
(枚舉類編譯時,沒有把常量值編譯到代碼中,即使常量值發生改變,也不會影響引用常量的類 )
將常量組織起來,統一管理
去除equals兩者判斷 由于常量值地址唯一,使用枚舉可以直接通過“==”進行兩個值之間的對比,性能會有所提高
經驗:一個類有固定幾個對象,就用枚舉代替
常規寫法:
public class Test01 {public static void main(String[] args) {Season spring = Season.spring;Season summer = Season.summer;Season autumn = Season.autumn;Season winter = Season.winter;System.out.println(spring);System.out.println(summer);System.out.println(autumn);System.out.println(winter);}
}
public class Season {public static final Season spring = new Season("春天", "萬物復蘇");public static final Season summer = new Season("夏天", "汗如雨下");public static final Season autumn = new Season("秋天", "秋高氣爽");public static final Season winter = new Season("冬天", "銀裝素裹");private String name;private String info;private Season() {}private Season(String name, String info) {this.name = name;this.info = info;}// get,set,toString省略
}
枚舉寫法:
注意:使用枚舉來解決該需求
經驗:一個類有固定幾個對象,就用枚舉代替
public class Test01 {public static void main(String[] args) {Season spring = Season.spring;Season summer = Season.summer;Season autumn = Season.autumn;Season winter = Season.winter;System.out.println(spring);System.out.println(summer);System.out.println(autumn);System.out.println(winter);}
}
//注意:枚舉就是一個特殊的類,但是他也是引用數據類型的一種
//注意:枚舉沒有顯示繼承
//注意:枚舉有隱式繼承 -> Season extends Enum extends Object
public enum Season{//注意:枚舉類第一行必須聲明對象//注意:枚舉對象默認使用public static final修飾 -- public static final Season spring = new Season("春天","春雨綿綿");spring("春天","春雨綿綿"),summer("夏天","烈日炎炎"),autumn("秋天","碩果累累"),winter("冬天","白雪皚皚");private String name;private String info;//注意:枚舉類的構造方法都是私有的private Season() {}private Season(String name, String info) {this.name = name;this.info = info;}//get,set,toString省略
}
2.枚舉的常用方法
方法名 | 解釋 |
---|---|
Enum.valueOf(Class enumType, String name) | 根據字符串找到該枚舉類中的對象 |
public static void values() | 獲取該枚舉類對象數組 |
public static void valueOf(String args0) | 根據字符串獲取該枚舉類中的對象 |
public class Test02 {public static void main(String[] args) {//通過字符串獲取枚舉類中的對象Season season1 = Enum.valueOf(Season.class, "spring");System.out.println(season1);//通過字符串獲取枚舉類中的對象Season season2 = Season.valueOf("spring");System.out.println(season2);//獲取Season枚舉類中所有的對象,返回數組Season[] values = Season.values();for (Season season : values) {System.out.println(season);}}
}
3.手撕枚舉底層源碼
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {private final String name;//枚舉對象名private final int ordinal;//枚舉編號(從0開始)protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}}
//枚舉本質意義上就是一個類,默認繼承Enum
public final class Season extends Enum{public static final Season spring;public static final Season summer;public static final Season autumn;public static final Season winter;private String name;private String info;//存放該枚舉類的所有對象 -- 數組private static final Season[] ENUM$VALUES;//靜態代碼塊(初始化枚舉對象、將枚舉對象存入ENUM$VALUES數組中)static{spring = new Season("spring", 0, "春天", "春雨綿綿");summer = new Season("summer", 1, "夏天", "烈日炎炎");autumn = new Season("autumn", 2, "秋天", "碩果累累");winter = new Season("winter", 3, "冬天", "白雪皚皚");ENUM$VALUES = (new Season[] {spring, summer, autumn, winter});}//改造了我們寫的無參構造 -- private Season(){}private Season(String s, int i){super(s, i);}//改造了我們寫的有參構造 -- private Season(String name,String info){}private Season(String s, int i, String name, String info){super(s, i);this.name = name;this.info = info;}//get,set省略public String toString(){return (new StringBuilder(String.valueOf(name))).append(" -- ").append(info).toString();}public static Season[] values(){Season[] aseason = ENUM$VALUES;//獲取ENUM$VALUESint i = aseason.length;//i - 4//創建了一個與ENUM$VALUES長度相同的數組Season[] aseason1 = new Season[i];System.arraycopy(aseason, 0, aseason1, 0, i);return aseason1;}public static Season valueOf(String s){return (Season)Enum.valueOf(com/qf/enum02/Season, s);}}
4.面試題
4.1 研究switch如何判斷枚舉類型
注意:底層會創建一個枚舉個數的數組 – ai[1,2,3,4]
public class Test03 {public static void main(String[] args) {switch (Season.spring) {case spring:System.out.println("春天");break;case summer:System.out.println("夏天");break;case autumn:System.out.println("秋天");break;case winter:System.out.println("冬天");break;}}
}
底層源碼
public class Test03{private static int[] $SWITCH_TABLE$com$qf$enum02$Season;//[1,2,3,4]public static void main(String args[]){//[1,2,3,4]int[] ai = $SWITCH_TABLE$com$qf$enum02$Season();switch (ai[Season.spring.ordinal()]){//ai[0] -- 1case 1: // '\001'System.out.println("春天");break;case 2: // '\002'System.out.println("夏天");break;case 3: // '\003'System.out.println("秋天");break;case 4: // '\004'System.out.println("冬天");break;}}static int[] $SWITCH_TABLE$com$qf$enum02$Season(){//[0,0,0,0]int[] ai = new int[Season.values().length];//Season.values().length - 4try{//Season.autumn.ordinal() -- 2ai[Season.autumn.ordinal()] = 3;//ai[2] = 3;}catch (NoSuchFieldError ) { }try{//Season.spring.ordinal() - 0ai[Season.spring.ordinal()] = 1;//ai[0] = 1}catch (NoSuchFieldError ) { }try{//Season.summer.ordinal - 1ai[Season.summer.ordinal()] = 2;//ai[1] = 2}catch (NoSuchFieldError ) { }try{//Season.winter.ordinal() - 3ai[Season.winter.ordinal()] = 4;//ai[3] = 4}catch (NoSuchFieldError ) { }//ai -> [1,2,3,4]return $SWITCH_TABLE$com$qf$enum02$Season = ai;}
}
4.2 研究switch如何判斷String類型
注意:底層原理就是先判斷hash值,然后再判斷兩個字符串是否相同(equals)
public class Test04 {public static void main(String[] args) {switch ("abc") {case "abc":System.out.println("abc");break;case "def":System.out.println("def");break;case "zyx":System.out.println("xyz");break;}}
}
底層源碼
//研究String的hash值是如何算出來的
String str = "abc";public class String{final char[] value;//['a','b','c']public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char[] val = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;}}
public class Test04{public static void main(String args[]){String s;switch ((s = "abc").hashCode()){//96354case 96354://"abc".hashCode();if (s.equals("abc"))System.out.println("abc");break;case 99333: //"def".hashCode();if (s.equals("def"))System.out.println("def");break;case 121113: //"zyx".hashCode();if (s.equals("zyx"))System.out.println("xyz");break;}}
}
補:hash值一樣,不需要if判斷可以嗎?
public class Test05 {/*** 知識點:研究switch如何判斷String類型* * 注意:底層原理就是先判斷hash值,然后再判斷兩個字符串是否相同(equals)*/public static void main(String[] args) {System.out.println("Aa".hashCode());System.out.println("BB".hashCode());switch ("Aa") {case "BB":System.out.println("BB");break;case "Aa":System.out.println("Aa");break;case "zyx":System.out.println("xyz");break;}}
}
public class Test05{public static void main(String args[]){String s;switch ((s = "Aa").hashCode()){//2112case 2112: //"BB".hashCode() -- "Aa".hashCode()if (!s.equals("BB")){if (s.equals("Aa"))System.out.println("Aa");} else{System.out.println("BB");}break;case 121113: if (s.equals("zyx"))System.out.println("xyz");break;}}
}
5.枚舉案例 之 狀態機
//信號燈的枚舉
public enum Signal {RED,YELLOW,GREEN;
}
public class Test01 {public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.println("請選擇信號燈:RED、YELLOW、GREEN");String str = scan.next();Signal signal = Signal.valueOf(str);String trafficInstruct = getTrafficInstruct(signal);System.out.println(trafficInstruct);scan.close();}public static String getTrafficInstruct(Signal signal){String instruct = "信號燈故障";switch (signal) {case RED:instruct = "紅燈停";break;case YELLOW:instruct = "黃燈請注意";break;case GREEN:instruct = "綠燈行";break;}return instruct;}
}
6.枚舉案例 之 錯誤碼/狀態碼
public enum AddCode {ERR_1(-1,"添加失敗 - 學生信息不合法"),ERR_2(-2,"添加失敗 - 有該學生"),OK(1,"添加成功");private int code;private String message;private AddCode() {}private AddCode(int code, String message) {this.code = code;this.message = message;}//get,set,toString省略
}
public class Test01 {public static void main(String[] args) {System.out.println(AddCode.ERR_1.getCode());System.out.println(AddCode.ERR_1.getMessage());System.out.println(AddCode.ERR_1);}
}
7.枚舉案例 之 組織枚舉
應用場景:把一些同類別的枚舉使用類或接口組織起來
注意:一般使用接口去組織多個枚舉
原因:
使用類去組織枚舉,類里的枚舉默認添加static修飾 – 靜態內部類
使用接口去組織枚舉,接口的枚舉默認添加public static修飾 – 接口內部類(公有靜態內部類)
就是說,在類中組織 enum,如果你不給它修飾為 public,那么只能在本包中進行訪問。
? 考慮到其他包中還可以使用到枚舉對象,所以推薦使用接口去組織
public interface Code {enum UpdateCode {ERR_1(-1,"修改失敗 -- 學生信息不合法"),ERR_2(-2,"修改失敗 -- 沒有該學生"),ERR_3(-3,"修改失敗 -- 修改值的信息不合法"),ERR_4(-4,"修改失敗 -- 目標班級上有學生"),ERR_5(-5,"修改失敗 -- 目標學號上有學生"),OK(1,"修改成功");private int code;private String message;private UpdateCode() {}private UpdateCode(int code, String message){this.code = code;this.message = message;}//get,set,toString省略}}
8.枚舉案例 之 策略枚舉
優點:這種枚舉通過枚舉嵌套枚舉的方式,將枚舉常量分類處理。
這種做法雖然沒有switch語句簡潔,但是更加安全、靈活。
需求:模擬公司計算工資的功能
分析:
員工類別:行政、講師
部門:Java(講師)、Python(講師)、HTML(講師)、總經辦(行政)、人力(行政)、財務(行政)
行政工資構成:基本工資 + 績效
講師工資構成:基本工資 + 績效 + 課時費*課時
//public final class QianFeng extends Enum
public enum QianFeng {Java(StaffType.teacher),Python(StaffType.teacher),HTML(StaffType.teacher),GeneralManagerOffice(StaffType.admin),HR(StaffType.admin),finance(StaffType.admin);private StaffType staffType;private QianFeng(StaffType staffType) {this.staffType = staffType;}public double getSalary(double basicSalary, double performance, double classFees, double classHours){return staffType.calculateSalary(basicSalary, performance, classFees, classHours);}//員工類型//public static abstract class QianFeng$StaffType extends Enumpublic enum StaffType{//class QianFeng$StaffType$1 extends QianFeng$StaffType//QianFeng$StaffType$1 admin = QianFeng$StaffType$1(){// @Override// public double calculateSalary(double basicSalary, double performance, double classFees, double classHours) {// return 0;// }//}admin {//行政類別@Overridepublic double calculateSalary(double basicSalary, double performance, double classFees, double classHours) {BigDecimal big1 = new BigDecimal(String.valueOf(basicSalary));BigDecimal big2 = new BigDecimal(String.valueOf(performance));double salary = big1.add(big2).doubleValue();return salary;}},//class QianFeng$StaffType$2 extends QianFeng$StaffType//QianFeng$StaffType$2 teacher = QianFeng$StaffType$2(){// @Override// public double calculateSalary(double basicSalary, double performance, double classFees, double classHours) {// return 0;// }//}teacher {@Overridepublic double calculateSalary(double basicSalary, double performance, double classFees, double classHours) {BigDecimal big1 = new BigDecimal(String.valueOf(basicSalary));BigDecimal big2 = new BigDecimal(String.valueOf(performance));BigDecimal big3 = new BigDecimal(String.valueOf(classFees));BigDecimal big4 = new BigDecimal(String.valueOf(classHours));double salary = big3.multiply(big4).add(big1).add(big2).doubleValue();return salary;}};public abstract double calculateSalary(double basicSalary, double performance, double classFees, double classHours);}
}
public class Test01 {public static void main(String[] args) {double salary1 = QianFeng.Java.getSalary(1800, 200, 88, 9);System.out.println(salary1);double salary2 = QianFeng.GeneralManagerOffice.getSalary(50000, 30000, 0, 0);System.out.println(salary2);}
}
總結
1.枚舉特點,優勢
2.枚舉的常用方法
3.底層原理
4.面試題
5.枚舉案例
狀態機
錯誤碼
組織枚舉
策略枚舉 — 難點