枚举的意义
我们在很多时候会拿枚举和常量来做对比,可实际我们在程序中大量使用枚举的地方就是为了代替常量。因为相对于静态的常量,枚举类显得更加直观,类型也相对更加安全。
枚举在生活中非常常见,列举如下:
- 表示星期几:SUNDAY、MONDAY、TUESTDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举;
- 性别:MALE(男)、FEMALE(女)也是一个枚举;
- 订单的状态:PAIDED(已付款)、UNPAIDED(未付款)、FINISHED(已完成),CANCELED(已取消)。
安全性
若一个方法中要求入参为int类型,开发者传入任意的int类型值就行,但是如果是枚举类型的话,就只能传入枚举类中包含的对象。
命名空间
开发者要在命名的时候建议以枚举类的共有属性名称开头,例如定义季节的枚举类,在枚举类中定义四个季节,则枚举类以SEASON_开头,这样另外一个开发者再看这段代码的时候,才知道这四个常量分别代表季节。
枚举的特点
- enum和class、interface的地位一样
- 类的对象只有有限个,确定的。我们称此类为枚举类;
- 当需要定义一组常量时,强烈建议使用枚举类;
- 如果枚举类中只有一个对象,则可以作为单例模式的实现方式;
- switch参数可以使用Enum;
- enum 允许为eunm 实例编写方法。所以可以为每个enum 实例赋予各自不同的行为。
- 使用enum定义的枚举类默认继承了java.lang.Enum,而不是继承Object类,由于Java是单继承机制,所以枚举类不可以被继承,枚举类可以实现一个或多个接口。
- 枚举类的所有实例都必须放在第一行展示,不需使用new 关键字,不需显式调用构造器。自动添加public static final修饰。
- 枚举类的构造器只能是私有的。
枚举类应用
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public enum Season { spring, SUMMER, AUTUMN, WINTER; }
public static void main(String[] args) { Season season = Season.spring; System.out.println(season); }
public enum Season { spring("春天","绿色"), SUMMER("夏天","橙色"), AUTUMN("秋天","黄色"), WINTER("冬天","白色");
private final String name; private final String color; Season(String name,String color) { this.name = name; this.color = color; } public String getName() { return name; }
public String getColor() { return color; } }
public static void main(String[] args) { Season season = Season.spring; System.out.println(season.getColor()); System.out.println(Season.spring.getColor()); }
|
常用方法
java.lang.Enum类 是 Java 语言枚举类型的公共基类,我们使用enum关键字定义的枚举类,是隐式继承自Enum类的,下面我们来看一下Enum类的常用方法:
- **values()**:返回枚举类型的对象数组。该方法可以很方便的遍历所有的枚举值;
- **valueOf()**:可以把一个字符串转换为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”,如果不是,会抛出IllegalArguementException;
- **toString()**:返回当前枚举类对象常量的名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class Test2 { public static void main(String[] args) { Test test1 = Test.man; System.out.println("调用 toString方法"); System.out.println(test1.toString()); System.out.println("调用values方法"); Test[] values = Test.values(); for (Test value : values){ System.out.println(value); } System.out.println("调用values方法"); Test test2 = Test.valueOf("man"); System.out.println(test2); }
enum Test{ man("男"), woman("女"), unknow("未知"); private final String sexName; Test(String sexName){ this.sexName=sexName; } public String getSexName() { return sexName; } } }
|
1 2 3 4 5 6 7 8
| 调用 toString方法 man 调用values方法 man woman unknow 调用values方法 man
|
值得注意的是,当调用valuOf()方法时,我们传递的对象的“名字”,在枚举类中并不存在,此时会抛出运行时异常:****IllegalArgumentException,实例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Test2 { public static void main(String[] args) { Test test1 = Test.man; System.out.println("调用values方法"); Test test2 = Test.valueOf("man1"); System.out.println(test2); }
enum Test{ man("男"), woman("女"), unknow("未知"); private final String sexName; Test(String sexName){ this.sexName=sexName; } public String getSexName() { return sexName; } } }
|
1 2 3 4 5
| 调用values方法 Exception in thread "main" java.lang.IllegalArgumentException: No enum constant com.caq.exception.Test2.Test.man1 at java.lang.Enum.valueOf(Enum.java:238) at com.caq.exception.Test2$Test.valueOf(Test2.java:12) at com.caq.exception.Test2.main(Test2.java:8)
|
枚举类对象实现接口的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| interface Info{ void show(); }
enum Season1 implements Info{ SPRING("春天","春暖花开"){ @Override public void show() { System.out.println("春天在哪里?"); } }, SUMMER("夏天","夏日炎炎"){ @Override public void show() { System.out.println("宁夏"); } }, AUTUMN("秋天","秋高气爽"){ @Override public void show() { System.out.println("秋天不回来"); } }, WINTER("冬天","冰天雪地"){ @Override public void show() { System.out.println("大约在冬季"); } }; }
|
单例模式
单例模式可以说是每个Java开发者必须掌握的一个设计模式了,通常我们说它的实现,有饱汉式和饿汉式,也有经常说的双重判断,今天我们介绍另外一种方式,借助枚举来实现:
1 2 3 4 5 6 7 8 9 10 11
| public enum SingleEnum { INSTANCE;
public void print(String word) { System.out.println(word); } } @Test public void testSingle() { SingleEnum.INSTANCE.print("hello world"); }
|
如上,用枚举实现单例模式真的非常简单,将类声明为枚举,内部只定义一个值即可。这样做主要是基于枚举的三个特性,即:枚举类不能new,因此保证单例、枚举类不能被继承、类不加载时,不会实例化。
使用枚举类创建的单例还有一个好处,就是使用了反射也无法打破它的单例性质,这是相比较于其他的实现方式的一个优点。但是在实际的项目中这种写法并不常见,这是因为我们习惯了将枚举作为常量来使用,很少在枚举类类中,添加复杂的业务逻辑。
策略模式
除了轻松实现上面的单例模式之外,枚举还可以非常简单的实现策略模式,比如下面这个例子:现有一个接口,通过接受的参数,来决定最终的数据存在什么地方,如果按照正常的写法,可能就是很多的if/else
1 2 3 4 5 6 7 8 9 10 11 12
| public void save(String type, Object data) { if ("mysql".equals(type) { saveMySQL(data); } else if ("redis".equals(type)) saveRedis(data); } else if ("file".eqauls(type)) { saveFile(type); } }
|
以上写法虽说简单直观,但是当type类型多了之后,这个if/else的代码行数就会越来越多了,而且看起来也不美观。如果我们换成枚举,基于策略模式的思想来解决上面的if/else问题,就会好的多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public enum SaveStrategyEnum { MYSQL("mysql") { @Override public void save(Object obj) { System.out.println("save in mysql:" + obj); } }, REDIS("redis") { @Override public void save(Object obj) { System.out.println("save in redis: " + obj); } }, FILE("file") { @Override public void save(Object obj) { System.out.println("save in file: " + obj); } };
private String type;
SaveStrategyEnum(String type) { this.type = type; }
public abstract void save(Object obj);
public static SaveStrategyEnum typeOf(String type) { for (SaveStrategyEnum strategyEnum: values()) { if (strategyEnum.type.equalsIgnoreCase(type)) { return strategyEnum; } } return null; } }
public void save(String type, Object data) { SaveStrategyEnum strategyEnum = SaveStrategyEnum.typeOf(type); if (strategyEnum != null) { strategyEnum.save(data); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| public class enumTest { public static void main(String[] args) { enumTest enumTest = new enumTest(); enumTest.save("MYSQL","我是MySQL"); } public void save(String type, Object data) { SaveStrategyEnum strategyEnum = SaveStrategyEnum.typeOf(type); if (strategyEnum != null) { strategyEnum.save(data); } } }
|
以上主要利用的是抽象类 + 枚举来完成不同的策略具体实现,这种实现方式体现了抽象方法的使用 (在模板设计模式中,更能体会抽象方法的使用妙处),利用枚举原生提供的values(),来实现遍历,找到目标。