Java枚举使用方式和注意事项

枚举类型 是一组固定常量组成合法值的类型,比如一年中的季节,一周中的星期。

int枚举模式

不使用枚举时定义常量的方法通常是这样:

public final class Week {
    public static final int WEEK_MONDAY = 1;
    public static final int WEEK_TUESDAY = 2;
    public static final int WEEK_WEDNESDAY = 3;
    public static final int WEEK_THURSDAY = 4;
    public static final int WEEK_FRIDAY = 5;
    public static final int WEEK_SATURDAY = 6;
    public static final int WEEK_SUNDAY = 7;
}

这种方法称作int枚举模式,这种方式有些缺点:

  • 类型非安全,不能在编译期间发现问题,比如setWeek(10)或者setWeek(MONTH_JANUARY)在语法上都是合法的,但你的本意并非如此。
  • 可读性差,缺乏方便的方法把常量转换为有意义的字符串显示。

java1.5开始java提供一种新的引用类型 – 枚举类型(enum type)

定义枚举类型以及简单使用

/**
* 定义了加、减、乘、除 操作
*/
public enum Operation {
  PLUS, MINUS, TIMES, DIVIDE
}
//计算并打印结果
public static void calc(int num1, int num2, Operation op) {

    switch (op) {
        case PLUS:
            System.out.println(num1 + " + " + num2 + " = "+num1 + num2);
            break;
        case MINUS:
            System.out.println(num1 - num2);
            break;
        case TIMES:
            System.out.println(num1 * num2);
            break;
        case DIVIDE:
            System.out.println(num1 / num2);
            break;

        default:
            throw new RuntimeException();
    }
}

  //调用计算方法
  calc(1, 2, Operation.PLUS);//输出: 1 + 2 = 12

其中的PLUS MINUS等都是Operation类型,因此可以避免上面int枚举模式中类型安全问题,
可以利用编译器检查出类型不匹配。

枚举的一些特点

使用javap反编译上面的Operation.class得到下面代码

public final class com.erick.hello.Operation extends java.lang.Enum<com.erick.hello.Operation> {
    public static final com.erick.hello.Operation PLUS;
    public static final com.erick.hello.Operation MINUS;
    public static final com.erick.hello.Operation TIMES;
    public static final com.erick.hello.Operation DIVIDE;
    static {};
    public static com.erick.hello.Operation[] values();
    public static com.erick.hello.Operation valueOf(java.lang.String);
}

通过上面反编译的结果可以知道:

  • Operation隐式继承了java.lang.EnumEnum实现了Comparable Serializable 这两个接口。
  • Operation不能再继承其他类,但可以实现其他接口。
  • Operation是final的,所以不能被扩展。但这不是真正原因,真正的限制应该是编译器做的限制。
  • 新生成了两个方法values() valueOf(String)
  • 定义的每一个枚举值实际上都是一个Operation类型的实例

除了完善了int枚举类型的不足之外,枚举类型还可以添加任意的方法和属性,并且可以实现任意接口。
添加任意方法的原因可能是想要将数据或者操作与对应的常量关联。

为枚举增加属性和方法

/**
 * 定义了加、减、乘、除 操作
 */
public enum Operation {
    //枚举常量的定义必须在第一行,最后一个常量后面加分号,后面是定义的属性和方法
    PLUS("+"), MINUS("-"), TIMES("*"), DIVIDE("/");

    private String op;

    private Operation(String op) {
        this.op = op;
    }

    public String getOP() {
        return op;
    }

    public void calc(int num1, int num2) {
        switch (this) {
        case PLUS:
            System.out.println(num1 + op + num2 + " = " + (num1 + num2));
            break;
        case MINUS:
            System.out.println(num1 - num2);
            break;
        case TIMES:
            System.out.println(num1 * num2);
            break;
        case DIVIDE:
            System.out.println(num1 / num2);
            break;

        default:
            throw new RuntimeException();
        }
    }

}


//测试
Operation.PLUS.calc(1, 2);
//输出:1+2 = 3

上面的例子中如果想要增加一个开方运算,就需要增加一个开方枚举,并在switch case 中加一个分支来做具体运算。
但是这样很容易只添加了开方枚举,但忘记了具体运算的实现,而且能够顺利编译。下面的方法可以尽量避免这一情况。

public enum Operation {
    PLUS("+") {
        @Override
        void calc(int num1, int num2) {
            System.out.println(num1 + this.getOP() + num2 + " = " + (num1 + num2));

        }
    },
    MINUS("-") {
        @Override
        void calc(int num1, int num2) {
            System.out.println(num1 + this.getOP() + num2 + " = " + (num1 + num2));

        }
    };

    private String op;

    private Operation(String op) {
        this.op = op;
    }

    public String getOP() {
        return op;
    }

    abstract void calc(int num1, int num2);
}

枚举中通用的方法

//比较此枚举与指定对象的顺序。
int compareTo(E o) 

//返回与此枚举常量的枚举类型相对应的 Class 对象。
Class getDeclaringClass() 

//返回此枚举常量的名称,在其枚举声明中对其进行声明。
String name() 

//与name()返回一致
String toString()

//返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
int ordinal() 

//返回带指定名称的指定枚举类型的枚举常量。
static > T valueOf(String name) 

//测试
for(Operation o : Operation.values()){
    System.out.println(o.ordinal() + " : " + o.name() + "  : " + o.getDeclaringClass());
}
// 输出:
//  0 : PLUS  : class com.erick.hello.Operation
//  1 : MINUS  : class com.erick.hello.Operation
//  2 : TIMES  : class com.erick.hello.Operation
//  3 : DIVIDE  : class com.erick.hello.Operation

ordinal()的使用

大多数情况下不需要使用这个方法,这个方法的设计是用于像EnumSet和EnumMap这种基于枚举通用数据结构的,
除非是编写这些数据结构,否则应该避免使用ordinal方法。

如果需要枚举与一个int值绑定,应该使用枚举实例的属性来绑定。

public enum Week {
    WEEK_MONDAY(1), WEEK_TUESDAY(2);

    private int num;

    Week(int num) {
        this.num = num;
    }
}

使用接口模拟可伸缩的枚举

枚举类型不能被继承,这是语言特性,而且枚举是用来表示有限数量的对象,因此大多情况下并不需要扩展。
但有些情况,比如一些特特定操作,当下只包括了所有操作的一部分就可以满足需求,
但需要更多操作时就需要扩展。

上面的计算器例子中,实现了计算器的加、减、乘、除操作,但之后又需要一个求幂的操作,
这时可以实现一个操作接口,来实现可扩展的特性。

//操作接口
public interface Operation {
    void calc(int num1, int num2);
}

//基础计算枚举
public enum BasicOperation implements Operation{
    //....省略了减法和乘除。
    PLUS("+") {
        @Override
        public void calc(int num1, int num2) {
            System.out.println(num1 + this.getOP() + num2 + " = " + (num1 + num2));

        }
    };

    private String op;

    private BasicOperation(String op) {
        this.op = op;
    }

    public String getOP() {
        return op;
    }
}

//扩展一个幂运算
public enum ExtendedOperation implements Operation{
    PLUS("^") {
        @Override
        public void calc(int num1, int num2) {
            System.out.println(num1 + this.getOP() + num2 + " = " + Math.pow(num1, num2));

        }
    };

    private String op;

    private ExtendedOperation(String op) {
        this.op = op;
    }

    public String getOP() {
        return op;
    }
}

只要API通过接口的方式操作,就可以允许客户端扩展自己的操作方式。

你可能感兴趣的:(java基础)