Java中级——enum

用enum代替int常量

在程序中,经常用int类型的常量表示对象的状态等信息,这种方式被称为int枚举模式,其是编译时常量,当int值发生变化时,需重新编译才能生效,如下

public static final int OFF = 0;
public static final int STANDBY = 1;
public static final int ON = 2;

每个enum默认继承java.lang.Enum,内部的enum实例都为公有静态常量,可直接通过类名访问

public enum State {
    OFF, STANDBY, ON;
}

方法values()将内部实例转为数组,ordinal()可获取实例位置(从0开始),valueOf()返回指定字符串所对应的Enum实例

State[] values = State.values();
for (State value : values) {
    System.out.println(value + " at index " + value.ordinal());
}
System.out.println(State.valueOf("OFF").getClass().getSimpleName());

如上打印(toString默认打印实例名)

OFF at index 0
STANDBY at index 1
ON at index 2
State

enum介绍

不同枚举可包含同名实例,因为每个枚举都有自己的命名空间,当新增某个值时也无需对其重新编译

public enum TVState {
    OFF, STANDBY, ON;
}

public enum DVDState {
    OFF, STANDBY, ON;
}

如上如果为int常量则需加上前缀作为区分,若将DVD_STATE_OFF传入需要TV_STATE_OFF的函数,也不会有任何问题,因为不会对int进行类型检查

public static final int TV_STATE_OFF = 0;
public static final int TV_STATE_STANDBY = 1;
public static final int TV_STATE_ON = 2;

public static final int DVD_STATE_OFF = 0;
public static final int DVD_STATE_STANDBY = 1;
public static final int DVD_STATE_ON = 2;

枚举还可以声明域(均为final,需放在后面)、方法(内部的每个对象都要实现其抽象方法和构造函数

public enum Operation {
    PLUS("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            return x / y;
        }
    };
    
    private final String symbol;
    
    Operation(String symbol) {
        this.symbol = symbol;
    }
    
    public abstract double apply(double x, double y);
}

调用过程如下

for (Operation operation : Operation.values()) {
    System.out.println("4.0" + operation + "2.0" + "=" + operation.apply(4.0, 2.0));
}

打印如下

4.0+2.0=6.0
4.0-2.0=2.0
4.0*2.0=8.0
4.0/2.0=2.0

避免使用ordinal()

ordinal()可返回枚举实例的位置,其从0开始

  • 除非确定实例的位置与顺序不变,不然应该避免使用ordinal()
  • 也不能由ordinal()导出相关值,而应该将关联的值保存在实例域中
public enum State {
    OFF(1), STANDBY(2), ON(3);
    
    private final int index;
    
    State(int index) {
        this.index = index;
    }
    
    public int getIndex() {
        return index;
    }
}

策略枚举

如下是计算当日工资的枚举,基本工资为10块,超过8小时为1.5倍,周末为2倍

enum PayDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
    double base = 10;

    double pay(int hours) {
        switch (this) {
            case SATURDAY:
            case SUNDAY:
                return hours * base * 2;
            default:
                return 8 * base + ((hours > 8) ? (hours - 8) * base * 1.5 : 0);
        }
    }
}

当需要增加一个Holiday(3倍工资),需同步增加switch语句,如果忘记则走default,为了强制实现在每次添加新实例都选择一个计算策略,可采用如下的策略枚举

enum PayDay {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);

    private final PayType payType;

    PayDay(PayType payType) {
        this.payType = payType;
    }

    PayDay() {
        this(PayType.WEEKDAY);
    }

    private enum PayType {
        WEEKDAY {
            double pay(int hours) {
                return hours * base * 2;
            }
        },
        WEEKEND {
            double pay(int hours) {
                return 8 * base + ((hours > 8) ? (hours - 8) * base * 1.5 : 0);
            }
        };

        private static double base = 10;

        abstract double pay(int hours);
    }
}

用EnumSet代替位域

如下有用于表示方向的位域

public static final int EAST = 1 << 0;
public static final int WEST = 1 << 1;
public static final int SOUTH = 1 << 2;
public static final int NORTH = 1 << 3;

private int direction = 0;

public void setDirection(int direction) {
    this.direction = direction;
}

public int getDirection() {
    return direction;
}

当需要表示东南方向时,需要传递 EAST | SOUTH,打印输出5显示不是可读的结果

setDirection(EAST | SOUTH);
System.out.println(getDirection());

当转变为使用Enum

enum Direction {
    EAST, WEST, SOUTH, NORTH;
}

利用EnumSet存储Enum并打印输出[EAST, SOUTH],可见其有更好的灵活性,同时EnumSet具有集合的优点,内部采用位运算具有较佳的效率

EnumSet<Direction> direction = EnumSet.of(Direction.EAST, Direction.SOUTH);
System.out.println(direction);

用EnumMap代替ordinal()索引映射

对于下面的类Plant及内部枚举Type

class Plant {
    enum Type {TREE, FLOWER, GLASS}

    final String name;
    final Type type;

    public Plant(String name, Type type) {
        this.name = name;
        this.type = type;
    }

    @NonNull
    @Override
    public String toString() {
        return name;
    }
}

存在下面的实例

List<Plant> garden = new ArrayList<>();
garden.add(new Plant("A", Plant.Type.TREE));
garden.add(new Plant("B", Plant.Type.FLOWER));
garden.add(new Plant("C", Plant.Type.GLASS));
garden.add(new Plant("D", Plant.Type.TREE));
garden.add(new Plant("E", Plant.Type.FLOWER));
garden.add(new Plant("F", Plant.Type.GLASS));

若要对其分类,如下创建Set[],以枚举的ordinal()为索引,将实例分类存储到set

Set<Plant>[] plantsOrder = new Set[Plant.Type.values().length];
for (int i = 0; i < plantsOrder.length; i++) {
    plantsOrder[i] = new HashSet<>();
}
for (Plant p : garden) {
    plantsOrder[p.type.ordinal()].add(p);
}
for (int i = 0; i < plantsOrder.length; i++) {
    System.out.printf("%s:%s\n", Plant.Type.values()[i], plantsOrder[i]);
}

打印如下

TREE:[A, D]
FLOWER:[E, B]
GLASS:[F, C]

如上虽然可行,但有许多问题

  • 数组不能与泛型共用,存在Unchecked异常
  • 数组不知道索引代表的意义(如上0存储的为TREE枚举),需要人为去处理

而使用EnumMap可避免上述问题,以Enum为键进行存储,而不是以它的ordinal(),如果是 多维的,可以使用 EnumMap<…,EnumMap<…>>

Map<Plant.Type, Set<Plant>> plantsOrder = new EnumMap<>(Plant.Type.class);
for (Plant.Type type : Plant.Type.values()) {
    plantsOrder.put(type, new HashSet<>());
}
for (Plant p : garden) {
    plantsOrder.get(p.type).add(p);
}
System.out.println(plantsOrder);

打印如下

{TREE=[A, D], FLOWER=[E, B], GLASS=[F, C]}

用接口模拟可扩展的枚举

如对于上面介绍的Operation枚举,可定义接口将其分离

interface Operation {
    double apply(double x, double y);
}

enum BasicOperation implements Operation {
    PLUS("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            return x / y;
        }
    };

    private final String symbol;

    BasicOperation(String symbol) {
        this.symbol = symbol;
    }
}

当需要扩展枚举时,可再定义一个枚举实现接口

enum ExtendedOperation implements Operation {
    EXP("^") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    REMAINDER("%") {
        public double apply(double x, double y) {
            return x / y;
        }
    };

    private final String symbol;

    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值