枚举类型(enum)

前言:枚举时Java SE5中的新特性,枚举类型的关键字为enum。在创建enum时编译器会自动添加一些有用的特性,在API中却没有显示,所以需要注意一下。

一、基础

枚举类型的定义格式为:enum <枚举类型名> {<枚举表>}; 

1.1 编译器自动添加

  在创建enum的时候,编译器会自动添加一些有用的特性。例如,会创建toString()方法,以便可以方便地显示某个enum实例的名字;会创建ordinal()方法,用来表示某个enum常量的声明顺序;创建 static values()方法,用来按照enum常量的声明书序,产生由这些常量值构成的数组。

定义一个枚举类型,示例:

public enum Spiciness {
  NOT, MILD, MEDIUM, HOT, FLAMING
}

测试用例:

public class EnumOrder {
  public static void main(String[] args) {
    for(Spiciness s : Spiciness.values())
      System.out.println(s + ", ordinal " + s.ordinal());
  }
}

输出结果:

NOT, ordinal 0
MILD, ordinal 1
MEDIUM, ordinal 2
HOT, ordinal 3
FLAMING, ordinal 4

 详细的原因可以参考:Java Enum 类 的 values()方法 api没有,实例是怎么调用的?

注意:enum实例在声明时的顺序是从0开始的。可以使用==来比较enum实例,编译器会自动为你提供equals()和hashCode()方法。Enum类实现了Comparable借口,所以具有compareTo()方法,同时也实现了Serializable接口。
  在enum实例上调用getDeclaring()方法,我们就能知道其所属的enum类;name()方法返回enum实例声明时的名字,这与使用toString()方法效果相同;valueOf()是在Enum中定义的static方法,可以根据给定的名字返回相应的enum实例,如果不存在给定名字的实例,将会抛出异常。

示例:

import static net.mindview.util.Print.*;

enum Shrubbery { GROUND, CRAWLING, HANGING }

public class EnumClass {
  public static void main(String[] args) {
    for(Shrubbery s : Shrubbery.values()) {
      print(s + " ordinal: " + s.ordinal());
      printnb(s.compareTo(Shrubbery.CRAWLING) + " ");
      printnb(s.equals(Shrubbery.CRAWLING) + " ");
      print(s == Shrubbery.CRAWLING);
      print(s.getDeclaringClass());
      print(s.name());
      print("----------------------");
    }
    // Produce an enum value from a string name:
    for(String s : "HANGING CRAWLING GROUND".split(" ")) {
      Shrubbery shrub = Enum.valueOf(Shrubbery.class, s);
      print(shrub);
    }
  }
} /* Output:
GROUND ordinal: 0
-1 false false
class Shrubbery
GROUND
----------------------
CRAWLING ordinal: 1
0 true true
class Shrubbery
CRAWLING
----------------------
HANGING ordinal: 2
1 false false
class Shrubbery
HANGING
----------------------
HANGING
CRAWLING
GROUND
*/

其中net.mindview.util.Print是一个简单的工具类,只是将输出封装了一下,完全可以更改一下,或者可以从《Java编程思想》随书代码中找到,链接为:http://download.csdn.net/detail/fanxiaobin577328725/9714670

1.2 静态导入enum

  使用static import能够将enum的标识符带入当前的命名空间,所以无需要用enum类型来修饰enum实例。编译器可以确保你使用的是正确的类型,但是需要注意的是,使用静态导入会导致代码令人难以理解。这也是静态导入的缺点,所以谨慎使用。

注意:在定义enum的同一个文件中,以及在默认包中定义enum时,无法使用静态导入。

示例:

//: enumerated/Spiciness.java
package enumerated;

public enum Spiciness {
  NOT, MILD, MEDIUM, HOT, FLAMING
}

测试用例:

//: enumerated/Burrito.java
package enumerated;
import static enumerated.Spiciness.*;

public class Burrito {
  Spiciness degree;
  public Burrito(Spiciness degree) { this.degree = degree;}
  public String toString() { return "Burrito is "+ degree;}
  public static void main(String[] args) {
    System.out.println(new Burrito(NOT));
    System.out.println(new Burrito(MEDIUM));
    System.out.println(new Burrito(HOT));
  }
} /* Output:
Burrito is NOT
Burrito is MEDIUM
Burrito is HOT
*/

1.3 向enum中添加新方法

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

  默认的toString()方法只能返回枚举实例的名字,但是我们有时希望每个枚举实例能够返回对自身的描述。为此,我们可以提供一个构造器,专门负责处理这个额外的信息,然后添加 一个方法,返回这个描述信息。

示例:

//: enumerated/OzWitch.java
// The witches in the land of Oz.
package enumerated;
import static net.mindview.util.Print.*;

public enum OzWitch {
  // Instances must be defined first, before methods:
  WEST("Miss Gulch, aka the Wicked Witch of the West"),
  NORTH("Glinda, the Good Witch of the North"),
  EAST("Wicked Witch of the East, wearer of the Ruby " +
    "Slippers, crushed by Dorothy's house"),
  SOUTH("Good by inference, but missing");
  private String description;
  // Constructor must be package or private access:
  private OzWitch(String description) {
    this.description = description;
  }
  public String getDescription() { return description; }
  public static void main(String[] args) {
    for(OzWitch witch : OzWitch.values())
      print(witch + ": " + witch.getDescription());
  }
} /* Output:
WEST: Miss Gulch, aka the Wicked Witch of the West
NORTH: Glinda, the Good Witch of the North
EAST: Wicked Witch of the East, wearer of the Ruby Slippers, crushed by Dorothy's house
SOUTH: Good by inference, but missing
*/

注意:如果打算定义自己的方法,那么必须在enum实例序列的最后添加一个分号。同时,Java要求你必须先定义enum示例。

  在上面的这个例子中,虽然构造器时private,但是对于他的可访问性,其实并没有什么变化。因为(即使不声明为private)我们只能在enum定义的内部使用其构造器创建enum实例。一旦enum的定义结束,编译器就不允许我们再使用其构造器来创建任何实例了。

1.4 switch语句中的enum

  一般情况下在switch中只能使用整数值,而枚举实例天生就具备整数值的次序,并且可以通过ordinal()方法取得其次序(文章开头提及),因此我们可以在switch语句中使用enum。

  虽然一般情况下我们必须使用enum类型来修饰一个enum实例,但是在case语句中却不必如此。

示例:

//: enumerated/TrafficLight.java
// Enums in switch statements.
package enumerated;
import static net.mindview.util.Print.*;

// Define an enum type:
enum Signal { GREEN, YELLOW, RED, }

public class TrafficLight {
  Signal color = Signal.RED;
  public void change() {
    switch(color) {
      // Note that you don't have to say Signal.RED
      // in the case statement:
      case RED:    color = Signal.GREEN;
                   break;
      case GREEN:  color = Signal.YELLOW;
                   break;
      case YELLOW: color = Signal.RED;
                   break;
    }
  }
  public String toString() {
    return "The traffic light is " + color;
  }
  public static void main(String[] args) {
    TrafficLight t = new TrafficLight();
    for(int i = 0; i < 7; i++) {
      print(t);
      t.change();
    }
  }
} /* Output:
The traffic light is RED
The traffic light is GREEN
The traffic light is YELLOW
The traffic light is RED
The traffic light is GREEN
The traffic light is YELLOW
The traffic light is RED
*///:~

注意:此示例中的switch中没有default但是仍然编译通过,并正常运行,如果我们去除其中某个case分支也是正常的。这就意味着,我们必须自己去确保覆盖所有的分支。但是,如果在case语句中调用return,那么编译器就会强制要求有default语句了。

二、高级

2.1 values()方法

  编译器创建的enum类都继承自java.lang.Enum类,我们都知道Java是单继承的,查看API发现没有这个方法,其实values()方法是有编译器添加的static方法。

  由于values()方法是由编译器插入到enum定义中的static方法,所以你讲enum实例向上转型为Enum,那么values()方法就不可访问了,

示例:

//: enumerated/UpcastEnum.java
// No values() method if you upcast an enum
package enumerated;

enum Search { HITHER, YON }

public class UpcastEnum {
  public static void main(String[] args) {
    Search[] vals = Search.values();
    Enum e = Search.HITHER; // Upcast
//     e.values(); // No values() in Enum
    for(Enum en : e.getClass().getEnumConstants())
      System.out.println(en);
  }
} /* Output:
HITHER
YON
*///:~

注意:因为getEnumConstants()是Class上的方法,所以我们可以对不是枚举的类调用此方法。

2.2 实现,而非继承

  我们已经知道,所有的Enum都继承自java.lang.Enum类。由于Java不支持多重继承,所以你的Enum不能再继承其他类。然而,在我们创建一个新的enum时,可以同时实现一个或多个接口:

//: enumerated/cartoons/EnumImplementation.java
// An enum can implement an interface
package enumerated.cartoons;

import java.util.*;
import net.mindview.util.*;

enum CartoonCharacter implements Generator<CartoonCharacter> {
	SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;
	private Random rand = new Random(47);

	public CartoonCharacter next() {
		return values()[rand.nextInt(values().length)];
	}
}

public class EnumImplementation {
	public static <T> void printNext(Generator<T> rg) {
		System.out.print(rg.next() + ", ");
	}

	public static void main(String[] args) {
		// Choose any instance:
		CartoonCharacter cc = CartoonCharacter.BOB;
		for (int i = 0; i < 10; i++)
			printNext(cc);
	}
}
/*
 * Output: BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY, NUTTY,
 * SLAPPY,
 */// :~

2.3 随机选取

  许多时候我们需要从enum实例中进行随机选择。我们可以利用泛型,从而使得这个工作更一般化,可以作为工具使用。

//: net/mindview/util/Enums.java
package net.mindview.util;
import java.util.*;

public class Enums {
  private static Random rand = new Random(47);
  public static <T extends Enum<T>> T random(Class<T> ec) {
    return random(ec.getEnumConstants());
  }
  public static <T> T random(T[] values) {
    return values[rand.nextInt(values.length)];
  }
}

理解这一段代码需要一点泛型的基础,其中<T extends Enum<T>>表示T是一个enum实例。下面是编写的使用示例:

//: enumerated/RandomTest.java
package enumerated;
import net.mindview.util.*;

enum Activity { SITTING, LYING, STANDING, HOPPING,
  RUNNING, DODGING, JUMPING, FALLING, FLYING }

public class RandomTest {
  public static void main(String[] args) {
    for(int i = 0; i < 20; i++)
      System.out.print(Enums.random(Activity.class) + " ");
    System.out.println();
    System.out.println("-----------------------------------");
    for(int i = 0; i < 20; i++)
    	System.out.print(Enums.random(Activity.values()) + " ");
  }
} /* Output:
STANDING FLYING RUNNING STANDING RUNNING STANDING LYING DODGING SITTING RUNNING HOPPING HOPPING HOPPING RUNNING STANDING LYING FALLING RUNNING FLYING LYING 
-----------------------------------
LYING HOPPING LYING JUMPING DODGING SITTING DODGING LYING RUNNING DODGING SITTING FALLING FALLING JUMPING LYING HOPPING LYING HOPPING RUNNING SITTING 
*///:~

2.4 使用接口组织枚举

  有时我们需要扩展原来enum中的元素,或者希望使用子类将一个enum中的元素进行分类,但是无法从enum继承子类,很让人头痛。

  我们可以在一个借口的内部,创建实现该接口的枚举,以此将元素进行分组,可以达到枚举元素分类组织的目的。

举例来说:假设想用enum来表示不同的类别的食物,同时还希望每个enum元素仍然保持Food类型,实现如下:

//: enumerated/menu/Food.java
// Subcategorization of enums within interfaces.
package enumerated.menu;

public interface Food {
  enum Appetizer implements Food {
    SALAD, SOUP, SPRING_ROLLS;
  }
  enum MainCourse implements Food {
    LASAGNE, BURRITO, PAD_THAI,
    LENTILS, HUMMOUS, VINDALOO;
  }
  enum Dessert implements Food {
    TIRAMISU, GELATO, BLACK_FOREST_CAKE,
    FRUIT, CREME_CARAMEL;
  }
  enum Coffee implements Food {
    BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
    LATTE, CAPPUCCINO, TEA, HERB_TEA;
  }
}

  对于enum而言,实现接口是使其子类化的唯一办法,所以嵌入在Food中的每个enum都实现了Food接口,我们可以说“所有东西都是某种类型的Food”。因为enum类型实现了Food接口,那么我们就可以将其实例向上转型为Food
同样还有一种更简洁的管理枚举的办法,就是将一个enum嵌套在另一个enum内,如下:

//: enumerated/menu/Meal2.java
package enumerated.menu;
import net.mindview.util.*;

public enum Meal2 {
  APPETIZER(Food.Appetizer.class),
  MAINCOURSE(Food.MainCourse.class),
  DESSERT(Food.Dessert.class),
  COFFEE(Food.Coffee.class);
  private Food[] values;
  private Meal2(Class<? extends Food> kind) {
    values = kind.getEnumConstants();
  }
  public interface Food {
    enum Appetizer implements Food {
      SALAD, SOUP, SPRING_ROLLS;
    }
    enum MainCourse implements Food {
      LASAGNE, BURRITO, PAD_THAI,
      LENTILS, HUMMOUS, VINDALOO;
    }
    enum Dessert implements Food {
      TIRAMISU, GELATO, BLACK_FOREST_CAKE,
      FRUIT, CREME_CARAMEL;
    }
    enum Coffee implements Food {
      BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
      LATTE, CAPPUCCINO, TEA, HERB_TEA;
    }
  }
  public Food randomSelection() {
    return Enums.random(values);
  }
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++) {
      for(Meal2 meal : Meal2.values()) {
        Food food = meal.randomSelection();
        System.out.println(food);
      }
      System.out.println("---");
    }
  }
}

三、 拓展

3.1 使用EnumSet替代标志

  对集合有了解对EnumSet应该理解起来就不难了,Set是集合中的一种,其要求添加不重复的对象。enum也要求其成员都是唯一的,所以enum看起来也具有集合的行为,但是enum中是无法删除或添加元素,所以有点鸡肋了。Java SE 5引入EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的“位标志”,EnumSet中的元素必须来自一个enum。

示例:

//: enumerated/AlarmPoints.java
package enumerated;
public enum AlarmPoints {
  STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
  OFFICE4, BATHROOM, UTILITY, KITCHEN
} 

然后我们用EnumSet来进行操作:

//: enumerated/EnumSets.java
// Operations on EnumSets
package enumerated;
import java.util.*;
import static enumerated.AlarmPoints.*;
import static net.mindview.util.Print.*;

public class EnumSets {
  public static void main(String[] args) {
    EnumSet<AlarmPoints> points =
      EnumSet.noneOf(AlarmPoints.class); // Empty set
    points.add(BATHROOM);
    print(points);
    points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
    print(points);
    points = EnumSet.allOf(AlarmPoints.class);
    points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
    print(points);
    points.removeAll(EnumSet.range(OFFICE1, OFFICE4));
    print(points);
    points = EnumSet.complementOf(points);
    print(points);
  }
} /* Output:
[BATHROOM]
[STAIR1, STAIR2, BATHROOM, KITCHEN]
[LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY]
[LOBBY, BATHROOM, UTILITY]
[STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4, KITCHEN]
*/

3.2 使用EnumMap

————————》》后期完善

参考资料:

  • 《Java编程思想 第四版》

赞赏

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页