Thking in java(第四版)-查缺补漏(第19章)

背景

继续查缺补漏,加油

1.基本enum特性

(1)下面代码演示的是enum的一些基本用法:

enum Shrubbery{	GROUND,CRAWLING,HANGING}
public class EnumClass {
	public static void main(String[] args){
		for(Shrubbery s:Shrubbery.values()){
			print(s+" ordinal: "+s.ordinal());
			printnb(s.compareTo(Shrubbery.CRAWLING)+" ");
			printnb(s.equals(Shrubbery.CRAWLING)+" ");
			print(s==Shrubbery.CRAWLING);
			print(s.getDeclaringClass());
			print(s.name());
			print("-------------------------------");
		}
		//Produce an enum value from a string name:
		for(String s:"HANGING CRAWLING GROUND".split(" ")){
			Shrubbery shrub=Enum.valueOf(Shrubbery.class, s);
			print(shrub);
		}
	}

values()返回enum实例数组;ordinal()返回一个int值,是每个enum实例在声明时的次序,从0开始;

getDeclaringClass()返回实例所属的enum类;name()返回enum实例声明时的名字;valueOf()根据

给定的名字返回相应的enum实例。

(2)静态导入enum

public enum Spiciness {
	NOT,MILD,MEDIUM,HOT,FLAMING
}
---------------------------------
import static enumerated.Spiciness.*;
import static tools.Print.*;
public class Burrito {
	Spiciness degree;
	public Burrito(Spiciness degree){	this.degree=degree;}
	public String toString(){	return "Burrito is "+degree;}
	public static void main(String[] args){
		print(new Burrito(NOT));
		print(new Burrito(MEDIUM));
		print(new Burrito(HOT));
	}
}

好处是不需要再用enum类型来修饰enum实例。如果在默认包中定义enum,这种技巧无法使用。

2.向enum添加方法

package enumerated;
import static tools.Print.*;
public enum OzWitch {
	//Instances must be defined first,before methods:
	WEST("Miss Gulch,aka the Wicked Witch of the West"),
	NORTH("Glinda,the Good Witch of the North"),
	EAST("Wicked Witch of the East,wearer of the Ruby "+
			"Slippers,crushed by Dorothy's house"),
	SOUTH("Good by inference, but missing");
	private String description;
	//Constructor must be package or private access:
	private OzWitch(String description){
		this.description=description;
	}
	public String getDescription(){	return description;}
	public static void main(String[] args){
		for(OzWitch witch:OzWitch.values())
			print(witch+": "+witch.getDescription());
	}
}

 如果要定义自己的方法,必须在enum实例序列的最后添加分号,而且enum实例必须先定。

 我们也可以像一般的类那样覆盖enum的方法。

3.switch语句中的enum

枚举的实例的次序是固定的,通过ordinal()方法可以获取次序,所以这个次序的值就可以用在switch。

编译器已经帮我们简化了这个操作。

enum Signal {
	GREEN,YELLOW,RED
}
Signal color=Signal.RED;
public void change(){
		switch(color){
		case RED:	color=GREEN;
					break;
		case GREEN:	color=YELLOW;
					break;
		case YELLOW:    color=RED;
					break;
		}
	}

4.values()的神秘之处

values是由编译器添加的static方法,还添加了valueOf方法。如果将enum实例向上转型为Enum,values方法

就不可访问了。使用Class中的getEnumConstants方法,就可以获得所有enum实例。

package enumerated;
enum Search{ HITHER,YON }

public class UpcastEnum {
	public static void main(String[] args){
		Search[] vals=Search.values();
		Enum e=Search.HITHER;	//Upcast
		//e.values(); //No values() in Enum
		for(Enum en:e.getClass().getEnumConstants())
			System.out.println(en);
		
	}
}

5.实现接口

enum CartoonCharacter implements Generator<CartoonCharacter>{
	SLAPPY,SPANKY,PUNCHY,SILLY,BOUNCY,NUTTY,BOB;
	private Random rand=new Random(47);
	public CartoonCharacter next(){
		return values()[rand.nextInt(values().length)];
	}
}

6.随机选取

package tools;
import java.util.*;
public class Enums {
	private static Random rand=new Random(47) ;
	public static <T extends Enum<T>> T random(Class<T> ec){
		return random(ec.getEnumConstants());
	}
	public static <T> T random(T[] values){
		return values[rand.nextInt(values.length)];
	}
}

7.使用接口组织枚举

package enumerated;

public interface Food {
	enum Appetizer implements Food{
		SALAD,SOUP,SPRING_ROLLS;
	}
	enum MainCourse implements Food{
		LASAGNE,BURRITO,PAD_THAI,
		LENTILS,HUMMOUS,VINDALOO;
	}
	enum Dessert implements Food{
		TIRAMISU,GELATO,BLACK_FOREST_CAKE,
		FRUIT,CREME_CARAMEL;
	}
	enum Coffee implements Food{
		BLACK_COFFEE,DECAF_COFFEE,ESPRESSO,
		LATTE,CAPPUCCINO,TEA,HERB_TEA;
	}
	enum Oypj implements Food{
		HEELO,WORLD
	}
}

这样做的好处是可以将enum中的元素进行分组,还可以保持相同类型。

将enum嵌套在另一个enum内:

package enumerated;
import tools.*;
public enum SecurityCategory {
	STOCK(Security.Stock.class) ,BOND(Security.Bond.class);
	Security[] values;
	SecurityCategory(Class<? extends Security> kind){
		values=kind.getEnumConstants();
	}
}

8.使用EnumSet替代标志

替代传统的基于int的“位标志”。这种标志可以用来表示某种“开/关”信息。我们最终操作的是一些bit。

就其内部而言,它(可能)就是将一个long值作为比特向量。优点是快速高效

public enum AlarmPoints {
	STAIR1,STAIR2,LOBBY,OFFICE1,OFFICE2,OFFICE3,OFFICE4,BATHROOM,UTILITY,KITCHEN
}
------------------------------------------------------------
EnumSet<AlarmPoints> points=EnumSet.noneOf(AlarmPoints.class);//Empty set
points.add(BATHROOM);
print(points);

向EnumSet添加enum实例的顺序并不重要,因为其输出的次序决定于enum实例定义时的次序 

9.使用EnumMap

要求键必须是enum,EnumMap内部可以由数组来实现,所以速度很快。

命令设计模式的用法:命令模式首先需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类。

package enumerated;
import java.util.*;
import static enumerated.AlarmPoints.*;
import static tools.Print.*;
interface Command{	void action();}
public class EnumMaps {
	public static void main(String[] args){
		EnumMap<AlarmPoints,Command> em=new EnumMap<AlarmPoints,Command>(AlarmPoints.class);
		em.put(KITCHEN,new Command(){	
			public void action(){	print("Kitchen fire!");}
		});
		for(Map.Entry<AlarmPoints,Command> e:em.entrySet()){
			printnb(e.getKey()+": ");
			e.getValue().action();
		}
	}
}

与常量相关的方法相比,EnumMap允许改变值对象,而常量相关的方法在编译期就被固定了。

10.常量相关方法

enum的特性之一是允许为enum实例编写方法。

要实现常量相关的方法,需要为enum定义一个或多个abstract方法,然后为每个enum实例实现该抽象

方法。

public enum ConstantSpecificMethod {
	DATE_TIME{
		String getInfo(){
			return DateFormat.getDateInstance().format(new Date());
		}
	},
	VERSION{
		String getInfo(){
			return System.getProperty("java.version");
		}
	};
	abstract String getInfo();
	
}

通过enum实例,我们可以调用其上的方法,这通常称为表驱动的代码。

通过常量相关的方法,每个enum实例可以具备自己独特的行为。

(1)使用enum的责任链:在责任链设计模式中,以多种不同的方式解决一个问题,然后将它们链接在一起。当

一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。

(2)使用enum的状态机:一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到下一个

状态,不过也可能存在瞬时状态,而一旦任务执行结束,状态机就会立刻离开瞬时状态。例如:自动售货机

11.多路分发

Java只支持单路分发。也就是说,如果要执行的操作包含了不止一个类型未知的对象时,那么Java的动态绑定

机制只能处理其中的一个类型。所以必须自己来判定其他的类型,从而实现自己的动态绑定行为。

解决的办法是多路分发。如果使用两路分发,那就必须有两个方法调用:第一个方法调用决定第一个未知类型,

第二个方法调用决定第二个未知类型。一般而言,我们需要设定好某种配置,使得一个方法调用能够引出更多的

方法调用,从而能够在这个过程中处理多种类型。

例如:

public enum Outcome {
	WIN,LOSE,DRAW
}
-------------------------------------------------------
package enumerated;
import java.util.*;
import static enumerated.Outcome.*;
interface Item{
	Outcome compete(Item it);
	Outcome eval(Paper p);
	Outcome eval(Scissors s);
	Outcome eval(Rock r);
}
//布
class Paper implements Item{
	public Outcome compete(Item it){	return it.eval(this);}
	public Outcome eval(Paper p){	return DRAW;}
	public Outcome eval(Rock r){	return LOSE;}
	public Outcome eval(Scissors s){    return WIN;}
    public String toString(){	return "Paper";}
}
//剪刀
class Scissors implements Item{
	public Outcome compete(Item it){	return it.eval(this);}
	public Outcome eval(Paper p){	return LOSE;}
	public Outcome eval(Scissors s){	return DRAW;}
	public Outcome eval(Rock r){	return LOSE;}
	public String toString(){	return "Scissors";}

}
//石头
class Rock implements Item{
	public Outcome compete(Item it){	return it.eval(this);}
	public Outcome eval(Paper p){	return WIN;}
	public Outcome eval(Scissors s){	return LOSE;}
	public Outcome eval(Rock r){	return DRAW;}
	public String toString(){	return "Rock";}
}
public class RoShamBo1 {
	static final int SIZE=20;
	private static Random rand=new Random(47);
	public static Item newItem(){
		switch(rand.nextInt(3)){
			default:
			case 0:return new Scissors();
			case 1:return new Paper();
			case 2:return new Rock();
		}
	}
	public static void match(Item a,Item b){
		System.out.println(a+" vs. "+b+": "+a.compete(b));
	}
	public static void main(String[] args){
		for(int i=0;i<SIZE;i++)
			match(newItem(),newItem());
	}
}

通过调用Item.compete()方法开始两路分发,先判定a的类型然后在调用compete方法的内部再判定b的类型,使用this

可以保存a的类型信息。

(1)使用enum分发

package enumerated;
import tools.*;
public class RoShamBo {
	public static <T extends Competitor<T>> void match(T a,T b){
		System.out.println(a+" vs. "+b+": "+a.compete(b));
	}
	public static <T extends Enum<T>&Competitor<T>> void  play(Class<T> rsbClass,int size){
		for(int i=0;i<size;i++)
			match(Enums.random(rsbClass),Enums.random(rsbClass));
	}
}

-------------------------------------------------------
package enumerated;
import static enumerated.Outcome.*;

public enum RoShamBo2 implements Competitor<RoShamBo2> {
	PAPER(DRAW,LOSE,WIN),
	SCISSORS(WIN,DRAW,LOSE),
	ROCK(LOSE,WIN,DRAW);
	private Outcome vPAPER,vSCISSORS,vROCK;
	RoShamBo2(Outcome paper,Outcome scissors,Outcome rock){
		this.vPAPER=paper;
		this.vSCISSORS=scissors;
		this.vROCK=rock;
	}
	public Outcome compete(RoShamBo2 it){
		switch(it){
			default:
			case PAPER:return vPAPER;
			case SCISSORS:return vSCISSORS;
			case ROCK:return vROCK;
		}
	}
	public static void main(String[] args){
		RoShamBo.play(RoShamBo2.class,20);
	}
}

第一次分发是compete方法的调用,第二次分发是switch的调用。

(2)使用常量相关的方法

package enumerated;
import static enumerated.Outcome.*;

public enum RoShamBo3 implements Competitor<RoShamBo3> {
	PAPER{
		public Outcome compete(RoShamBo3 it){
			switch(it){
				default: //To placate the compiler
				case PAPER:return DRAW;
				case SCISSORS:return LOSE;
				case ROCK:return WIN;
			}
		}
	},
	SCISSORS{
		public Outcome compete(RoShamBo3 it){
			switch(it){
				default: //To placate the compiler
				case PAPER:return WIN;
				case SCISSORS:return DRAW;
				case ROCK:return LOSE;
			}
		}
	},
	ROCK{
		public Outcome compete(RoShamBo3 it){
			switch(it){
				default: //To placate the compiler
				case PAPER:return LOSE;
				case SCISSORS:return WIN;
				case ROCK:return DRAW;
			}
		}
	};
	public abstract Outcome compete(RoShamBo3 it);
	public static void main(String[] args){
		RoShamBo.play(RoShamBo3.class, 20);
	}
}

再简化一下:

package enumerated;

public enum RoShamBo4 implements Competitor<RoShamBo4>{
	ROCK{
		public Outcome compete(RoShamBo4 opponent){
			return compete(SCISSORS,opponent);
		}
	},
	SCISSORS{
		public Outcome compete(RoShamBo4 opponent){
			return compete(PAPER,opponent);
		}
	},
	PAPER{
		public Outcome compete(RoShamBo4 opponent){
			return compete(ROCK,opponent);
		}
	};
	Outcome compete(RoShamBo4 loser,RoShamBo4 opponent){
		return ((opponent==this)?Outcome.DRAW:((opponent==loser)?Outcome.WIN:Outcome.LOSE));
	}
	public static void main(String[] args){
		RoShamBo.play(RoShamBo4.class, 20);
	}
}

(3)使用EnumMap分发

package enumerated;
import java.util.*;
import static enumerated.Outcome.*;
public enum RoShamBo5 implements Competitor<RoShamBo5>{
	PAPER,SCISSORS,ROCK;
	static EnumMap<RoShamBo5,EnumMap<RoShamBo5,Outcome>>
		table=new EnumMap<RoShamBo5,EnumMap<RoShamBo5,Outcome>>(RoShamBo5.class);
	static{
		for(RoShamBo5 it:RoShamBo5.values())
			table.put(it,new EnumMap<RoShamBo5,Outcome>(RoShamBo5.class));
		initRow(PAPER,DRAW,LOSE,WIN);
		initRow(SCISSORS,WIN,DRAW,LOSE);
		initRow(ROCK,LOSE,WIN,DRAW);
	}
	static void initRow(RoShamBo5 it,Outcome vPAPER,Outcome vSCISSORS,Outcome vROCK){
		EnumMap<RoShamBo5,Outcome> row=RoShamBo5.table.get(it);
		row.put(RoShamBo5.PAPER,vPAPER);
		row.put(RoShamBo5.SCISSORS,vSCISSORS);
		row.put(RoShamBo5.ROCK,vROCK);
	}
	public Outcome compete(RoShamBo5 it){
		return table.get(this).get(it);
	}
	public static void main(String[] args){
		RoShamBo.play(RoShamBo5.class,20);
	}
}

(4)使用二维数组

package enumerated;
import static enumerated.Outcome.*;
public enum RoShamBo6 implements Competitor<RoShamBo6> {
	PAPER,SCISSORS,ROCK;
	private static Outcome[][] table={
		{DRAW,LOSE,WIN},
		{WIN,DRAW,LOSE},
		{LOSE,WIN,DRAW},
	};
	public Outcome compete(RoShamBo6 other){
		return table[this.ordinal()][other.ordinal()];
	}
	public static void main(String[] args){
		RoShamBo.play(RoShamBo6.class, 20);
	}
}

总结

最大的收获是学习了几个设计模式还有enum的各种用法。

猜你喜欢

转载自blog.csdn.net/a614528195/article/details/82501995