Java基础-枚举

    技术2022-07-11  80

    什么是枚举

    在Java中,我们可以这样定义一个枚举类:

    public enum Size { SMALL, NORMAL, LARGE }

    这段代码的意思是:创建一个用来表示尺寸的枚举类,里面有大中小三种不同的尺寸。

    在代码层面的含义则是:创建了一个枚举类,里面有三个不同的对象。

    问题1:为什么说是三个不同的对象呢?

    既然是对象,那么自然就有构造方法了,因此我们也可以这样来定义一个枚举类:

    public enum Size { SMALL("1"), NORMAL("2"), LARGE("3"); private String capacity; Size(String s) { capacity = s; } public String getCapacity() { return capacity; } }

    然后让我们来写一段测试代码:

    public static void main(String[] args) { EnumDemo demo = new EnumDemo(); System.out.println(Size.NORMAL.name()); System.out.println(Size.NORMAL.toString()); System.out.println(Size.valueOf("NORMAL")); System.out.println(Size.NORMAL.ordinal()); System.out.println(Size.NORMAL.capacity); }

    输出的结果是:

    NORMAL NORMAL NORMAL 1 2

    说明确实是可以通过实现不同的构造方法来传入不同的参数,这也侧面印证了枚举类里面的是一个个不同的对象这一说法。

    枚举用途

    上面列举了枚举类的而一些常用用法,包括自定义构造函数来实现多个不同参数。那么我们为什么要使用枚举类呢?

    单例模式

    使用枚举来实现单例是一个非常好的方法:

    线程安全,不用使用双重检查锁防止多次实例化,哪怕是反序列化也不行本身实现了Serializable接口,提供序列化机制

    这里提供一个简单的枚举实现单例的例子:

    enum Single { SINGLE; private Single() {} } // 创建 Single single = Single.SINGLE;

    策略模式

    通常来说,策略模式需要先定义一个接口,然后通过不同的实现类来重写不同的策略方法。

    然后再通过一个Context作为媒介,观察策略是否发生变化。这样外部只要持有Context对象就可以得到当前真正的实现类,从而执行相关的操作。

    使用枚举可以这样做:

    public class EnumDemo { private Size size; public enum Size { SMALL("1") { @Override protected void printSize() { System.out.println("this is the small size"); } }, NORMAL("2") { @Override protected void printSize() { System.out.println("this is the normal size"); } }, LARGE("3") { @Override protected void printSize() { System.out.println("this is the large size"); } }; private String capacity; Size(String s) { capacity = s; } public String getCapacity() { return capacity; } protected abstract void printSize(); }

    不使用接口,而是使用抽象方法,让每个实例实现这个方法。

    使用方式:

    public static void main(String[] args) { EnumDemo demo = new EnumDemo(); demo.size = Size.NORMAL; demo.size.printSize(); demo.size = Size.SMALL; demo.size.printSize(); }

    创建一个对象持有Size类,然后当Size发生变化的时候,调用pringSize() 方法也就会执行不同的逻辑。实现了和策略模式一样的效果。

    用枚举来代替if…else

    我们经常会看到这样的代码:

    if (type == 1) { // ... } else if (type == 2) { // ... } else { // ... }

    这种代码可读性就没有这么好,存在优化的空间:

    使用常量来代替 1,2,3…使用switch来代替if…else final int TYPE_ONE = 1; final int TYPE_TWO = 2; final int TYPE_TRHEE = 3; switch(type) { case TYPE_ONE: break; case TYPE_TWO: break; case TYPE_THREE: break; }

    我们还可以用枚举来代替这些常量

    switch(Size) { case Size.SMALL: break; case Size.NORMAL: break; case Size.LARGE: break; }

    使用枚举的的好处是什么呢?

    首先可读性会比直接使用变量要好,其次因为枚举类可以自定义构造方法和其他方法,因此可以做的事情更多。

    枚举类的一些细节

    1.Enum是所有枚举类的父类

    构造方法:

    public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { } /** * Sole constructor. Programmers cannot invoke this constructor. * It is for use by code emitted by the compiler in response to * enum type declarations. * * @param name - The name of this enum constant, which is the identifier * used to declare it. * @param ordinal - The ordinal of this enumeration constant (its position * in the enum declaration, where the initial constant is assigned * an ordinal of zero). */ protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }

    这里可以回答问题1,Enum有一个构造函数,里面包括两个参数:

    name

    ordinal

    name就是SMALL / NORMAL / LARGE 这几个对象的字符串,oridnal则是一个从0开始自增的整型。

    2.name() / ordinal() / toString()

    Enum提供了name和ordinal的get方法, 并重写了toString()方法。

    从注释中我们看到官方推荐我们使用toString() 方法而不是 name()

    /** * Returns the name of this enum constant, as contained in the * declaration. This method may be overridden, though it typically * isn't necessary or desirable. An enum type should override this * method when a more "programmer-friendly" string form exists. * * @return the name of this enum constant */ public String toString() { return name; }

    虽然默认实现中toString() 也是返回name,但是我们可以重写 toString() 用于返回可读性更好的字符串。

    3.==和equals()

    我们可以直接使用 == 来比较两个枚举对象,不用像String那样使用equals()方法

    答案也在源码里面:

    /** * Returns true if the specified object is equal to this * enum constant. * * @param other the object to be compared for equality with this object. * @return true if the specified object is equal to this * enum constant. */ public final boolean equals(Object other) { return this==other;

    equals()方法其实也是直接调用 == ,我们都知道这两者的区别就是 == 比较的是两个对象的内存地址。在 equals() 方法中传入的也是 Object, 因此要比较两个Object是否相等,用 == 更合适。

    4.EnumSet

    顾名思义,EnumSet就是一个专门为枚举类型设计的Set类型

    我们可以通过这几个方法来创建EnumSet

    private void testEnumSet() { // EnumSet.none EnumSet<Size> noneSet = EnumSet.noneOf(Size.class); System.out.println("noneOf():"); for (Size item : noneSet) { System.out.println(item.toString()); } // EnumSet.allOf EnumSet<Size> allSet = EnumSet.allOf(Size.class); System.out.println("allOf(): "); for (Size item : allSet) { System.out.println(item.toString()); } // EnumSet.range EnumSet<Size> set = EnumSet.range(Size.NORMAL, Size.LARGE); System.out.println("range(): "); for (Size item : set) { System.out.println(item.toString() + " " + item.ordinal()); } }

    看一下输入结果:

    noneOf(): allOf(): SMALL NORMAL LARGE range(): NORMAL 1 LARGE 2

    noneOf(): 创建一个空的EnumSet

    allOf(): 创建一个包含Size里面所有对象的EnumSet

    range(): 这里就需要用到我们上面在Enum里面说到的ordinal了

    /** * Creates an enum set initially containing all of the elements in the * range defined by the two specified endpoints. The returned set will * contain the endpoints themselves, which may be identical but must not * be out of order. * * @param <E> The class of the parameter elements and of the set * @param from the first element in the range * @param to the last element in the range * @throws NullPointerException if {@code from} or {@code to} are null * @throws IllegalArgumentException if {@code from.compareTo(to) > 0} * @return an enum set initially containing all of the elements in the * range defined by the two specified endpoints */ public static <E extends Enum<E>> EnumSet<E> range(E from, E to) { if (from.compareTo(to) > 0) throw new IllegalArgumentException(from + " > " + to); EnumSet<E> result = noneOf(from.getDeclaringClass()); result.addRange(from, to); return result; }

    EnumSet本身是抽象类,它有两个子类,分别是 RegularEnumSet、JumboEnumSet。当枚举数量大于64的时候用JumboEnumSet,否则用RegularEnumSet。

    5.EnumMap

    顾名思义,EnumMap就是一个专门存放Enum类型数据的Map类型

    由于EnumMap继承了AbstractMap,因此也同样具有Map相关的操作。

    我们可以这样来使用EnumMap:

    private void testEnumMap() { EnumMap<Size, List<EnumDemo>> map = new EnumMap<>(Size.class); List<EnumDemo> list = new ArrayList<>(); EnumDemo demo1 = new EnumDemo(); demo1.setSize(Size.SMALL); list.add(demo1); EnumDemo demo2 = new EnumDemo(); demo2.setSize(Size.NORMAL); list.add(demo2); EnumDemo demo3 = new EnumDemo(); demo3.setSize(Size.LARGE); list.add(demo3); EnumDemo demo4 = new EnumDemo(); demo4.setSize(Size.LARGE); list.add(demo4); for (EnumDemo item : list) { if (map.containsKey(item.size)) { map.get(item.size).add(item); } else { List<EnumDemo> newList = new ArrayList<>(); newList.add(item); map.put(item.size, newList); } } System.out.println("large size:" + map.get(Size.LARGE).size()); }

    通过Enum作为Key,我们可以快速查找的功能。

    举个例子,如果我们想知道仓库里还有多少件大码的衣服,那么可以直接通过 map.get(Size.LARGE).size() 得到对应的list的数目。

    枚举在Android的替代方案

    由于枚举实际上是创建了一个个对象,因此内存占用会比基本的数据类型更多。在Android中我们根据具体的情况来判断是否需要使用枚举。如果只是一个占位符或者标志位,我们也可以用官方提供的注解来实现。

    首先添加依赖:

    implementation 'com.android.support:support-annotations:28.0.0'

    然后就可以开始使用了:

    public class MainActivity extends Activity { public static final int SUNDAY = 0; public static final int MONDAY = 1; public static final int TUESDAY = 2; public static final int WEDNESDAY = 3; public static final int THURSDAY = 4; public static final int FRIDAY = 5; public static final int SATURDAY = 6; @IntDef({SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY}) @Retention(RetentionPolicy.SOURCE) public @interface WeekDays { } @WeekDays int currentDay = SUNDAY; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setCurrentDay(WEDNESDAY); @WeekDays int today = getCurrentDay(); switch (today) { case SUNDAY: break; case MONDAY: break; {...省略部分} default: break; } } public void setCurrentDay(@WeekDays int currentDay) { this.currentDay = currentDay; } @WeekDays public int getCurrentDay() { return currentDay; } }

    通过@IntDef注解来修饰枚举类,表示里面的对象都是Int型的

    通过@Retention来声明注解,也就是元注解,这里的意思就是声明了一个WeekDays的注解

    通过定义int类型的常量,常量名就是枚举中的对象名

    最后在需要判断的地方加上@WeekDays,表示只接受这个枚举集合中的值。

    Processed: 0.011, SQL: 9