枚举类(enum)

【1】什么是枚举类?

    实例(也叫对象)有限且固定不变的类,在Java里被称为枚举类。

    例如,季节类,它只有4个实例(春、夏、秋、冬),并且这4个实例不会改变。可以用枚举类来表示:

public enum SeasonEnum{
    SPRING,SUMMER,FALL,WINTER;
}

    枚举类是一种特殊的类,它一样可以有自己的成员变量、方法,可以实现一个或多个接口,也可以有自己的构造器。

    Java5新增了enum 关键字(与calss、interface 关键字用法相同),用来定义枚举类。

【2】为什么需要枚举类?

    (1)就如上面所说的,有些类的实例有限且固定,需要有一种特定且方便的方式来表示这种类。

    (2)使用枚举类可以使程序更加健壮,避免创建对象的随意性。

    (3)避免一些常量值的意义不明确(这个在下面会有具体示例)。

【3】枚举类的语法

    (1) 枚举类默认继承 java.lang.Enum 类,而不是 Object 类,因此枚举类不能显示继承其他父类。

    (2) 使用 enum 定义的非抽象的枚举类默认会使用 final 修饰,因此非抽象枚举类不能派生子类(即不能被继承)。

             > final关键字回顾:final修饰的类不能被继承、修饰的方法不能被重写、修饰的属性其值不能改变。

    (3) 枚举类的构造器只能使用 private 访问控制符,如果忽略访问控制符的话,则默认使用 private 修饰;如果强制指定其他的访问控制符(例如public、procted等),则会报错。

    (4) 枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远都不可能产生实例。列出的这些实例,系统会自动给它们加上 public static final 修饰。枚举类的实例以逗号分隔,分号结束,这些列出的枚举值代表了该枚举类的所有可能的实例。

【4】枚举类的方法和用法

    (1)JDK 1.5中switch对枚举的扩展

            switch( )的控制表达式(即括号中的条件)可以是任何枚举类型;当switch控制表达式使用枚举类型时,后面case表达式中的值可以直接使用枚举值的名字,而无需添加枚举类作为限定(不需要 [ 也不能 ] 这样写:SeasonEnum.SPRING)。

public enum SeasonEnum{
    SPRING,SUMMER,FALL,WINTER;
}
public void test(SeasonEnum s){
        switch(s)
        {
                case SPRING:
			System.out.println("春天到了");
			break;
		case SUMMER:
			System.out.println("夏天到了");
			break;
		case FALL:
			System.out.println("秋天到了");
			break;
		default:
			System.out.println("冬天到了");
        }
}

    (2)枚举类默认有一个 values() 方法(不是继承来的),可以返回该枚举类的所有实例

for(SeasonEnum s : SeasonEnum.values()){
	System.out.println(s);
}

        System.out.pring(s)输出的结果和 System.out.pring(s.toString()) 是一样的,因为 System.out.pring(s) 实际上也是调用了 toString() 方法。

    (3)所有枚举类都继承了 java.lang.Enum 抽象类,所以枚举类可以直接使用 java.lang.Enum 类中所包含的方法

//java.lang.Enum
package java.lang;

import java.io.Serializable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

	private final String name;
	public final String name() {
		return name;
	}
	private final int ordinal;
	public final int ordinal() {
		return ordinal;
	}
	protected Enum(String name, int ordinal) {
        	this.name = name;
        	this.ordinal = ordinal;
    	}
	public String toString() {
		return name;
	}
	public final boolean equals(Object other) {
		return this==other;
	}
	public final int hashCode() {
		return super.hashCode();
	}
	protected final Object clone() throws CloneNotSupportedException {
     		throw new CloneNotSupportedException();
    	}
    	public final int compareTo(E o) {
        	Enum<?> other = (Enum<?>)o;
        	Enum<E> self = this;
        	if (self.getClass() != other.getClass() && self.getDeclaringClass() != other.getDeclaringClass())
        		throw new ClassCastException();
        	return self.ordinal - other.ordinal;
    	}
	public final Class<E> getDeclaringClass() {
        	Class<?> clazz = getClass();
	        Class<?> zuper = clazz.getSuperclass();
        	return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    	}
	public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {
        	T result = enumType.enumConstantDirectory().get(name);
	        if (result != null)
        	    return result;
	        if (name == null)
        	    throw new NullPointerException("Name is null");
	        throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + name);
    	}
	protected final void finalize() { }
        private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {
	        throw new InvalidObjectException("can't deserialize enum");
    	}
	private void readObjectNoData() throws ObjectStreamException {
        	throw new InvalidObjectException("can't deserialize enum");
    	}
}

        【###】常用方法

            > int compareTo(E o):用于与指定枚举对象比较顺序,同一个枚举实例只能与相同类型的枚举实例进行比较。如果该枚举对象在指定枚举对象之后,则返回正整数;如果该枚举对象再指定枚举对象之前,则返回负整数;否则返回0。

public enum SeasonEnum {
    SPRING,SUMMER,FALL,WINTER;
}

System.out.println(SeasonEnum.SUMMER.compareTo(SeasonEnum.SPRING));  //1
System.out.println(SeasonEnum.SUMMER.compareTo(SeasonEnum.SUMMER));  //0
System.out.println(SeasonEnum.SUMMER.compareTo(SeasonEnum.FALL));    //-1
System.out.println(SeasonEnum.SUMMER.compareTo(SeasonEnum.WINTER));  //-2

        > String name()、String toString():返回枚举常量(即实例)的名称,两个方法作用相似(可以看Enum类的源码进行比较),推荐使用 toString()。

System.out.println(SeasonEnum.SPRING.name());        // SPRING
System.out.println(SeasonEnum.SPRING.toString());    // SPRING

        > ordinal():返回枚举值(即实例)在枚举类中的索引值(即声明时的位置),第一个枚举值的索引值为 0。

System.out.println(SeasonEnum.SPRING.ordinal());    // 0
System.out.println(SeasonEnum.SUMMER.ordinal());    // 1
System.out.println(SeasonEnum.FALL.ordinal());      // 2
System.out.println(SeasonEnum.WINTER.ordinal());    // 3

        > public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name):静态方法,用于返回指定枚举类中指定名称的枚举值。名称必须与在该枚举类中声明枚举值时所使用的标识符完全匹配,不允许使用额外的空白字符。

SeasonEnum seasonEnum = SeasonEnum.valueOf(SeasonEnum.class, SeasonEnum.FALL.toString());
System.out.println(seasonEnum.toString());    //FALL

【5】枚举类的成员变量、方法和构造器

    前面说过,枚举类是一种特殊的类,它一样可以有自己的成员变量、方法,可以实现一个或多个接口,也可以有自己的构造器。

    同时,枚举类的每个实例的成员变量的值(即对应的含义)应该是不能改变的;所以,建议将枚举类的成员变量都使用 pirvate final 修饰。

    那么问题又来了,所有成员变量都使用 final 修饰,则必须使用下面三种方法为成员变量指定初始值:

        (1)在构造器里为这些成员变量指定初始值。

        (2)在定义成员变量时指定默认值。

        (3)在初始化块中指定初始值。

    实际上,后面两种方式并不常用。所以,应该为枚举类显示定义带参数的构造器。一旦为枚举类显示定义了带参数的构造器,列举枚举值时就必须对应的传入参数。

public enum Gender{
    MALE("男"),FEMALE("女");
    private final String name;
    private Gender(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
}

    在第一行列出所有枚举值(实例)时,实际上就是调用了构造器创建枚举类对象;只是这里不需要使用 new 关键字,也无需显示调用构造器罢了。

    在前面定义 SeasonEnum 时,没有传入参数,甚至没使用括号;是因为 SeasonEnum 里包含的是默认的无参构造器。

6】实现接口的枚举类,不同枚举值(实例)可以提供不同的接口方法实现方式

    > 定义一个接口:

public interface GenderDesc{
    void info();
}

    > 枚举类实现该接口,并重写接口中的方法(普通方式):

public enum Gender implements GenderDesc{
    MALE("男"),FEMALE("女");
    private final String name;
    private Gender(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
    public void info(){
        System.out.println("这是一个性别枚举类");
    }
}
        如果由枚举类来实现接口里的方法,则每个枚举值在调用该方法时都有相同的行为方式。

    > 枚举类实现该接口,并重写接口中的方法(枚举类特有方式):

public enum Gender implements GenderDesc{
    MALE("男"){
	public void info(){
      	    System.out.println("这个枚举值代表男性");
    	}
    },
    FEMALE("女"){
	public void info(){
            System.out.println("这个枚举值代表女性");
        }
    };
    private final String name;
    private Gender(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
}

        当每个枚举实例像上面这样分别实现该方法时,在枚举实例调用该方法时就会呈现出不同的行为方式。

        上面这种在枚举值后面紧跟花括号的语法其实是创建匿名内部类的语法,花括号就是类体部分。当创建 MALE、FEMALE 枚举值时,并不是直接创建 Gender 枚举类的实例,而是相当于创建 Gender 的匿名子类的实例。编译上述程序,可以看到生成了 Gender.class、Gender$1.class、Gender$2.class 三个文件。

        并不是所有的枚举类都使用了 final 修饰,非抽象的枚举类才默认使用 final 修饰。对于一个抽象的枚举类(只要它包含了抽象方法,它就是抽象枚举类)而言,系统默认使用 abstract 修饰,而不是 final 。

【7】包含抽象方法的枚举类

    需求:定义一个 Operation 枚举类,它有4个枚举值 PLUS,MINUS,TIMES,DIVIDE 分别代表加、减、乘、除 4种运算。该枚举类需要定义一个 eval() 方法来完成计算,且需要让4个枚举值对 eval() 方法各有不同的实现。

    方案1:可以按照上面的方式,先定义一个接口,接口中定义 eval() 方法,再由 Operation 实现该接口,且每个枚举值提供不同的 eval() 实现方式。

public interface OperationEval {
    double eval(double x,double y);
}

public enum Operation implements OperationEval {
    PLUS{
        public double eval(double x,double y){
            return x + y;
        }
    },
    MINUS{
        public double eval(double x,double y){
            return x - y;
        }
    },
    TIMES{
        public double eval(double x,double y){
            return x * y;
        }
    },
    DIVIDE{
        public double eval(double x,double y){
            return x / y;
        }
    };
    public static void main(String[] args){
        System.out.println(Operation.PLUS.eval(4,2));   //6.0
        System.out.println(Operation.MINUS.eval(4,2));  //2.0
        System.out.println(Operation.TIMES.eval(4,2));  //8.0
        System.out.println(Operation.DIVIDE.eval(4,2)); //2.0
    }
}

    但是像上面这样做,还要多定义一个接口,多此一举。

    方案2:可以直接在枚举类中定义一个抽象方法,然后让每个枚举值提供不同的实现方式,可以起到同样的效果。

public enum Operation2 {
    PLUS{
        public double eval(double x,double y){
            return x + y;
        }
    },
    MINUS{
        public double eval(double x,double y){
            return x - y;
        }
    },
    TIMES{
        public double eval(double x,double y){
            return x * y;
        }
    },
    DIVIDE{
        public double eval(double x,double y){
            return x / y;
        }
    };
    public abstract double eval(double x,double y);
    public static void main(String[] args){
        System.out.println(Operation2.PLUS.eval(4,2));   //6.0
        System.out.println(Operation2.MINUS.eval(4,2));  //2.0
        System.out.println(Operation2.TIMES.eval(4,2));  //8.0
        System.out.println(Operation2.DIVIDE.eval(4,2)); //2.0
    }
}

    需要注意的是:枚举类里定义抽象方法时不能使用 abstract 关键字将枚举类定义成抽象类,因为系统会自动为它添加 abstract 修饰。但因为枚举类需要显示创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。

【8】枚举的使用场景

    在开发中,枚举类可以用来定义一组固定的常量,并且可以清楚的展示常量所表示的含义。

    例如,可以定义一个系统登录渠道的枚举类:

public enum LoginChannelEnum {
	ACCOUNT(0,"account","账号登录"),
	EMAIL(1,"email", "邮箱登录"),
	WECHAT(2,"wechat", "微信登录"),
	QQ(3,"qq", "QQ登录");

	private final int code;
	private final String value;
	private final String desc;

	ChannelEnum(int code,String value, String desc) {
		this.code = code;
		this.value = value;
		this.desc = desc;
	}
	
	...
}
  • 35
    点赞
  • 145
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值