JAVA基础之枚举
历史
枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数。枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型。在 JDK 1.5 之前没有枚举类型,那时候一般用接口常量来替代。而使用 Java 枚举类型 enum 可以更贴近地表示这种常量。
基本语法
声明枚举
Java 中的每一个枚举都继承自 java.lang.Enum 类。当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,这些枚举成员默认都被 final、public, static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。
public enum Sex {
// 男
MALE,
// 女
FEMALE,
// 男变女
MALE_TO_FEMALE,
// 女变男
FEMALE_TO_MALE;
}
public enum Sex {
// 男
MALE(100),
// 女
FEMALE(200),
// 男变女
MALE_TO_FEMALE(300),
// 女变男
FEMALE_TO_MALE(400);
/**
* 成员变量
*/
private int sequence;
private Sex(int sequence) {
this.sequence = sequence;
}
public int getSequence() {
return this.sequence;
}
}
枚举类方法
方法名称 | 描述 |
---|---|
values() | 以数组形式返回枚举类型的所有成员 |
valueOf() | 将普通字符串转换为枚举实例 |
compareTo() | 比较两个枚举成员在定义时的顺序 |
ordinal() | 获取枚举成员的索引位置 |
public static void main(String[] args) {
// 字符串转枚举
Sex e = Sex.valueOf("MALE");
System.out.println(Sex.MALE.equals(e));
}
public static void main(String[] args) {
// 获取所有成员数组
Sex[] values = Sex.values();
System.out.println(values.length);
}
public static void main(String[] args) {
// 比较两个枚举的顺序(返回值等于枚举项ordinal()返回值相减)
System.out.println(Sex.MALE.compareTo(Sex.FEMALE));
}
public static void main(String[] args) {
// 获取枚举成员的索引位置
System.out.println(Sex.MALE.ordinal());
}
覆写基类的方法
public enum Sex {
// 男
MALE(100),
// 女
FEMALE(200),
// 男变女
MALE_TO_FEMALE(300),
// 女变男
FEMALE_TO_MALE(400);
/**
* 成员变量
*/
private int sequence;
private Sex(int sequence) {
this.sequence = sequence;
}
public int getSequence() {
return this.sequence;
}
/**
* 覆写基类的toString
*/
@Override
public String toString() {
return this.name()+this.sequence;
}
}
switch
public static void main(String[] args) {
Sex sex = Sex.MALE;
switch (sex) {
case MALE:
System.out.println("性别是男");
break;
case FEMALE:
System.out.println("性别是女");
break;
case MALE_TO_FEMALE:
System.out.println("性别是男转女");
break;
case FEMALE_TO_MALE:
System.out.println("性别是女转男");
break;
default:
System.out.println("性别是未知");
break;
}
}
应用场景
定义常量
对于一组相关的常量,在没有枚举之前,通常采用的是定义一个常量类批量声明常量值的做法。但是在JDK1.5引入枚举之后,可以使用枚举来处理相关常量的定义问题,可以有效的提高代码的整洁性、可读性、可维护性等等。
/**
*
* @ClassName: OrderTypeConstant
* @Description: 订单类型常量池
* @author: 王同学
* @date: 2023年1月5日 下午2:44:59
* @Copyright:
*/
public final class OrderTypeConstant {
// 普通订单
public final static int ORDINARY = 0;
// 活动订单
public final static int ACTIVITY = 1;
// 团购订单
public final static int GROUPON = 2;
}
/**
*
* @ClassName: OrderTypeEnum
* @Description: 订单类型枚举
* @author: 王同学
* @date: 2023年1月5日 下午2:44:59
* @Copyright:
*/
public enum OrderTypeEnum {
// 普通订单
ORDINARY,
// 活动订单
ACTIVITY,
// 团购订单
GROUPON;
}
替换if-else
需求:购物APP的会员等级有1、2、3、4,每级会员在下单购买时享受的折扣都不同。现在我们需要写一个函数根据会员等级计算订单金额。
从下面的代码示例可以看出我们将获取折扣的逻辑封装在了枚举内部,使 orderAmount(int level,double amount)
函数看起来更简洁,同时如果增加了新的会员等级我们只需要修改枚举类 Vip
就可以。
扩展:这个示例并不是想说明你能把if-else给干掉,更多的是提现一种编码习惯,让代码的逻辑收敛到合适的类中,让代码更简洁,方便以后的维护。
使用if-else的实现
// 使用if-else判断
public double orderAmount(int level,double amount) {
double discount = 1.0;
if(level==1) {
discount = 0.9;
}else if(level==2) {
discount = 0.8;
}else if(level==3) {
discount = 0.7;
}else if(level==4) {
discount = 0.6;
}
return amount*discount;
}
使用枚举优化
/**
*
* @ClassName: Discount
* @Description: 获取折扣
* @author: 王同学
* @date: 2023年1月5日 下午4:59:30
* @Copyright:
*/
public interface Discount {
public double get();
}
/**
*
* @ClassName: Vip
* @Description: 会员等级枚举
* @author: 王同学
* @date: 2023年1月5日 下午5:06:28
* @Copyright:
*/
public enum Vip implements Discount {
_0(){
@Override
public double get() {
return 1.0;
}
},
_1(){
@Override
public double get() {
return 0.9;
}
},
_2(){
@Override
public double get() {
return 0.8;
}
},
_3(){
@Override
public double get() {
return 0.7;
}
},
_4(){
@Override
public double get() {
return 0.6;
}
};
public static Vip get(int level) {
if(level==1) {
return _1;
}
if(level==2) {
return _2;
}
if(level==3) {
return _3;
}
if(level==4) {
return _4;
}
return _0;
}
}
public double orderAmount(int level,double amount) {
double discount = Vip.get(level).get();
return amount*discount;
}
策略模式
下面的代码是使用策略模式解决上个小结需求的示例。使用枚举解决了策略模式面临的类膨胀问题(这个问题需要分情况当我们的模式实现比较复杂的情况下还是建议使用单独的类实现策略抽象)。
/**
*
* @ClassName: Discount
* @Description: 策略抽象
* @author: 王同学
* @date: 2023年1月5日 下午4:59:30
* @Copyright:
*/
public interface Discount {
public double get();
}
/*
* 策略实现 vip0
*/
public class Vip0 implements Discount{
@Override
public double get() {
return 1.0;
}
}
/*
* 策略实现 vip1
*/
public class Vip1 implements Discount{
@Override
public double get() {
return 1.0;
}
}
/*
* 策略实现 vip2
*/
public class Vip2 implements Discount{
@Override
public double get() {
return 1.0;
}
}
/*
* 策略实现 vip3
*/
public class Vip3 implements Discount{
@Override
public double get() {
return 1.0;
}
}
/*
* 策略实现 vip4
*/
public class Vip4 implements Discount{
@Override
public double get() {
return 1.0;
}
}
/**
*
* @ClassName: Context
* @Description: 执行环境
* @author: 王同学
* @date: 2023年1月5日 下午6:00:45
* @Copyright:
*/
public class Context {
private Discount discount;
private Context(Discount discount) {
this.discount = discount;
}
public static Context get(int level) {
Context context = null;
if(level==1) {
context = new Context(new Vip1());
}else if(level==2) {
context = new Context(new Vip1());
}else if(level==3) {
context = new Context(new Vip1());
}else if(level==4) {
context = new Context(new Vip1());
}else {
context = new Context(new Vip0());
}
return context;
}
public double orderAmount(double amount) {
return amount*this.discount.get();
}
}
public double orderAmount(int level,double amount) {
Context context = Context.get(level);
return context.orderAmount(amount);
}
扩展阅读
内存存储
以上个章节的 OrderTypeEnum
为例,通过 javap
工具查看对应的字节码文件可以发下,枚举的底层还是使用的对象,我们的枚举项就是对象变量名,并且每个对象都使用 public static final
进行了修饰。
$ javap -verbose OrderTypeEnum
...
public static final com.wwq.test.mj.OrderTypeEnum ORDINARY;
descriptor: Lcom/wwq/test/mj/OrderType2;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final com.wwq.test.mj.OrderTypeEnum ACTIVITY;
descriptor: Lcom/wwq/test/mj/OrderType2;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final com.wwq.test.mj.OrderTypeEnum GROUPON;
descriptor: Lcom/wwq/test/mj/OrderType2;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
...
EnumMap
为了更好地支持枚举类型,java.util 中添加了个新类:EnumMap
可以更高效地操作枚举类型。由于枚举类型实例的数量相对固定且有限,所以 EnumMap
使用数组来存放与枚举类型对应的值,使得 EnumMap
的效率非常高。
public static void main(String[] args) {
EnumMap<OrderTypeEnum, Object> map = new EnumMap<OrderTypeEnum, Object>(OrderTypeEnum.class);
map.put(OrderTypeEnum.ORDINARY, "普通订单");
map.put(OrderTypeEnum.ACTIVITY, "活动订单");
map.put(OrderTypeEnum.GROUPON, "团购订单");
}
EnumSet
为了更好地支持枚举类型,java.util 中添加了个新类:EnumSet
,新类提供了许多工厂方法以便于初始化。
方法名称 | 描述 |
---|---|
allOf(Class element type) | 创建一个包含指定枚举类型中所有枚举成员的 EnumSet 对象 |
complementOf(EnumSet s) | 创建一个与指定 EnumSet 对象 s 相同的枚举类型 EnumSet 对象, 并包含所有 s 中未包含的枚举成员 |
copyOf(EnumSet s) | 创建一个与指定 EnumSet 对象 s 相同的枚举类型 EnumSet 对象, 并与 s 包含相同的枚举成员 |
noneOf(<Class elementType) | 创建指定枚举类型的空 EnumSet 对象 |
of(E first,e…rest) | 创建包含指定枚举成员的 EnumSet 对象 |
range(E from ,E to) | 创建一个 EnumSet 对象,该对象包含了 from 到 to 之间的所有枚 举成员 |
枚举与穷尽
枚举:
- 数量:数量少且相对固定。
- 方案:可选方案少,可以快速找到最优方案。
穷尽:
- 数量:数量多并且之间可能没有明显规律。
- 方案:可选方案很多,且没有办法快速找到最优方案。