Java之路:枚举

枚举(Enum)

一、定义

enum 枚举值 { 枚举值表};
// 例如
enum WeekDay { Mon, Tue, Wed, Thu, Fri, Sat,Sun };

其中,enum是Java中的关键字。在枚举值表中应罗列出所有的可用值,这些值也称为枚举元素。

枚举变量也可用不同的方式说明,如先定义后说明、定义的同时说明或直接说明。
设有变量a、b、c被定义为上述的枚举类型WeekDay,可采用下述任意一种方式。

enum WeekDay{Mon,Tue,Wed,Thu,Fri,Sat,Sun};   //先定义
enum WeekDay a,b,c;                          //后说明
// 或者为:
enum WeekDay {Mon,Tue,Wed,Thu,Fri,Sat,Sun}a,b,c; //定义的同时说明
// 或者为:
enum {Mon,Tue,Wed,Thu,Fri,Sat,Sun}a,b,c     //直接说明,即定义无名枚举

1、在Java中使用简单的枚举

enum Color { 红色, 绿色, 蓝色 };
public class EnumColor {

	public static void main(String[] args) {
		Color c1 = Color.红色;
		Color c2;
		c2 = Color.绿色;	// 通过“ 枚举名.枚举值 ”获得枚举值
		System.out.println(c1);
		System.out.println(c2);
	}
}

【结果】
Java之路:枚举

2、在switch语句中使用枚举

enum Color { 红色, 绿色, 蓝色 };
public class SwitchEnum {

	public static void main(String[] args) {
		Color c1 = Color.红色;
		switch(c1) {
			case 红色: System.out.println("我是红色!"); break; 
			case 绿色: System.out.println("我是绿色!"); break; 
			case 蓝色: System.out.println("我是蓝色!"); break; 
		}
	}

}

【结果】
Java之路:枚举
注:由于Java采用的是Unicode的字符串编码方式,所以枚举值也可支持中文。

二、枚举类和枚举关键字

枚举类型的出现,有助于简洁程序的代码量,减少出错量。在大多数情况下,枚举类和枚举关键字是相互依存的。

枚举关键字是定义枚举类型时必不可少的声明,而枚举类则是规定的枚举类型母类。

1、枚举类

枚举类(Enum类)是在Java.lang包下定义的一个公共类,它的作用是用来构造新的枚举类型。

这是在JDK1.5之后Java推出的一个新的类,用来弥补关于枚举这一常用集合在Java中的不足。同时, Enum类中的构造方法可方便对代码的操作。

在Enum类中定义了大约十多个方法,每一种方法都是对用Enum创建的枚举对象实施操作,所以**Enum类是一个完整的类型。**它拥有自己的方法,当创建一个关于Enum的类型对象时,便可调用其中的方法来完善对于枚举类型的操作。

下表列出了Enum类中的主要方法:
Java之路:枚举

enum Color { 红色, 绿色, 蓝色 };
public class ValueOfEnum {
	public static void main(String[] args) {
		/* 在上面的Enum类“方法概要”的表格中,并没有列出来values()方法。
		 * 实际上,这是编译器添加的隐式方法,
		 * 如果一个对象被声明为enum类型,
		 * 编译器会自动给该类型添加一个隐含的方法values(),
		 * 它的原型为:public static E[] values();
		 * 通过values()方法获得枚举类型中各个对象的值
		 */
		Color[] allColor =Color.values();	
		for( Color mc : allColor )
			System.out.println(mc);
	}
}

【结果】
Java之路:枚举

【注】
枚举关键字(enum)是定义的一个枚举类型。 实际上,在此次定义的过程中,通过enum关键字相当于定义了一个类,并且此类将继承自Enum类。

2、枚举关键字

在Enum类中的构造方法中是保护类型的,实际上对于每一个枚举的对象一旦声明之后,就表示自动调用此构造方法,所有的编号方式均采用自动编号的方式进行。

在没有对编号做出特殊声明时,Java虚拟机一般将对被创建的枚举类型对象自动编号,编号从0开始。如下:

enum Color { 红色, 绿色, 蓝色 };
public class OrderOfEnum {
	public static void main(String[] args) {
		Color[] allColor = Color.values();
		for( MyColor mc : allColor )
			print(mc.name() + " --> " + mc.ordinal());
	}
}

【结果】
Java之路:枚举

3、枚举类与枚举关键字之间的关系

实际上,使用enum关键字,相当于定义了一个类,该类将继承自Enum类。

在Enum类中的方法,访问权限都是保护(protected)类型的,因此这些方法都可以在enum关键字定义的对象中直接使用。

在下面的范例中,先用enum关键字定义了一个枚举对象Color,然后调用Enum类中的valueof()方法:

enum Color { 红色, 绿色, 蓝色 };
public class ValueOfEnum {
	public static void main(String[] args) {
		Color c = Color.valueOf(Color.class, "红色");		
		System.out.println(c);
	}
}

【结果】
Java之路:枚举
【代详解】
Color枚举类型调用在Enum类中方法valueof(),这是一个静态方法,用于返回指定枚举类中指定名称的枚举值。
在本例中,方法valueof()的第一个参数“Color.class”,用来返回Color的类型——枚举类型,第二个参数是字符串常量“红色”。

4、使用枚举类的注意事项

(1) 如果enum定义的枚举类访问权限定义为public,则需要单独形成一个.java文件,即不可与包含main方法的public类同处于同一个文件。如果enum定义的枚举类访问权限为默认类型,即enum关键字前没有修饰符,则enum定义的对象可在同一个包里访问,无需重复定义。

若要在其他包中访枚举类Color,可用如下方法:

package color;	// 在color包中定义Color
public enum Color { 红色, 绿色, 蓝色 }

// 在另一个包中访问Color
package enum_color;
import color.Color;	// 用import关键字导入Color即可
public class EnumColor2 {
	public static void main(String[] args) {
		System.out.println(Color.红色);
	}
}

(2) 使用enum定义的枚举类,默认继承于java.lang.Enum类。使用enum定义的枚举类,默认会使用final修饰,因此该类无法派生子类。

(3) 使用enum定义的枚举类,其所有的枚举值(实际上是该类的实例)必须在枚举类的第一行显示列出,否则这个枚举类将永远不能产生实例。在列出这些实例(即枚举值)时,**系统会自动添加public static final修饰。**也即是说,所有枚举值都是public static final类型的。

(4) 所有使用enum定义的枚举类,都会由系统隐式提供一个values()方法,该方法可方便地遍历所有的枚举值。

三、深入了解枚举

1、枚举的构造方法

枚举的使用非常灵活,它可应用于代码中的各个角落,只要定义的对象具有枚举的形式,均可使用枚举对其进行定义。这样在减少代码量的同时,也可增加代码的可读性和可操作性。

enum NewColor{
	/* 
	 * 定义三个枚举值RED GREEN BLUE,
	 * 实际上,这三个枚举值 是NewColor枚举类的三个实例化对象
	 * 注意:枚举类定义的对象必须出现 在该类有效代码的第一行,
	 * 否则,编无法通过
	 * 一旦枚举类的构造方法定义之后,
	 * 那么所有的枚举对象都必须显示调用此构造方法,例如:RED("红色",4)
	 * NewColor定义的三个对象
	 */
	RED("红色",4),	// 调用构造方法NewColor(String name, int index)
					// 注意 后面是逗号	
	GREEN("绿色",5),	// 注意 后面是逗号
	BLUE("蓝色",6); 	// 注意 后面是分号
	
	// 成员变量
	private String name; 
	private int index;
	    
	// NewColor类私有构造方法,	外部无法调用枚举构造方法  
	private NewColor(String name,int index) {         
		this.name=name;
		this.index=index;
	}
	// 普通方法,通过索引获得其对应的名称
	public static String getName(int index) {      
		for( NewColor c : NewColor.values() ) {
			if(c.getIndex()==index) { return c.name; }
	    }
		return null;
	}
	public String getName() { return name; }
	// 普通方法,通过索引设置其对应的名称
	public static void setName(int index,String name) {
		for( NewColor c : NewColor.values() ) {
			if(c.getIndex() == index) {
				c.name=name;
				return;
	        }
	    }
	}
	public int getIndex() { return index;  }
	// 普通方法,通过名字设置其对应的标号
	public static void setIndex(int index, String name) {
		for( NewColor c : NewColor.values() ) {
			if(c.getName() == name) {
				c.index=index;
				return;
	        }
	    }
	}
}

public class EnumConstructor {
	public static void main(String[] args) {
		
		System.out.println("------------输出枚举中的元素----------");
		System.out.println(NewColor.RED.getIndex()+"---->"+NewColor.RED.getName());
		System.out.println(NewColor.GREEN.getIndex()+"---->"+NewColor.GREEN.getName());
		System.out.println(NewColor.BLUE.getIndex()+"---->"+NewColor.BLUE.getName());

		System.out.println("------在自定义编号和属性值之后,测试-------");
		NewColor.setName(4,"黑色");          //重新设置名称,将4号设为黑色
		System.out.println("4---->"+NewColor.getName(4));
		NewColor.setIndex(7,"黑色");          //重新设置索引编号,将黑色设为7号
		System.out.println("7---->"+NewColor.getName(7));
    }
}

【结果】
Java之路:枚举
注:枚举类定义的对象必须出现 在该类有效代码的第一行,否则,编无法通过。一旦枚举类的构造方法定义之后,那么所有的枚举对象都必须显示调用此构造方法。 例如:RED(“红色”,4)。

2、枚举的接口

在前文中,我们已经提到,所有的枚举事实上都继承自java.lang.Enum类。

这表明枚举已有了一个“父类”,那它不能再继承其他类,那么是不是枚举就“注定孤独地”使用继承自Enum类中的方法呢?

Java语言的设计者是聪明的,他们为用户“关闭了一扇门”,但又为“开启了另外一扇窗”,那这扇窗就是Java中的一个重要概念——接口(Interface)。

在Java的一个类中,既包括数据成员(即属性),又包括对这些属性的操作(即方法)。

而在Java的一个接口中,在某种程度上,它可视为一个简化版本的“类”,其仅包括一系列方法和一些不可更改的静态常量。

Java中的接口不能直接拿来实例化任何对象,因为在其内仅仅提供了方法的声明,即只有方法的特征而没有方法的具体实现,它不具备实例化对象的条件。换句话说,接口“生来”就是要被继承的,在继承中被具体化实现。

接口中的这些方法可在不同的地方被不同的类实现,从而可表现出具有不同的“个性化”行为(功能)。

Java语言中的接口,只是对要实现该接口方法的所有类提出了一个共享的固定格式的协议(protocol)。这些协议固定了在其内的静态常量和方法签名(方法名+参数列表)形式,而继承这个接口的类,就可在其类中对这些继承而来的方法,“独立自主、*发挥”地实现这些方法。

Java语言虽然不支持一个类有多个直接的父类(即不支持多继承),但一个类却可以实现(implements)多个接口。

接口弥补了Java类不能多继承缺点,单继承和多接口的双重设计既保持了类的数据安全,也间接实现了多继承。Java的“另外一扇窗””就在这里开启。

枚举接口的实现:

package enum_interface;
// 定义一个接口,注意 public 类型必须在它自己的包中定义
public interface ColorInterface {	
	public String getColor();
}

package newcolor;
// 实现枚举接口,注意 public 类型必须在它自己的包中定义
public enum NewColor implements ColorInterface {
	红色
	{
		public String getColor() {
			return "RED";
		}
	},	// 注意,每个枚举值之间是用逗号“,”分隔开的,最后一个才用“;”
	
	绿色
	{
		public String getColor() {
			return "GREEN";
		}
	},
	
	蓝色
	{
		public String getColor() {
			return "BLUE";
		}
	};	// 最后一个枚举值用分号“;”结尾
}

package testcolor;
// 测试枚举接口,注意 public 类型必须在它自己的包中定义
public class TestColor {
	public static void main(String[] args) {
		for(NewColor nc : NewColor.values())
			System.out.println(nc.ordinal() + " --> " + 
								nc.name() + " : " + 
								nc.getColor());
	}
}

【结果】
Java之路:枚举

3、在枚举中的定义抽象方法

在接口中定义的方法,由于只有方法原型,没有具体实现,这些方法可视为是“抽象的”方法,不管用不用修饰符abstract都是一样。

实际上,Java也可以在枚举中直接定义一个或多个抽象方法。 需要注意的是,这种情况下,需要为枚举中的每个对象单独地实现此方法。

enum NewColor3 {

	红色	// 每个枚举对象都需要实现所定义的抽象方法
	{
		public String getColor() {
			return "RED";
		}
	},
	
	绿色
	{
		public String getColor() {
			return "GREEN";
		}
	},
	
	蓝色
	{
		public String getColor() {
			return "BLUE";
		}
	};
	/* 定义抽象方法,
	 * 注意,之所以把抽象方法的声明放在这里,
	 * 是因为,枚举类定义的对象必须出现 在该类有效代码的第一行,
	 * 否则,编无法通过。
	 * 也即,如果把声明放在“红色”前面,会扬子报错
	 */
	public abstract String getColor();	
}

public class AbstractEnum {
	public static void main(String[] args) {
		for(NewColor3 nc : NewColor3.values()) {
			print(nc.ordinal() + " --> " + ": " + nc.getColor());
		}
	}
}

【结果】
Java之路:枚举

注:在Java编程实现上,所谓抽象方法,就是用关键字abstract修饰且没有实现主体的方法。

四、使用枚举类的注意事项

1、枚举类型不能用 public 和 protected 修饰符修饰构造方法。构造方法的权限只能是private或者friendly,friendly是当没有修饰符时的默认权限。

因为枚举的这种特性,所以枚举对象是无法在程序中通过直接调用其构造方法来初始化的。

public enum Color {
	//Color定义的三个对象
	RED("红色",4),
	GREEN("绿色",5),
	BLUE("蓝色",6); 
	
	//成员变量
	private String name; 
	private int index;
	    
	//构造方法  
	/*
	 * 构造方法Color只能是 private 或 friendly
	 * 如果是 public 或 protected 会报错 
	 */
	private Color(String name,int index) {         
		this.name=name;
		this.index=index;
	}
}

2、定义枚举类型时,如果是简单类型,那么最后一个枚举值后可以不加分号。 如:

enum Color { 红色, 绿色, 蓝色 };

但是如果枚举中包含有方法,那么最后一个枚举值后面代码必须要用分号“;”隔开。 如:

package newcolor;
public enum NewColor implements ColorInterface {
	红色
	{
		public String getColor() {
			return "RED";
		}
	},	// 注意,每个枚举值之间是用逗号“,”分隔开的,最后一个才用“;”
	
	绿色
	{
		public String getColor() {
			return "GREEN";
		}
	},
	
	蓝色
	{
		public String getColor() {
			return "BLUE";
		}
	};	// 最后一个枚举值用分号“;”结尾
}

3、枚举类不可以被继承

在枚举类内部可以定义一个或多个抽象方法时,那这个枚举类为什么不能用abstract修饰呢?

如果一个枚举类用abstract修饰,那么就说明需要其他类继承这个所谓的“抽象枚举类”,而在Java中,规定枚举是不能被继承的。

因此,枚举不能用abstract修饰,只能在每个枚举的实例实现每个抽象方法。