枚举学习笔记

亦凉 2022-01-16 04:39 393阅读 0赞

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

枚举Enum

关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。

一个简单例子

调用enum的values()方法,可以遍历enum实例。values()方法返回enum实例的数组,而且该数组中的元素严格保持其在enum中声明时的顺序

创建enum,编译器会为你生成一个相关的类,这个类继承自java.lang.Enum。下面的例子演示了Enum提供的一些功能。

  1. public class EnumClass {
  2. public static void main(String[] args) {
  3. for (Animal animal: Animal.values()) {
  4. System.out.println(animal + " ordinal: " + animal.ordinal());
  5. System.out.println(animal.name());
  6. System.out.println(animal.getDeclaringClass());
  7. }
  8. Animal a = Enum.valueOf(Animal.class, "PIGGER");
  9. System.out.println(a.name());
  10. }
  11. }
  12. enum Animal {
  13. PIGGER, MONKEY, DOG, CAT, ELEPHANT, KOLA
  14. }
  15. ------------------output------------------
  16. PIGGER ordinal: 0
  17. PIGGER
  18. class com.stupidzhe.jdklearning.enu.Animal
  19. MONKEY ordinal: 1
  20. MONKEY
  21. class com.stupidzhe.jdklearning.enu.Animal
  22. DOG ordinal: 2
  23. DOG
  24. class com.stupidzhe.jdklearning.enu.Animal
  25. CAT ordinal: 3
  26. CAT
  27. class com.stupidzhe.jdklearning.enu.Animal
  28. ELEPHANT ordinal: 4
  29. ELEPHANT
  30. class com.stupidzhe.jdklearning.enu.Animal
  31. KOLA ordinal: 5
  32. KOLA
  33. class com.stupidzhe.jdklearning.enu.Animal
  34. PIGGER
  35. ------------------------------------------

ordinal()方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。可以使用==来比较enum实例,编译器会自动为你提供equals()和hashCode()方法。Enum类实现了Comparable接口,所以它具有compareTo()方法。同时,它还实现了Serializable接口。

如果在enum实例上调用getDeclaringClass()方法,我们就能得到该注解所属的enum类。Enum.valueOf()是在Enum中定义的static方法,它根据给定的名字返回相应的enum实例,如果不存在将抛出异常。


static import

  1. import static com.stupidzhe.jdklearning.enu.Animal.*;
  2. public class EnumClass {
  3. public static void main(String[] args) {
  4. System.out.println(PIGGER);
  5. }
  6. }
  7. enum Animal {
  8. PIGGER, MONKEY, DOG, CAT, ELEPHANT, KOLA
  9. }

使用static import能够将enum实例的标识符带入当前的命名空间,所以无需再使用enum类型来修饰enum实例。这是一个好的想法吗?或者还是显式地修饰enum实例更好?这要看代码的复杂程度了。编译器可以保证你使用的是正确的类型,所以唯一需要单行的是,使用静态导入会不会导致你的代码令人难以理解。

总的来说,编码原则是:具体情况具体分析


向enum中添加新方法

除了不能继承自一个enum之外,我们基本上可以将enum看作是一个常规的类。 也就是说,我们可以在enum中添加方法,enum甚至可以有main()方法。

注意:如果你打算定义自己的方法, 那么必须在enum实例序列的最后添加一个分号。 同时,Java要求你必须先定义enum实例。如果在定义enum实例之前定义了任何方法或属性,那么在编译时就会得到错误信息。

  1. import static com.stupidzhe.jdklearning.enu.Animal.*;
  2. public class EnumClass {
  3. public static void main(String[] args) {
  4. System.out.println(PIGGER);
  5. Animal animal = Animal.PIGGER;
  6. animal.print();
  7. }
  8. }
  9. enum Animal {
  10. // 实例序列的最后添加一个分号
  11. PIGGER, MONKEY, DOG, CAT, ELEPHANT, KOLA;
  12. public static void main(String[] args) {
  13. }
  14. public void print() {
  15. System.out.println(this.toString());
  16. }
  17. }


## switch语句中的enum 在switch中使用enum,是enum提供的一项非常便利的功能。一般来说,在switch中只能使用整数值,而枚举实例天生就具有整数值的次序,并且可以通过ordinal()方法取得其次序(显然编译器帮我们做了类似的工作),因此我们可以在switch语句总会给你使用enum。

虽然一般情况下我们必须使用enum类型来就是enum实例,但是在case语句中却不亦如此,下面的例子使用enum构造了一个小型状态机

  1. public class EnumClass {
  2. public static void main(String[] args) {
  3. System.out.println(PIGGER);
  4. Animal animal = Animal.PIGGER;
  5. switch (animal) {
  6. case PIGGER:
  7. animal.print();
  8. animal = MONKEY;
  9. case MONKEY:
  10. animal.print();
  11. animal = DOG;
  12. case DOG:
  13. animal.print();
  14. animal = CAT;
  15. case CAT:
  16. animal.print();
  17. animal = ELEPHANT;
  18. case ELEPHANT:
  19. animal.print();
  20. animal = KOLA;
  21. case KOLA:
  22. animal.print();
  23. }
  24. }
  25. }
  26. enum Animal {
  27. PIGGER, MONKEY, DOG, CAT, ELEPHANT, KOLA;
  28. public void print() {
  29. System.out.println(this.toString());
  30. }
  31. }
  32. -------output------
  33. PIGGER
  34. PIGGER
  35. MONKEY
  36. DOG
  37. CAT
  38. ELEPHANT
  39. KOLA
  40. -------------------

编译器并没有抱怨switch中没有default语句,但这并不是因为每一个Animal都有相对应的case语句。


## values()的神秘之处 前面已经提到过,编译器为你创建的enum类继承自Enum类。然而,如果你研究一下Enum类你会发现,它并没有values()方法。

通过反射来观察方法:

  1. /**
  2. * @author Mr.W
  3. */
  4. public class EnumMethodTest {
  5. public static void main(String[] args) {
  6. Class<Animal> animalClass = Animal.class;
  7. Method[] methods = animalClass.getMethods();
  8. System.out.println("---------methods-------");
  9. for (Method method : methods) {
  10. System.out.println(method.getName());
  11. }
  12. System.out.println("------super class------");
  13. Class superClass = animalClass.getSuperclass();
  14. System.out.println(superClass.getName());
  15. System.out.println("------implements-------");
  16. Type[] ins = superClass.getGenericInterfaces();
  17. for (Type impl: ins) {
  18. System.out.println(impl.getTypeName());
  19. }
  20. }
  21. }
  22. -------output-------
  23. ---------methods-------
  24. values
  25. valueOf
  26. print
  27. name
  28. equals
  29. toString
  30. hashCode
  31. compareTo
  32. compareTo
  33. valueOf
  34. getDeclaringClass
  35. ordinal
  36. wait
  37. wait
  38. wait
  39. getClass
  40. notify
  41. notifyAll
  42. ------super class------
  43. java.lang.Enum
  44. ------implements-------
  45. java.lang.Comparable<E>
  46. java.io.Serializable
  47. -----------------------

答案是values()是由编译器添加的static方法。

注意:由于values()是由编译器插入到enum定义中的static方法,所以,如果你将enum实例向上转型成Enum,那么value()方法就不可访问了。不过在Class中有一个getEnumConstants()方法,所以即使Enum接口中没有value()方法,我们仍然可以通过Class对象取得所有enum实例。


## 实现,而非继承 我们知道,所有的enum类型都继承自Enum类。由于Java不支持多继承,所以你的enum不能再继承其他类。

然而我们在创建一个新的enum时,可以同时实现一个或多个接口,不过你必须有一个enum实例才能调用其上的方法。


EnumSet

Set是一种集合,只能向其中添加不重复的对象。当然,enum也要求其对象是唯一的,所以看来enum也具有集合的行为。 不过由于enum不能进行删除或添加元素,所以它只能算是不太有用的集合。

Java SE5引入EnumSet,是为了通过enum创建一种代替拼,以代替基于int的位标志。这种标志可以用来表示某种“开关”信息,不过,使用这种标志,我们最终操作的只是bit,而不是这些bit想要表达的概念。

EnumSet的设计充分考虑到了速度因素,因为它必须与非常高效的bit标志相竞争。就内部而言,它可能就是将一个long值作为比特向量,所以EnumSet非常快速高效。

EnumSet的优点就是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能问题。

EnumSet中的元素必须来自一个enum。

  1. public class ExtendsTestClass {
  2. public static void main(String[] args) {
  3. EnumSet<Animal> enumSet = EnumSet.noneOf(Animal.class);
  4. enumSet.add(Animal.MONKEY);
  5. enumSet.add(Animal.PIGGER);
  6. }
  7. }

枚举数不超过64的时候,EnumSet的实现类时RegularEnumSet,add()源码如下:

  1. public boolean add(E e) {
  2. typeCheck(e);
  3. long oldElements = elements;
  4. elements |= (1L << ((Enum<?>)e).ordinal());
  5. return elements != oldElements;
  6. }

可以看到,是一个long类型的位操作。

注意:枚举数大于64个,实现类会是JumboEnumSet。


EnumMap

EnumMap是一种很特殊的Map,它要求其中的键(key)必须来自一个enum。由于enum本身的限制,所以EnumMap内部可由数组实现,因此EnumMap的速度十分快。

我们需要通过enum的实例作为键来调用put()方法,其他操作与使用一般的Map差不多。

  1. EnumMap<Animal, String> enumMap = new EnumMap<>(Animal.class);
  2. enumMap.put(Animal.MONKEY, Animal.MONKEY.name());
  3. System.out.println(enumMap.get(Animal.ELEPHANT));
  4. ------output------
  5. null
  6. ------------------

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

enum的每个实例作为键总是存在的。


常量相关的方法

Java的enum有一个非常有趣的特性,即它允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为。要实现常量相关的方法,你需要为enum定义一个或多个adbstract方法,然后为每个enum实例实现该抽象方法。

  1. public enum Animal {
  2. PIGGER {
  3. public String getInfo() {
  4. return "pigger";
  5. }
  6. // 覆盖
  7. @Override
  8. public void wow() {
  9. System.out.println("hehe");
  10. }
  11. },
  12. MONKEY {
  13. public String getInfo() {
  14. return "monkey";
  15. }
  16. },
  17. DOG {
  18. public String getInfo() {
  19. return "dog";
  20. }
  21. },
  22. CAT {
  23. public String getInfo() {
  24. return "cat";
  25. }
  26. },
  27. ELEPHANT{
  28. public String getInfo() {
  29. return "elephant";
  30. }
  31. },
  32. KOLA{
  33. public String getInfo() {
  34. return "kola";
  35. }
  36. };
  37. // 添加抽象方法
  38. public abstract String getInfo();
  39. // 常量相关的方法
  40. public void wow() {
  41. System.out.println("haha");
  42. }
  43. public void print() {
  44. System.out.println(this.toString());
  45. }
  46. // 直接调用即可
  47. //System.out.println(Animal.PIGGER.getInfo());
  48. }

转载于:https://my.oschina.net/StupidZhe/blog/1573605

发表评论

表情:
评论列表 (有 0 条评论,393人围观)

还没有评论,来说两句吧...

相关阅读

    相关 IOS学习

               枚举就是自己定义的一些常量,而这些常量是根据自己的需要定义不同的用途。 有两种形式定义枚举。代码如下:          import <

    相关 学习笔记

    [2019独角兽企业重金招聘Python工程师标准>>> ][2019_Python_] ![hot3.png][] 枚举Enum > 关键字enum可以将一组具名的值