先来个DEMO:
public enum Fruit {
APPLE, PEAR, PEACH;
}
class EnumTest{
public static void main(String[] args) {
/*values()*/
Arrays.stream(Fruit.values()).forEach(e -> {
System.out.println(e);
/* compareTo*/
System.out.println(e.compareTo(Fruit.PEAR));
System.out.println(e == Fruit.PEAR);
/* getDeclaringClass VS getClass()*/
System.out.println(e.getDeclaringClass());
/*name()场景: This method is designed primarily for use in specialized situations
where correctness depends on getting the exact name, which will not vary from release to release.
API DOC翻译即为:name()只用在当对枚举的名字精确的要求下的场合,枚举的name()是不变的,但是toString()可能被
override
*/
System.out.println(e.name());
System.out.println("-------");
});
}
}
再看:
public enum Fruit extends Food{
APPLE, PEAR, PEACH;
}
简单API的使用不需赘言,只说几个关键的:
public abstract class Enum>
implements Comparable, Serializable {
// .....
}
我们使用枚举时多半希望每个枚举实例能够返回对自身的描述,而不仅仅默认返回toString()实现,toString()只能返回枚举的名字而已。此时,我们可以添加对枚举的描述信息,并提供有参构造方法以及get()方法
public enum Color {
RED("i love red"),
BLACK("i hate black"),
;
private final String desc;
Color(String desc) { // 即使是包内可见,编译器还有会把它变成私有构造器
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
在switch case 中使用enum,此时enum就相当于一个小型的状态机。
public enum Sign {
GREEN,RED,YELLOW;
}
class TrafficLight{
Sign sign = Sign.RED;
public void f() {
switch (sign) {
case RED: // 注意:::这里的状态是流转的! 确实像个小型的状态机
sign = Sign.GREEN;
break;
case GREEN:
sign = Sign.YELLOW;
break;
case YELLOW:
sign = Sign.RED;
break;
}
}
@Override
public String toString() {
return " light of Color is " + sign;
}
public static void main(String[] args) {
TrafficLight trafficLight = new TrafficLight();
IntStream.range(0, 10).forEach(e -> {
trafficLight.f();
System.out.println(trafficLight.sign);
});
}
}
我们看到,其实java.lang.Enum 类中并没有定义values()这个方法,那它来自何处呢?
values()是编译器自动添加的静态方法,同样valueOf() 也是如此
一段简单的enum定义是这样:
public enum Explore {
HERE, THERE,
;
}
实际编译完了以后是这样:
final class Explore extends java.lang.Enum{
public static final Explore HERE;
public static final Explore THERE;
public static final Explore[] values();
public static Explore valueOf(java.lang.String);
static {};
}
另外,我们看到:
class Reflection{
public static void main(String[] args) throws IOException {
Enum here = Explore.HERE; // 将 Explore 枚举的一个实例 强转为 Enum
// here.values(); values()方法将不可用
// 不过 ,可以通过 下面这种方式 getEnumConstants 获取 枚举实例,这是Class类的API!!
// 所以,即使对象不是 枚举也有这个API,但是返回的结果可能为null而已!
Arrays.stream(here.getClass().getEnumConstants())
.forEach(System.out::println);
}
}
自定义枚举时不能继承除java.lang.Enum 以外的类,但是可以实现不同的接口。这样,所有的枚举实例都能作为接口的实现类对象传参,有时会显得比较滑稽。囧。。。
public enum Drink implements Generator {
PEPSI, COKECOLA,
;
private Random random = new Random();
@Override
public Drink next() {
return Drink.values()[random.nextInt(Drink.values().length)];
}
}
class DrinkTest{
public static void main(String[] args) {
Drink drink = Drink.PEPSI;
// 这里比较搞笑,传入的 明明是 pepsi,但是可以产生多种 实例
IntStream.range(0, 10).forEach(e -> printNext(drink));
}
public static void printNext(Generator generator) {
System.out.println(generator.next());
}
}
这里来写一个小小的工具类来陶冶一下情操:
public final class Enums {
private Enums(){}
private static final Random rand = new Random();
/* 泛型 > 表示出现在这个泛型方法中的 T 可能为 enum 实例,连其子类都不可以(当然也没
* 子类。
*
* 这个方法作用: 传入一个枚举类Class对象,随机获取一个枚举类的实例
* */
private static > T random(Class extends T> clazz) {
return random(clazz.getEnumConstants());
}
private static T random(T[] enumConstants) {
return enumConstants[rand.nextInt(enumConstants.length)];
}
public static void main(String[] args) {
IntStream.range(0, 5).forEach(e -> System.out.println(random(Color.class)));
}
}
为啥使用接口来组织枚举?
答曰:因 不能从enum继承子类(注意是enum,不是java.lang.Enum),但是有时我们希望能扩展原enum中的元素,或者将一个enum中元素进行分组。
形式1:
public interface Food {
enum Vegetable implements Food{
LETTUCE, RUSHROOM
}
enum Meat implements Food{
PORK, STEAK
}
enum MainFood implements Food{
RICE, BREAD
}
}
class TypeOfFood{
public static void main(String[] args) {
Food food = Food.Vegetable.LETTUCE;
food = Food.Meat.PORK;
}
}
从OOP的角度看:所有的内部枚举实例都是Food,因此可以转型为Food。
当要分组的类型太多时,接口就不如枚举好用了,我们构造了类似枚举的枚举
形式2:
public enum GoodFood {
VEGETABLE(Food.Vegetable.class),
MAINFOOD(Food.MainFood.class),
;
// 尤其要注意的是:每个GoodFood实例都有着自己的Food[] values 域!!!
// 虽然 GoodFood的枚举实例写在 GoodFood内部,但最好把它当作 一般实例来看待
private Food[] values;
GoodFood(Class extends Food> clazz) {
// 只要实例化GoodFood这个枚举,就会调用这个有参构造,打印出“---”;
// 而且是 有几个 GoodFood枚举实例就调用几次
System.out.println("---");
values = clazz.getEnumConstants();
}
public Food randomSelection() {
return Enums.random(values);
}
}
class GoodFoodTest {
public static void main(String[] args) {
IntStream.range(0, 5).forEach(e -> {
Arrays.stream(GoodFood.values()).forEach(u -> {
System.out.println(u.randomSelection());
});
System.out.println("++++++++");
});
}
}
形式3:
public enum GoodFood2 {
VEGETABLE(Food.Vegetable.class),
MAINFOOD(Food.MainFood.class),
;
private Food[] values;
GoodFood2(Class extends Food> clazz) {
values = clazz.getEnumConstants();
}
public Food randomSelection() {
return Enums.random(values);
}
public interface Food {
enum Vegetable implements Food {
LETTUCE, RUSHROOM
}
enum Meat implements Food {
PORK, STEAK
}
enum MainFood implements Food {
RICE, BREAD
}
}
}
class GoodFood2Test{
public static void main(String[] args) {
Arrays.stream(GoodFood2.values()).forEach(e -> {
System.out.println(e.randomSelection());
});
}
}
第三种形式和第二种形式只是代码组织上不同,但是显得结构更清晰一点。
Set集合中元素是不重复的,从这一点上看,enum有异曲同工之妙。JDK引入EnumSet,是为了替代传统的基于int 来表征“开关量”(有或没有)的做法,而是使用 bit vectors(位向量)来表征。所以EnumSet的效率是很高的(具体到什么方法???)
public class EnumSets {
enum AlarmPoints {
STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
OFFICE4, BATHROOM, UTILITY, KITCHEN
}
public static void main(String[] args) {
EnumSet alarmPoints = EnumSet.noneOf(AlarmPoints.class);
alarmPoints.add(AlarmPoints.BATHROOM);
alarmPoints.addAll(EnumSet.of(AlarmPoints.LOBBY, AlarmPoints.UTILITY));
alarmPoints.addAll(EnumSet.range(AlarmPoints.OFFICE1, AlarmPoints.OFFICE4));
EnumSet leftPoints = EnumSet.complementOf(alarmPoints); // 补集
System.out.println(alarmPoints);
System.out.println(leftPoints);
}
}
注意一下:
Set s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
2. 多个enum 实例加入到EnumSet中, 总是按定义时的先后顺序来读写的。这一点要十分小心
特点:
场景:
利用enum的分发特性,结合命令模式,可以有十分简洁的 结构。
/**
* 一个典型的 命令模式 ,command pattern。
* 命令模式:首先需要一个只有一个单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类
*/
public class EnumMaps {
enum AlarmPoints {
STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
OFFICE4, BATHROOM, UTILITY, KITCHEN
}
public static void main(String[] args) {
EnumMap em = new EnumMap<>(AlarmPoints.class);
em.put(AlarmPoints.KITCHEN, () -> System.out.println("fire in kitchen!"));
em.put(AlarmPoints.BATHROOM, () -> System.out.println("fire in bathroom !"));
em.forEach((u, v) -> {
System.out.println(u);
v.action();
});
}
}
@FunctionalInterface
interface Command{
void action();
}
enum有个很有意思的特性:
enum内部每个实例都可以有自己的方法(行为)。同时呢,每个实例其实又是常量类型。
再联系到EnumMap:::map的值可变,但是键(enum实例)中的方法却在编译期就固定了
看个例子:
public enum ConstantSpecificMethod {
DATE_TIME {
String getInfo() {
return DateFormat.getDateInstance().format(new Date());
}
},
CLASSPATH {
String getInfo() {
return System.getenv("CLASSPATH");
}
},
;
abstract String getInfo();
}
class ConstantSpecificMethodTest {
public static void main(String[] args) {
// 这里的 e(枚举实例)像是被当做 ConstantSpecificMethod 这个父类来使用了(多态)
Arrays.stream(ConstantSpecificMethod.values()).forEach(e -> System.out.println(e.getInfo()));
}
}
从OOP思想看,不同的行为与不同的类关联(意思是:同一个类的实例应该具有相同的行为,假如行为不同,就应该定义为不同的类),但是枚举实例(常量)却有着自己独有的方法,好像每个枚举实例就是一个独特的类一样。不过呢,也只是像而已,enum实例不是一个真正的类型,想想编译后的enum就会发现每个enum实例都是一个 枚举类的 static final 实例!
下面来一发小demo来说明枚举实例内定义方法的场景:
自助洗车时,机器上会有选择菜单让客户自行选择洗车的具体内容,每项内容对应一个动作。这个时候我们可以利用枚举来建模:每个枚举实例相当于一个服务具体内容,内容的动作抽象成一个抽象类,由各个内容自行定义。
public class CarWash {
public enum Cycle{
UNDERDBODY{
@Override
void action() {
Cycle.print(" spraying the underbody!");
}
},
WHEELWASH {
void action() { print("Washing the wheels"); }
},
PREWASH {
void action() { print("Loosening the dirt"); }
},
BASIC {
void action() { print("The basic wash"); }
},
HOTWAX {
void action() { print("Applying hot wax"); }
},
RINSE {
void action() { print("Rinsing"); }
},
BLOWDRY {
void action() { print("Blowing dry"); }
};
/*必须是静态方法,应该enum实例编译后实际是 static final实例*/
private static void print(String s) {
System.out.println(s);
}
abstract void action();
}
EnumSet cycles = EnumSet.of(Cycle.UNDERDBODY, Cycle.RINSE);
public void add(Cycle cycle) {
cycles.add(cycle);
}
public void wash() {
cycles.forEach(Cycle::action);
}
}
class CarWashTest{
public static void main(String[] args) {
/*这种表达说明了 Cycle 这个枚举就像是 内部类*/
CarWash.Cycle cycle = CarWash.Cycle.BASIC;
CarWash carWash = new CarWash();
carWash.add(CarWash.Cycle.BASIC);
carWash.add(CarWash.Cycle.PREWASH);
carWash.wash();
}
}
此外呢,constant-specific methods (指上例中的各个枚举常量独有的方法)
可以被override掉
public enum OverrideConstantSpecific {
NUT, BOLT,
WASHER {
void f() {
/*这里会 override f()*/
System.out.println(("Overridden method"));
}
};
void f() {
System.out.println(("default behavior"));
}
}
class OverrideConstantSpecificTest{
public static void main(String[] args) {
NUT.f(); // default behavior
WASHER.f(); // Overridden method
}
}
职责链:
chain of responsibility:直白点说,一个问题,有多个方法处理它,那么把这多种方法封装起来,像一个链一样。使用时,用链上的方法(也可以叫做 策略)遍历尝试解决,若能解决,跳出链,若不能,那遍历完所有“策略”,接下来该干啥干啥了。
假设场景:
邮局处理邮件时会采用各种手段逐个尝试能不能投递成功,每种处理手段可以封装成一个职责链;
邮件本身有很多特征,使用GeneralDelivery 、Scannability ,etc来描述
为了简化模型,每个特征量都只描述为“YES”、“NO”等
public class Mail {
enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5}
enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4}
enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}
enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6}
enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5}
GeneralDelivery generalDelivery;
Scannability scannability;
Readability readability;
Address address;
ReturnAddress returnAddress;
static long counter = 0;
long id = counter++;
public String toString() { return "Mail " + id; }
public String details() {
return toString() +
", General Delivery: " + generalDelivery +
", Address Scanability: " + scannability +
", Address Readability: " + readability +
", Address Address: " + address +
", Return address: " + returnAddress;
}
// Generate test Mail:
public static Mail randomMail() {
Mail m = new Mail();
m.generalDelivery= Enums.random(GeneralDelivery.class);
m.scannability = Enums.random(Scannability.class);
m.readability = Enums.random(Readability.class);
m.address = Enums.random(Address.class);
m.returnAddress = Enums.random(ReturnAddress.class);
return m;
}
public static Iterable generator(final int count) {
return new Iterable() {
int n = count;
@Override
public Iterator iterator() {
return new Iterator() {
@Override
public boolean hasNext() {
return n-- > 0;
}
@Override
public Mail next() {
return randomMail();
}
};
}
};
}
}
public class PostOffice {
// 看这里,,,,,这里才是真正的 职责链 封装!
enum MailHandler {
GENERAL_DELIVERY {
@Override
boolean handle(Mail mail) {
switch (mail.generalDelivery) {
case YES:
System.out.println(" using general delivery for " + mail);
return true;
default:
return false;
}
}
},
MACHINE_SCAN {
@Override
boolean handle(Mail mail) {
switch (mail.scannability) {
case UNSCANNABLE:
return false;
default:
System.out.println(" scanning " + mail);
return true;
}
}
},
VISUAL_INSPECTION {
@Override
boolean handle(Mail mail) {
switch (mail.readability) {
case ILLEGIBLE:
return false;
default:
System.out.println(" visual inspecting " + mail);
return true;
}
}
};
abstract boolean handle(Mail mail);
}
public static void main(String[] args) {
Mail.generator(10).forEach(e -> {
Arrays.stream(MailHandler.values()).forEach(u -> {
u.handle(e);
});
});
}
}