枚举类型(3):使用EnumSet替代标志、使用EnumMap

一、使用EnumSet替换标志

    Set是一种集合,只能向其中添加不重复的对象。当然,enum也要求其成员都是唯一的,所以enum看起来也具有集合的行为。不过,由于不能从enum中删除或添加元素,所以它只能算是不太有用的集合。java SE5引入EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些bit,而不是这些bit想要表达的概念,因此很容易写出令人难以理解的代码。

    EnumSet的设计充分考虑到了速度因素,因为它必须与非常高效的bit标志相竞争(其操作与HashSet相比,非常地快)。就其内部而言,它(可能)就是将一个long值作为比较向量,所以EnumSet非常快速高效。使用EnumSet的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。

    EnumSet中的元素必须来自一个enum。下面的enum表示在一座大楼中,警报传感器的安放位置:

package enumerated;

public enum AlarmPoints {
	STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY, KITCHEN
}

    然后,我们用EnumSet来跟踪报警器的状态:

package enumerated;

import static enumerated.AlarmPoints.*;

import java.util.EnumSet;

public class EnumSets {
	public static void main(String[] args) {
		EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);// Empty set
		points.add(BATHROOM);
		System.out.println(points);
		points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
		System.out.println(points);
		points = EnumSet.allOf(AlarmPoints.class);
		points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
		System.out.println(points);
		points.removeAll(EnumSet.range(OFFICE1, OFFICE4));
		System.out.println(points);
		points = EnumSet.complementOf(points);
		System.out.println(points);
	}
}

    使用static import可以简化enum常量的使用。EnumSet的方法的名字都相当直观,你可以查阅JDK文档找到其完整详细的描述。如果仔细研究了EnumSet的文档,你还会发现一个有趣的地方:of()方法被重载了很多次,不但为可变数量参数进行了重载,而且为接收2至5个显式的参数的情况都进行了重载。这也从侧面表现了EnumSet对性能的关注。因为,其实只使用可变参数已经可以解决整个问题了,但是对比显式的参数,会有一点性能损失。采用现在这种设计,但你只使用2到5个参数调用of()方法时,你可以调用对应的重载过的方法(速度稍微快一点),而当你使用一个参数或多过5个参数时,你调用的将是使用可变参数的of()方法。注意,如果你只使用一个参数,编译器并不会构造可变参数的数组,所以与调用只有一个参数的方法相比,也就不会有额外的性能损耗。

    EnumSet的基础是long,一个long值有64位,而一个enum实例只需一位bit表示其是否存在。也就是说,在不超过一个long的表达能力的情况下,你的EnumSet可以应用于最多不超过64个元素的enum。如果enum超过了64个元素会发生什么?

package enumerated;

import java.util.EnumSet;

public class BigEnumSet {
	enum Big {
		A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23,
		A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45,
		A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56, A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67,
		A68, A69, A70, A71, A72, A73, A74, A75
	}

	public static void main(String[] args) {
		EnumSet<Big> bigEnumSet = EnumSet.allOf(Big.class);
		System.out.println(bigEnumSet);
	}
}

    显然,EnumSet可以应用于多过64个元素的enum,所以我猜测,Enum会在必要的时候增加一个long。

二、使用EnumMap

    EnumMap是一种特殊的Map,它要求其中的键(key)必须来自一个enum。由于enum本身的限制,所以EnumMap在内部可由数组实现。因此EnumMap的速度很快,我们可以放心地使用enum实例在EnumMap中进行查找操作。不过,我们只能将enum的实例作为键来调用put()方法,其他操作与使用一般Map差不多。

    下面的例子演示了命令设计模式的用法。一般来说,命令模式首先需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类。接下来,程序员就可以构造命令对象,并在需要的时候使用它们了:

package enumerated;

import java.util.EnumMap;
import java.util.Map;

import static enumerated.AlarmPoints.*;

interface Command {
	void action();
}

public class EnumMaps {
	public static void main(String[] args) {
		EnumMap<AlarmPoints, Command> em = new EnumMap<>(AlarmPoints.class);
		em.put(KITCHEN, () -> {
			System.out.println("Kitchen fire!");
		});
		em.put(BATHROOM, () -> {
			System.out.println("Bathroom alert!");
		});
		for (Map.Entry<AlarmPoints, Command> e : em.entrySet()) {
			System.out.print(e.getKey() + ": ");
			e.getValue().action();
		}
		// 如果某个键没有值
		try {
			em.get(UTILITY).action();
		} catch (Exception e1) {
			e1.printStackTrace();
		}
	}
}

    与EnumSet一样,enum实例定义时的次序决定了其在EnumMap中的顺序。

    main()方法的最后部分说明,enum的每个实例作为一个键,总是存在的。但是,如果你没有为这个键调用put()方法来存入相应的值的话,其对应的值就是null。

    与常量相关的方法(constant-specific methods将在下一节中介绍)相比,EnumMap有一个优点,那EnumMap允许程序员改变值对象,而常量相关的方法在编译期就被固定了。

    稍后你会看到,在你有多种类型的enum,而且它们之间存在互操作的情况下,我们可以用EnumMap实现多路分发(multiple dispatching)。

如果本文对您有很大的帮助,还请点赞关注一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

游王子og

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值