Java编程思想 第十九章:枚举类型

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

1. 基本enum特性

values()返回enum实例的数组,而且保持声明的顺序:

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);
        }
    }
} 

运行结果:
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
  1. ordinal方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。
  2. ==来比较enum实例,编译器会自动提供equals和hashCode方法。
  3. getDeclaringClass()获取其所属的enum类。
  4. name()返回enum实例声明时的名字,与使用toString()效果相同。
  5. valueOf()实在Enum中定义的static方法,他根据给定的名字返回相应的enum实例,如果不存在会抛出异常。

1.1 将静态导入用于enum

使用static import能够将enum实例的标识符代入当前的命名空间,所以无需再用enum类型来修饰enum实例。唯一担心的是使用静态导入会不会导致代码令人难以理解。

1.2 向enum中添加新方法

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

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());
        }
    }
}

必须在enum实例序列的最后添加一个分号。Java要求必须先定义enum实例,如果在实例之前定义任何方法或属性,编译时会报错。有意将构造器声明为private,但对于他的可访问性并没有什么影响,因为即使不声明为private,我们只能在enum内部使用其构造器创建enum实例。一旦enum的定义结束,编译器就不允许在使用其构造器来创建任何实例了。

1.3 覆盖enum的方法

public enum SpaceShip {
    SCOUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP;

    @Override
    public String toString() {
        String id = name();
        String lower = id.substring(1).toLowerCase();
        return id.charAt(0) + lower;
    }

    public static void main(String[] args) {
        for (SpaceShip s : values()) {
            System.out.println(s);
        }
    }
}

2. switch语句中的enum

一般来说switch中只能使用整形值,而枚举类型天生就具备整形值的次序,并且可以通过ordinal()方法获取其次序。

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;
        }
    }

    @Override
    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();
        }
    }
}

3.values()的神秘之处

enum类都继承自Enum类,我们可以查看Enum中并没有values()方法。利用反射机制查看究竟:

values()是由编译器添加的static方法。同时创建Explore的过程中,编译器还添加了valueOf()方法。不是Enum类不是已经有valueOf()方法了吗,不过Enum中的ValueOf()方法需要两个参数,这个新增方法只需一个参数。由于Set只存储方法的名字,不考虑签名,所以removeAll只剩下values。

由于values方法有编译器插入到enum定义中的static方法,所以enum向上转型为Enum,那么values就不可访问了,不过Class中有一个getEnumConstants方法,所以即便Enum接口中没有vlaues方法,仍然可以通过Class对象取得所有enum实例:

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);
        }
    }
} /*
HITHER
YON
*/

getEnumConstants() 获取所有Enum对象的实例

5. 实现,而非继承

创建一个新的enum,可以同时实现一个或多个接口

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

    @Override
    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);
        }
    }
} /*
BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY, NUTTY, SLAPPY,
*/

6. 随机选取

7. 使用接口组织枚举

有时希望使用子类将一个enum中的元素进行分组。在一个接口内部创建实现该接口的枚举,以此将元素分组。

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 class TypeOfFood {
  public static void main(String[] args) {
    Food food = Appetizer.SALAD;
    food = MainCourse.LASAGNE;
    food = Dessert.GELATO;
    food = Coffee.CAPPUCCINO;
  }
}

8. 使用EnumSet替代标志

Set是一种集合不能添加重复元素。enum也要求其成员是唯一的。

Java SE5引入了EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的“位标志”。这种标志可以用来表示某种开关信息,不过,使用这种标志,最终操作的只是一些bit。使用EnumSet的有点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。

EnumSet 包含的使用方法:

EnumSet<AlarmPoints> points =EnumSet.noneOf(AlarmPoints.class); // Empty set

points.addAll() 添加所有Enum元素
EnumSet.of() 返回参数中添加的Enum元素集合
points.removeAl() 移除参数中包含的Enum元素集合
EnumSet.complementOf() 用于创建包含与指定的Enum_Set类型相同的元素的EnumSet
研究EnumSet文档,会发现of()方法被重载了很多次,不但为可变数量参数进行了重载,而且为接受2至5个显式的参数的情况都进行了重载。这也从侧面表现了EnumSet对性能的关注。

9. 使用EnumMap

EnumMap要求其中的键必须来自于一个enum由于enum本身的限制,所以EnumMap内部是数组实现。因此EnumMap速度很快,可以放心进行查找操作。

EnumMaps 包含的方法

EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
interface Command {
    void action();
}

public class EnumMaps {
    public static void main(String[] args) {
        EnumMap<AlarmPoints, Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
        em.put(KITCHEN, new Command() {
            public void action() {
                print("Kitchen fire!");
            }
        });
        em.put(BATHROOM, new Command() {
            public void action() {
                print("Bathroom alert!");
            }
        });
        for (Map.Entry<AlarmPoints, Command> e : em.entrySet()) {
            printnb(e.getKey() + ": ");
            e.getValue().action();
        }
        try { // If there's no value for a particular key:
            em.get(UTILITY).action();
        } catch (Exception e) {
            print(e);
        }
    }
}

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

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

10 常量相关的方法

**在Enum中定义的元素都是Enum类中的各个实例对象。每个Enum元素都是一个Enum类型的staic final类型对象。**Java的enum有一个非常有趣的特性,即它允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为。要实现常量相关的方法,需要为enum定义一个或多个abstract方法,然后为每个enum实例实现该抽象方法:

public enum ConstantSpecificMethod {
    DATE_TIME {
        String getInfo() {
            return
                    DateFormat.getDateInstance().format(new Date());
        }
    },
    CLASSPATH {
        String getInfo() {
            return System.getenv("CLASSPATH");
        }
    },
    VERSION {
        String getInfo() {
            return System.getProperty("java.version");
        }
    };

    abstract String getInfo();

    public static void main(String[] args) {
        for (ConstantSpecificMethod csm : values()) {
            System.out.println(csm.getInfo());
        }
    }
}

10.1 使用enum的职责链

10.2 使用enum的状态机

猜你喜欢

转载自blog.csdn.net/qq_21125183/article/details/85088516
今日推荐