Java基础-枚举

什么是枚举

在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;

策略模式

策略模式的 UML 图

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

然后再通过一个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提供了nameordinal的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本身是抽象类,它有两个子类,分别是 RegularEnumSetJumboEnumSet。当枚举数量大于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,表示只接受这个枚举集合中的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值