什么是枚举
在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
,表示只接受这个枚举集合中的值。