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.枚举案例
状态机
错误码
组织枚举
策略枚举 — 难点