Java编程笔记19:枚举

本文详细探讨了Java中的枚举,包括枚举的基本操作、静态导入、枚举类的实现、覆盖Enum的方法、switch语句的使用、values()方法、实现接口而非继承、随机选取枚举值、通过接口组织枚举、枚举的枚举、EnumSet和EnumMap的使用。同时,还介绍了如何使用枚举实现状态模式和责任链模式,以及如何实现多路分发。通过示例代码,阐述了枚举在实际开发中的应用和优势。
摘要由CSDN通过智能技术生成

Java编程笔记19:枚举

5c9c3b3b392ac581.jpg

图源:PHP中文网

之前在Java编程笔记2:初始化和清理 - 魔芋红茶’s blog (icexmoon.cn)中简单介绍过枚举,在对反射和泛型等内容学习后,我们可以更深入地学习和理解枚举。

基本操作

下面这个例子说明了一些枚举的基本操作:

package ch19.basic;

import util.Fmt;

enum Color {
   
    RED, BLUE, GREEN, YELLO, BLACK
}

public class Main {
   
    public static void main(String[] args) {
   
        Color comparedColor = Color.GREEN;
        for (Color color : Color.values()) {
   
            System.out.print(color + "\t");
            System.out.print("#" + color.ordinal() + "\t");
            String flag = "";
            int compare = color.compareTo(comparedColor);
            if (compare < 0) {
   
                flag = "<";
            } else if (compare == 0) {
   
                flag = "=";
            } else {
   
                flag = ">";
            }
            Fmt.printf("%s %s %s\t", color, flag, comparedColor);
            if (color == comparedColor) {
   
                flag = "=";
            } else {
   
                flag = "!=";
            }
            Fmt.printf("%s %s %s\t", color, flag, comparedColor);
            if (color.equals(comparedColor)) {
   
                flag = "=";
            } else {
   
                flag = "!=";
            }
            Fmt.printf("%s %s %s\t", color, flag, comparedColor);
            System.out.print(color.name() + "\t");
            System.out.print(color.getDeclaringClass());
            System.out.println();
        }
        Color c = Enum.valueOf(Color.class, "RED");
        System.out.println(c);
    }
}
// RED     #0      RED < GREEN     RED != GREEN    RED != GREEN    RED     class ch19.basic.Color
// BLUE    #1      BLUE < GREEN    BLUE != GREEN   BLUE != GREEN   BLUE    class ch19.basic.Color
// GREEN   #2      GREEN = GREEN   GREEN = GREEN   GREEN = GREEN   GREEN   class ch19.basic.Color
// YELLO   #3      YELLO > GREEN   YELLO != GREEN  YELLO != GREEN  YELLO   class ch19.basic.Color
// BLACK   #4      BLACK > GREEN   BLACK != GREEN  BLACK != GREEN  BLACK   class ch19.basic.Color
// RED

枚举类型有一个values方法,会返回一个所有枚举值组成的数组。可以利用它遍历所有的枚举值。

枚举值会按照定义的循序分配一个整型值,可以用ordinal方法获取。

枚举类型实现了Comparable接口,所以可以使用compareTo方法进行比较,比较结果与枚举对值的整型值的比较结果一致。

此外,可以通过compareTo比较两个枚举值是否相等,或者用==比较也是同样的效果。

枚举值的name方法会返回枚举值的字面量对应的字符串,getDeclaringClass方法可以获取枚举类型。

最后,可以用Enum.valueOf方法获取枚举值,其第一个参数是枚举类型的Class对象,第二个参数是枚举值字面量对应的字符串。

静态导入

通常在使用枚举值的时候必须带上枚举类型,比如Color.RED这样。实际上枚举值是枚举类型的静态属性,所以可以利用静态导入让使用枚举值的方式更简单:

package ch19.static1;

public enum Color {
   
    RED, GREEN, BLUE
}
package ch19.static1;

import static ch19.static1.Color.*;

public class Main {
   
    public static void main(String[] args) {
   
        System.out.println(RED);
        System.out.println(GREEN);
        System.out.println(BLUE);
        System.out.println(RED.getDeclaringClass());
    }
}
// RED
// GREEN
// BLUE
// class ch19.static1.Color

这个示例中枚举类型和Main是分别定义在一个包中的两个java文件中的,但其实在同一个文件中也可以静态导入枚举值,但似乎没有太大必要。

Enum

如果你使用javap查看枚举类型的字节码,就会看到类似下面这样的内容:

❯ javap -cp D:\workspace\java\java-notebook\xyz\icexmoon\java_notes ch19.methods2.Color
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
Compiled from "Color.java"
public final class ch19.methods2.Color extends java.lang.Enum<ch19.methods2.Color> {
   
  public static final ch19.methods2.Color RED;
  public static final ch19.methods2.Color GREEN;
  public static final ch19.methods2.Color BLUE;
  public static ch19.methods2.Color[] values();
  public static ch19.methods2.Color valueOf(java.lang.String);
  static {
   };
}

这说明Java中的枚举类型实际上是一个继承自java.lang.Enum类的子类,并且被声明为final的,因此枚举类型不能被继承。此外,Enum还是一个泛型类,所以你可以看到,示例中出现Color extends Enum<Color>这样的内容,类似的写法在泛型部分我们解释过。

枚举值被以静态属性的方式定义,就像示例中的REDGREEN那样。

最后,相比基类Enum,枚举类型还多出两个静态方法valuesvalueOf

当然,这些实现细节对开发者是不可见的,都是由Java编译器实现的,我们只需要编写enum{...}这样的简单的枚举类型定义,编译器会自动帮我们将其转换为字节码中的这种定义。

了解上边这些内容后,我们可以通过反射的方式进一步进行验证:

package ch19.methods2;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import util.Fmt;

public class Main {
   
    public static void main(String[] args) {
   
        Set<String> subMethods = analysis(Color.class);
        Set<String> superMethods = analysis(Enum.class);
        subMethods.removeAll(superMethods);
        System.out.println(subMethods);
    }

    private static <T extends Enum> Set<String> analysis(Class<T> cls) {
   
        Fmt.printf("========analysis %s=========\n", cls.getName());
        System.out.println("super class:" + cls.getSuperclass().getName());
        System.out.println("interfaces:" + Arrays.toString(cls.getInterfaces()));
        Set<String> methods = new HashSet<>();
        for (Method method : cls.getMethods()) {
   
            methods.add(method.getName());
        }
        System.out.println("methods:" + methods);
        return methods;
    }
}
// ========analysis ch19.methods2.Color=========
// super class:java.lang.Enum
// interfaces:[]
// methods:[getClass, wait, valueOf, values, notifyAll, compareTo, describeConstable, notify, getDeclaringClass, hashCode, equals, name, toString, ordinal]
// ========analysis java.lang.Enum=========
// super class:java.lang.Object
// interfaces:[interface java.lang.constant.Constable, interface java.lang.Comparable, interface java.io.Serializable]
// methods:[getClass, wait, valueOf, notifyAll, compareTo, describeConstable, notify, getDeclaringClass, hashCode, equals, name, toString, ordinal]
// [values]

这个示例说明了上边的论点,之所以两个方法名称集合求差集后只有values没有valueOf,是因为实际上Enum也有一个静态方法valueOf,这点之前已经展示过了,不过它需要两个参数,而枚举类型只需要一个。

添加方法

就像上边说的,enum类型可以看作是一种特殊的类,除了存在一些限制外,大多数情况下可以当作普通的类来使用。这里边也包含添加普通方法和类方法,甚至包括构造函数:

package ch19.methods;

import java.util.Random;

enum Color {
   
    RED("this is a dack color."), BLUE, YELLOW("this is a light color."), BLACK, WHITE;

    private String des;
    private static Random rand = new Random();

    private Color(String des) {
   
        this.des = des;
    }

    private Color() {
   
        this("default description.");
    }

    public String getDes() {
   
        return this.des;
    }

    public static Color getRandomColor() {
   
        Color[] colors = values();
        return colors[rand.nextInt(colors.length)];
    }
}

public class Main {
   
    public static void main(String[] args) {
   
        for (int i = 0; i < 6; i++) {
   
            Color c = Color.getRandomColor();
            System.out.print(c + " ");
            System.out.println(c.getDes());
        }
    }
}
// BLACK default description.
// BLUE default description.
// WHITE default description.
// BLUE default description.
// WHITE default description.
// YELLOW this is a light color.

要注意的是,为枚举类添加的构造函数必须是private,否则无法通过编译。这样规定是合理的,因为实际上除了在枚举类型定义中添加枚举值,你是没法在其它地方添加枚举值的,自然其构造函数也就没有必要是private以外的访问权限。

还有要注意的是,在为枚举类型添加了其它属性和方法后,就需要在枚举值后添加一个;作为结束。

因为枚举值可以看作是一种“常量”,所以枚举类型的普通方法也被称作”特定常量方法“(constant-specific methods)。

覆盖Enum的方法

我们已经知道,枚举类型继承自Enum,因此,可以覆盖Enum的方法:

package ch19.methods3;
...
enum Color {
   
	...
    @Override
    public String toString() {
   
        return Fmt.sprintf("%s(%s)", name(), des);
    }
}

public class Main {
   
    public static void main(String[] args) {
   
        for (int i = 0; i < 6; i++) {
   
            Color c = Color.getRandomColor();
            System.out.println(c);
        }
    }
}
// YELLOW(this is a light color.)
// RED(this is a dack color.)
// RED(this is a dack color.)
// YELLOW(this is a light color.)
// BLUE(default description.)
// BLACK(default description.)

switch

Java编程笔记1:基础 - 魔芋红茶’s blog (icexmoon.cn)中我们讨论了Java中switch语句的局限性,并且在Java编程笔记2:初始化和清理 - 魔芋红茶’s blog (icexmoon.cn)中提到可以在switch语句中使用枚举类型。

下面是一个使用switch和枚举编写的红绿灯模拟示例:

package ch19.switch1;

enum TrafficLight {
   
    RED, YELLOW, GREEN
}

public class Main {
   
    public static void main(String[] args) {
   
        TrafficLight tl = TrafficLight.RED;
        StringBuilder sb= new StringBuilder();
        sb.append(tl);
        for (int i = 0; i < 10; i++) {
   
            tl = turn(tl);
            sb.append("=>");
            sb.append(tl);
        }
        System.out.println(sb.toString());
    }

    private static TrafficLight turn(TrafficLight tl) {
   
        switch (tl) {
   
            case RED:
                return TrafficLight.GREEN;
            case GREEN:
                return TrafficLight.YELLOW;
            case YELLOW:
                return TrafficLight.RED;
            default:
                return TrafficLight.RED;
        }
    }
}
// RED=>GREEN=>YELLOW=>RED=>GREEN=>YELLOW=>RED=>GREEN=>YELLOW=>RED=>GREEN

这里的红绿灯转换可以看作是一个简单的状态机,有意思的是,将枚举值用于switch块中的case关键字后时,可以省略掉枚举类型,这可能是因为switch条件中已经包含了一个枚举实例,编译器可以根据该实例确定其所属的枚举类型,所以就没必要在case后明确所属的枚举类型了。但是这也仅仅限于case条件语句,case之后的执行语句块中依然需要写明枚举类型。

values()

在前边已经介绍了编译器是如何实现枚举类型,以及如何给枚举类型添加了一个values()静态方法。这里再介绍一些相关示例。

因为values()方法是编译器添加的,而Enum实际上是没有该方法的,所以如果将一个枚举值向上转型为Enum类型,就无法调用这个方法。

package ch19.values;

enum Color {
   
    RED, YELLOW, GREEN, BLUE
}

public class Main {
   
    public static void main(String[] args) {
   
        Color[] colors = Color.RED.values();
        System.out.println(colors);
        Enum e = Color.RED;
        // colors = e.values();
    }
}

注释部分代码无法通过编译。

实际上这里不应该使用Color.RED.values这样的代码,实际上这是用对象调用静态方法,是不被推荐的方式。但在这个例子中,e.values()实际上也是试图在用一个Enum实例调用静态方法,所以是有意为之。

虽然在这个例子中,句柄已经从Color变成了Enum,产生了”细节丢失“,也因此无法调用values获取全部枚举值,但实际上e的真实类型依然是Color,并不会因为句柄的改变而改变(这也正是多态的精髓所在)。所以我们可以通过反射来还原真实类型,并获取相应的枚举值:

package ch19.values2;

import java.util.Arrays;

enum Color {
   
    RED, YELLOW, GREEN, BLUE
}

public class Main {
   
    public static void main(String[] args) {
   
        Color[] colors = Color.RED.values();
        System.out.println(Arrays.toString(colors));
        Enum e = Color.RED;
        // colors = e.values();
        Enum<Color>[] colors2 = e.getClass().getEnumConstants();
        System.out.println(Arrays.toString(colors2));
    }
}
// [RED, YELLOW, GREEN, BLUE]
// [RED, YELLOW, GREEN, BLUE]

Class.getEnumConstants方法可以获取枚举类型的全部枚举值,类似于values。但是前提必须是Class对象是一个枚举类型的Class对象。如果你对一个非枚举类型的Class对象调用该方法:

package ch19.values3;

public class Main {
   
    public static void main(String[] args) {
   
        System.out.println(Integer.class.getEnumConstants());
    }
}
// null

实现,而非继承

前边说了,枚举类型继承自Enum类,Java也不支持多继承,因此我们不能让枚举类型继承其它类。但是可以让其实现其它接口:

package ch19.interface1;

import ch15.test2.Generator;

enum TrafficLight implements Generator<TrafficLight> {
   
    RED, GREEN, YELLOW;

    @Override
    public TrafficLight next() {
   
        switch (this) {
   
            case RED:
                return GREEN;
            case GREEN:
                return YELLOW;
            case YELLOW:
                return RED;
            default:
                return RED;
        }
    }

}

public class Main {
   
    public static void main(String[] args) {
   
        Generator<TrafficLight> gen = TrafficLight.RED;
        StringBuilder sb = new StringBuilder();
        sb.append(gen);
        for (int i = 0; i < 10; i++) {
   
            gen = gen.next();
            sb.append("=>");
            sb.append(gen);
        }
        System.out.println(sb.toString());
    }
}
// RED=>GREEN=>YELLOW=>RED=>GREEN=>YELLOW=>RED=>GREEN=>YELLOW=>RED=>GREEN

在这个示例中,枚举类型TrafficLight实现了Generator接口,可以看作是一个生成器模式的应用,不过因为TrafficLight也可以看作一个简单的状态机,所以也可以看作是一个状态模式,交通灯可以由next方法从一个状态转换到下一个状态。

关于状态模式,可以阅读设计模式 with Python 10:状态模式 - 魔芋红茶’s blog (icexmoon.cn)

随机选取

经常会有从某个枚举类型中随机选一个枚举值的需求,可以为此编写一个工具方法:

package util;

import java.util.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值