java 枚举(网上找的留着温习)

对枚举类型印象大多来自于C 语言,在 C 语言中,枚举类型是一个 HardCode (硬编码)类型,其使用价值并不大。因此,在 Java 5 之前,枚举是被抛弃的。然而 Java 5 以后的发现版本开始对枚举进行支持,枚举的引入给 Java 世界带来了争议。

笔者比较赞同引入枚举,作为一门通用的静态编程语言,应该是海纳百川的(因此笔者赞成闭包进入Java 7 ),多途径实现功能。

如果您不了解枚举的用法,建议参考笔者以前网络资源,了解基本的用法。 地址为: http://mercyblitz.blog.ccidnet.com/blog-htm-do-showone-uid-45914-type-blog-itemid-189396.html

枚举是一种特殊的(受限制的)类,它具有以下特点:

  1. 可 列性
  2. 常 量性
  3. 强 类型
  4. 类 的特性

留下一个问题-怎么利用这些枚举特点,更好为设计服务呢?根据这些特 点,下面向大家分别得介绍设计技巧。

一、 可 列性

在设计中,必须搞清楚枚举 使 用场景 枚 举内部成员都是可列的,或者说固定的。这种硬编码的形式,看上去令人觉得不自在,不过这就是枚举。如果需要动态(不可列)的成员话,请不好使用枚举。

JDK提供不少良好的可列性设计枚举。比如时间单位 java.util.concurrent.TimeUnit 和 线程状态枚举 java.lang.Thread.State

 

假设有一个游戏难度枚举,有三种难度NORMAL , MEDIUM, HARD

Java代码
  1. /** 
  2.  
  3.  * 游戏中的难度枚举:NORMAL , MEDIUM, HARD 
  4.  
  5.  *  
  6.  
  7.  * @author mercyblitz 
  8.  
  9.  */  
  10.   
  11. public enum Difficulty {  
  12.   
  13. NORMAL, MEDIUM, HARD //注意:枚举成员命名,请使用英文大写形式  
  14.   
  15. }  

/**

 * 游戏中的难度枚举:NORMAL , MEDIUM, HARD

 * 

 * @author mercyblitz

 */

public enum Difficulty {

NORMAL, MEDIUM, HARD //注意:枚举成员命名,请使用英文大写形式

}

 

 

如果要添加其他成员,只能通过硬编码的方法添加到枚举类。回到枚举Difficulty,低版 本的必定会影响二进制兼容性。对于静态语言来说,是无法避免的,不能认为是枚举的短处。

 

二、 常 量性

之所以定性为常量性,是因为枚举是不能改变,怎么证明 成员其不变呢?利用上面的Difficulty枚举为例,一段简单的代码得到其原型,如下:

Java代码
  1. package org.mercy.design.enumeration;  
  2.   
  3. import java.lang.reflect.Field;  
  4.   
  5. /** 
  6.  * Difficulty 元 信息 
  7.  * @author mercy 
  8.  */  
  9. public class MetaDifficulty {  
  10.   
  11.     public static void main(String[] args) {  
  12.         MetaDifficulty instance = new MetaDifficulty();  
  13.         // 利用反射连接其成员的特性  
  14.         Class<Difficulty> classDifficulty = Difficulty.class;  
  15.         for (Field field : classDifficulty.getFields()) {  
  16.             instance.printFieldSignature(field);  
  17.             System.out.println();  
  18.         }  
  19.     }  
  20.   
  21.     /** 
  22.      * 打 印字段 签名(signature) 
  23.      *  
  24.      * @param field 
  25.      */  
  26.     private void printFieldSignature(Field field) {  
  27.         StringBuilder message = new StringBuilder("字段  ")  
  28.                                 .append(field.getName())  
  29.                                 .append(" 的签名:")  
  30.                                 .append(field.toString())  
  31.                                 .append("/t");  
  32.         System.out.print(message);  
  33.     }  

	package org.mercy.design.enumeration;
	
	import java.lang.reflect.Field;
	
	/**
	 * Difficulty 元信息
	 * @author mercy
	 */
	public class MetaDifficulty {
	
		public static void main(String[] args) {
			MetaDifficulty instance = new MetaDifficulty();
			// 利用反射连接其成员的特性
			Class<Difficulty> classDifficulty = Difficulty.class;
			for (Field field : classDifficulty.getFields()) {
				instance.printFieldSignature(field);
				System.out.println();
			}
		}
	
		/**
		 * 打印字段 签名(signature)
		 * 
		 * @param field
		 */
		private void printFieldSignature(Field field) {
			StringBuilder message = new StringBuilder("字段  ")
									.append(field.getName())
									.append(" 的签名:")
									.append(field.toString())
									.append("/t");
			System.out.print(message);
		}

}

 

 

printFieldSignature 方 法输出枚举 Difficulty的 字段,其结果为:

字段  NORMAL 的签 名:public static final org.mercy.design.enumeration.Difficulty org.mercy.design.enumeration.Difficulty.NORMAL

字段  MEDIUM 的签 名:public static final org.mercy.design.enumeration.Difficulty org.mercy.design.enumeration.Difficulty.MEDIUM

字段  HARD 的签 名:public static final org.mercy.design.enumeration.Difficulty org.mercy.design.enumeration.Difficulty.HARD

 

这个结果得出了两个结论,其一,每个枚举成员是枚举的字段。其二,每个成员都被 public static final。 凡是 static final  变量都是 Java 中的“常量”,其保存在常量池中。根据其常量性和命名规则,建议全大写命名每个枚举的成员(前面提到)。

常量性提供了数据一致性,不必担心被被其他地方修改,同时保证了线程安全。因此在设计过程中,不必担心线程安全问题。

枚举类型是常量,那么在判断是可以使用== 符号来做比较。可是如果枚举成员能够克隆 (Clone) 的话 , 那么 == 比较会失效,从而一致性不能得到保证。如果按照类的定义方法,考虑枚举的话,那么枚举类是集成了 java.lang.Object 类,因此,它继承了 protected java.lang.Object clone() 方法,也就是说支持 clone ,虽然需要通过反射的手段去调用。 Java 语言规范提到,每个枚举继承了 java.lang.Enum<E> 抽象基类。

提供一段测试代码来验明真伪:

Java代码
  1. /** 
  2.  * 指 定的类是枚举java.lang.Enum<E>的子类吗? 
  3.  *  
  4.  * @param klass 
  5.  * @return 
  6.  */  
  7. private boolean isEnum(Class<?> klass) {  
  8.     Class<?> superClass = klass.getSuperclass();  
  9.     while (true) { // 递归查找  
  10.         if (superClass != null) {  
  11.             if (Enum.class.equals(superClass))  
  12.                 return true;  
  13.         } else {  
  14.             break;  
  15.         }  
  16.         superClass = superClass.getSuperclass();  
  17.     }  
  18.     return false;  
  19. }  

	/**
	 * 指定的类是枚举java.lang.Enum<E>的子类吗?
	 * 
	 * @param klass
	 * @return
	 */
	private boolean isEnum(Class<?> klass) {
		Class<?> superClass = klass.getSuperclass();
		while (true) { // 递归查找
			if (superClass != null) {
				if (Enum.class.equals(superClass))
					return true;
			} else {
				break;
			}
			superClass = superClass.getSuperclass();
		}
		return false;
	}

 

    客户端代码调用: instance.isEnum(Difficulty. class ) ;  结 果返回true ,那么证明了 Difficulty 枚 举继承了java.lang.Enum<E> 抽象基类。

那么java.lang.Enum<E> 有没有覆盖 clone 方法呢?查看一下源代码:

Java代码
  1. /** 
  2.  
  3.      * Throws CloneNotSupportedException.  This guarantees that enums 
  4.  
  5.      * are never cloned, which is necessary to preserve their "singleton" 
  6.  
  7.      * status. 
  8.  
  9.      * 
  10.  
  11.      * @return (never returns) 
  12.  
  13.      */  
  14.   
  15.     protected final Object clone() throws CloneNotSupportedException{  
  16.   
  17. throw new CloneNotSupportedException();  
  18.   
  19. }  

/**

     * Throws CloneNotSupportedException.  This guarantees that enums

     * are never cloned, which is necessary to preserve their "singleton"

     * status.

     *

     * @return (never returns)

     */

    protected final Object clone() throws CloneNotSupportedException{

throw new CloneNotSupportedException();

}

 

 

 

 

很明显,java.lang.Enum<E> 基类 final 定义了 clone 方法,即枚举不支持克隆,并且 Java doc 提到要保持单体性。那么这也是面向对象设计的原则之一 - 对于保持单态性的对象而言,尽可能不支持或者不暴露clone 方法。

 

三、 强 类型

前面的两个特性,在前Java 5 时代,利用了常量字段也可以完成需 要。比如可以这么设计Difficulty类的字段,

 

Java代码
  1. public static final int NORMAL = 1;  
  2.   
  3. public static final int MEDIUM = 2;  
  4.   
  5. public static final int HARD = 3;  

public static final int NORMAL = 1;

public static final int MEDIUM = 2;

public static final int HARD = 3;

 

 

这么设计有一个缺点-弱类型,因为三个字段都是int原生型。

比如有一个方法用于设置游戏难度,定义如下:

Java代码
  1. public void setDifficulty(int difficulty)  

public void setDifficulty(int difficulty)

   

  利用int类型作为参数,可能会有问题-如果参数在NORMAL,MEDIUM,HARD之外int数是可以接受的。利用规约的方法可以避免这个问题,比 如设计范围检查:

Java代码
  1. /** 
  2.  
  3.  * 设置游戏难度 
  4.  
  5.  * @param difficulty 难 度数 
  6.  
  7.  * @throws IllegalArgumentException 
  8.  
  9.  *             如 果参数不是 NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException 
  10.  
  11.  */  
  12.   
  13. public void setDifficulty(int difficulty)  
  14.   
  15.  throws IllegalArgumentException   

/**

 * 设置游戏难度

 * @param difficulty 难度数

 * @throws IllegalArgumentException

 *             如果参数不是 NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException

 */

public void setDifficulty(int difficulty)

 throws IllegalArgumentException 

 

 

在可能实现方法中,通过三次成员比较,这样比较笨拙和憋足。

如果您在使用Java 5以前版本的话,作者提供一个较好的实现方法:

Java代码
  1. package org.mercy.design.enumeration;  
  2.   
  3. import java.util.Collections;  
  4. import java.util.HashSet;  
  5. import java.util.Set;  
  6. /** 
  7.  * Difficulty JDK1.4 实现 Difficulty枚举 
  8.  * @author mercy 
  9.  */  
  10. public class Difficulty14 {  
  11.     //枚举字段  
  12.     public static final int NORMAL = 1;  
  13.     public static final int MEDIUM = 2;  
  14.     public static final int HARD = 3;  
  15.       
  16.     private final static Set difficulties;  
  17.   
  18.     static {  
  19.         // Hash 提供快速查找  
  20.         HashSet difficultySet = new HashSet();  
  21.         difficultySet.add(Integer.valueOf(NORMAL));  
  22.         difficultySet.add(Integer.valueOf(MEDIUM));  
  23.         difficultySet.add(Integer.valueOf(HARD));  
  24.         //利用不变的对象是一个好的设计实践  
  25.         difficulties= Collections.unmodifiableSet(difficultySet);  
  26.     }  
  27.       
  28.     /** 
  29.      * 设 置游戏难度 
  30.      * @param difficulty 难 度数 
  31.      * @throws IllegalArgumentException 
  32.      *             如果参数不是NORMAL、MEDIUM和Hard其中一个,那么报出 IllegalArgumentException 
  33.      */  
  34.     public void setDifficulty(int difficulty)   
  35.     throws IllegalArgumentException {  
  36.         if(!difficulties.contains(Integer.valueOf(difficulty)))  
  37.             throw new IllegalArgumentException("参数错误");  
  38.         //设置难度...  
  39.     }  
  40. }  

package org.mercy.design.enumeration;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
 * Difficulty JDK1.4实现 Difficulty枚举
 * @author mercy
 */
public class Difficulty14 {
	//枚举字段
	public static final int NORMAL = 1;
	public static final int MEDIUM = 2;
	public static final int HARD = 3;
	
	private final static Set difficulties;

	static {
		// Hash 提供快速查找
		HashSet difficultySet = new HashSet();
		difficultySet.add(Integer.valueOf(NORMAL));
		difficultySet.add(Integer.valueOf(MEDIUM));
		difficultySet.add(Integer.valueOf(HARD));
		//利用不变的对象是一个好的设计实践
		difficulties= Collections.unmodifiableSet(difficultySet);
	}
	
	/**
	 * 设置游戏难度
	 * @param difficulty 难度数
	 * @throws IllegalArgumentException
	 *             如果参数不是NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException
	 */
	public void setDifficulty(int difficulty) 
	throws IllegalArgumentException {
		if(!difficulties.contains(Integer.valueOf(difficulty)))
			throw new IllegalArgumentException("参数错误");
		//设置难度...
	}
}

 

 

在上面的代码中,尽管提供了范围检查,不过参数范围还是巨大(可以 说是无数),并且是运行时检查。因为 setDifficulty 的 参数是 int的, 客户端调用时候,编译器可以接受 int 以及范围更小的 shortbyte 等类型。那么违反了 一个良好的实践 -在面向对象设计中,类型范围最好在编译时确定而非运行时。

另一个良好的面向对象实践 -利用对象类型,而不是原生型(如果编程语言支持的话)。 那 么,如果使用java.lang.Integer 取代 int 类型,并且 Integerfinal 类,没有必要担心多态的情况下,不就可以提供强类型吗?的确,提供了强类型约束,并且更好的锁定类型范围(因为是 final 的)。可是, Integer 的范围在一定程度上,认为是无限的,同时不支持 swtich 语句( 仅支持 int shortbyteJava 5  枚举类型)。因此 Integer 还是不合适的。

枚举的常量性和可列性,在Difficulty 场景中尤其适合。

 

四、 类 的特性

已知每个枚举都继承了java.lang.Enun<E> 基类,其既有常量性,同时也有类的特点。尽管它是一种被限制的类,比如name和ordinal字段状态都是有JVM处理的。不过开发人员可以充分的利用 类的特点,作出优美的设计。

枚举既然也是类,那么也遵循类的设计。通过扩张 Difficulty 类, 面向对象的方式来设计枚举。

1.  封 装设计

如果有一个 需求 - Difficulty 持 久化,把其存入 d ifficult ies数 据库表中,并且提供一个唯一的 id 整型值。面向对象的封装,枚举也适用。示例如下:

Java代码
  1. public enum Difficulty {  
  2.     // 注意:枚举成员命名,请使用英文大写形式  
  3.     NORMAL(1), MEDIUM(2), HARD(3);   
  4.     /** 
  5.      * final 修饰字段是一个良好的实践。 
  6.      */  
  7.     final private int id;  
  8.       
  9.     Difficulty(final int id){  
  10.         this.id=id;  
  11.     }  
  12.     /** 
  13.      * 获 得ID 
  14.      * @return 
  15.      */  
  16.     public int getId() {  
  17.         return id;  
  18.     }  
  19. }  

public enum Difficulty {
	// 注意:枚举成员命名,请使用英文大写形式
	NORMAL(1), MEDIUM(2), HARD(3); 
	/**
	 * final修饰字段是一个良好的实践。
	 */
	final private int id;
	
	Difficulty(final int id){
		this.id=id;
	}
	/**
	 * 获得ID
	 * @return
	 */
	public int getId() {
		return id;
	}
}

 

 

通过调用getId 方法可以获取枚举成员的 ID 值。

2.  抽 象设计

在不同的游 戏难度级别中,不同任务的难度值不同(大多数情况是通过值来表示,而非枚举对象本身)。以Difficulty为例,定义一个抽象的方法,计算难度值。

Java代码
  1. /** 
  2.  * 获 取不同任务的难度值 
  3.  *  
  4.  * @param mission 
  5.  * @return 
  6.  * @throws IllegalArgumentException 
  7.  *             如果<code>mission</code> 为负数时。 
  8.  */  
  9. public abstract int getValue(int mission) throws IllegalArgumentException;  

	/**
	 * 获取不同任务的难度值
	 * 
	 * @param mission
	 * @return
	 * @throws IllegalArgumentException
	 *             如果<code>mission</code> 为负数时。
	 */
	public abstract int getValue(int mission) throws IllegalArgumentException;

 

3.  多 态设计

Difficulty 枚举中定义抽象方法getValue,那么其子类必须实现这个方法。不过枚举不能被继承,也不能继承其他类,除了默认额 java.lang.Enum<E>类以外。因此枚举是一个final的版本,不能实现抽象方法?

枚举的特殊 在此,枚举虽然不能显示地利用extends关键字继承,不过它的每个成员都是自己的子类。那么以Difficulty为例,其类层次关系 为:java.lang.Enum<E> -> Difficulty -> HARD。这么看来,每个枚举成员相当于定了一个 final的类内置类。

回到 Difficulty枚举,实现抽象方法如下:

 

 

Java代码
  1. NORMAL(1) {  
  2.         @Override  
  3.         public int getValue(int mission)   
  4.     throws IllegalArgumentException {  
  5.             return mission + this.getId();  
  6.         }  
  7.     },  
  8.     MEDIUM(2) {  
  9.         @Override  
  10.         public int getValue(int mission)   
  11.     throws IllegalArgumentException {  
  12.             return mission * this.getId();  
  13.         }  
  14.     },  
  15.     HARD(3) {  
  16.         @Override  
  17.         public int getValue(int mission)   
  18.     throws IllegalArgumentException {  
  19.             return mission << this.getId();  
  20.         }  
  21.     };  

NORMAL(1) {
		@Override
		public int getValue(int mission) 
	throws IllegalArgumentException {
			return mission + this.getId();
		}
	},
	MEDIUM(2) {
		@Override
		public int getValue(int mission) 
	throws IllegalArgumentException {
			return mission * this.getId();
		}
	},
	HARD(3) {
		@Override
		public int getValue(int mission) 
	throws IllegalArgumentException {
			return mission << this.getId();
		}
	};

 

4.  继 承设计

在上述实现 中,可以观察到一点,每个实现getValue 的方法都利用的 getId() 方法。那么再次说明了枚举类本身也是一个特殊基类,可以定义模板方法。

5.  串 行化设计

在某些设计中,需要把枚举通过串行化。回到 Difficulty14 的 示例中,三个成员变量都是常量,那么 static的 变量是不可能被串行化的。如果去掉 static 修饰,那么语义将会被改变。而枚举不同,所有自定义枚举都是 java.lang.Enum<E> 的 子类,因此所有的枚举都是可序列化的( java.lang.Enum<E>  实 现了java.io.Serializable 。 这也是枚举相对于常量的优势之一。

实现中可以提供类似这样的方法:

Java代码
  1. private void readObject(ObjectInputStream in) throws IOException  
  2.   
  3. private void readObjectNoData() throws ObjectStreamException   

private void readObject(ObjectInputStream in) throws IOException

private void readObjectNoData() throws ObjectStreamException 

 

 

总之,枚举也可以像类那样,实现面向对象的特点,不过值得一提的 是, 枚举中应该保持尽量可能少的状态,职责单一的设计。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值