枚举类型(2):实现,而非继承、随机选取、使用接口组织枚举

一、实现,而非继承

    我们已经知道,所有的enum都继承自java.lang.Enum类。由于java不支持多重继承,所以你的enum不能再继承其他类:

enum NotPossible extends Pet{ }

    然而,在我们创建一个新的enum时,可以同时实现一个或多个接口:

package enumerated.cartoons;

import java.util.Random;

import com.buba.util.Generator;

enum CartoonCharacter implements Generator {
	SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;

	Random r = new Random();

	@Override
	public CartoonCharacter next() {
		return values()[r.nextInt(values().length)];
	}

}

public class EnumImplementation {
	public static  void printNext(Generator rg) {
		System.out.print(rg.next() + ", ");
	}

	public static void main(String[] args) {
		CartoonCharacter cc = CartoonCharacter.BOB;
		for (int i = 0; i < 10; i++) {
			printNext(cc);
		}
	}
}

    这个结果有点奇怪,不过你必须要有一个enum实例才能调用其上的方法。现在,在任何接受Generator参数的方法中,例如printNext(),都可以使用CartoonCharacter。

二、随机选取

    就像你在CartoonCharacter.next()中看到的那样,本章中的很多示例都需要从enum实例中进行随机选择。我们可以利用泛型,从而使得这个工作更一般化,并将其加入到我们的工具库中。

package com.buba.util;

import java.util.Random;

public class Enums {
	private static Random r = new Random();

	public static > T random(Class ec) {
		return random(ec.getEnumConstants());
	}

	public static  T random(T[] values) {
		return values[r.nextInt(values.length)];
	}
}

    古怪的语法>表示T是一个enum实例。而将Class作为参数的话,我们就可以利用Class对象得到enum实例的数组了。重载后,random()方法只需使用T[]作为参数,因为它并不会调用Enum上的任何操作,它只需从数组中随机选择一个元素即可。这样,最终返回类型正是enum的类型。

    下面是random()方法的一个简单示例:

package enumerated;

import com.buba.util.Enums;

enum Activity {
	SITTING, LYING, STANDING, HOPPING, RUNNING, DODGING, JUMPING, FALLING, FLYING
}

public class RandomTest {
	public static void main(String[] args) {
		for (int i = 0; i < 20; i++) {
			System.out.print(Enums.random(Activity.class) + " ");
		}
	}
}

    虽然Enum只是一个相当短小的类,但是在本章中你会发现,它能消除很多重复的代码。重复总会制造麻烦,因此消除重复总是有益处的。

三、使用接口组织枚举

    无法从enum继承子类有时很令人沮丧。这种需求有时源自我们希望扩展原enum中的元素,有时是因为我们希望使用子类将一个enum中的元素进行分组。

    在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。举例来说,假设你想用enum来表示不同类别的食物,同时还希望每个enum元素仍然保持Food类型。那可以这样实现:

package enumerated.menu;

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而言,实现接口是使其子类化的唯一办法,所以嵌入在Food中的每个enum都实现了Food接口。现在,在下面的程序中,我们可以说“所有东西都是某种类型的Food”:

package enumerated.menu;

import enumerated.menu.Food.Appetizer;
import enumerated.menu.Food.Coffee;
import enumerated.menu.Food.Dessert;
import enumerated.menu.Food.MainCourse;

public class TypeOfFood {
	public static void main(String[] args) {
		Food food = Appetizer.SALAD;
		food = MainCourse.LASAGNE;
		food = Dessert.GELATO;
		food = Coffee.CAPPUCCINO;
	}
}

    如果enum类型实现了Food接口,那么我们就可以将其实例向上转型为Food,所以上例中的所有东西都是Food。

    然而,当你需要与一大堆类型打交道时,接口就不如enum好用了。例如,如果你想创建一个“枚举的枚举”,那么可以创建一个新的enum,然后用其实例包装Food中的每一个enum类:

package enumerated.menu;

import com.buba.util.Enums;

public enum Course {
	APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class),
	COFFEE(Food.Coffee.class);

	private Food[] values;

	private Course(Class kind) {
		values = kind.getEnumConstants();
	}

	public Food randomSelection() {
		return Enums.random(values);
	}
}

    在上面的程序中,每一个Course的实例都将其对应的Class对象作为构造器的参数。通过getEnumConstants()方法,可以从该Class对象中取得某个Food子类的所有enum实例。这些实例在randomSelection()中被用到。因此,通过从每一个Course实例中随机地选择一个Food,我们便能够生成一份菜单:

package enumerated.menu;

public class Meal {
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			for (Course course : Course.values()) {
				Food food = course.randomSelection();
				System.out.println(food);
			}
			System.out.println("-----------");
		}
	}
}

    在这里例子中,我们通过遍历每一个Course实例来获得“枚举的枚举”的值。稍后,在VendingMachine.java中,我们看到另一种组织枚举实例的方式,但其也有一些其他的限制。

    此外,还有一种更简洁的管理枚举的办法,就是将一个enum嵌套在另一个enum内。就像这样:

package enumerated;

import com.buba.util.Enums;

public enum SecurityCategory {
	STOCK(Security.Stock.class), BOND(Security.Bond.class);

	Security[] values;

	SecurityCategory(Class kind) {
		values = kind.getEnumConstants();
	}

	interface Security {
		enum Stock implements Security {
			SHORT, LONG, MARGIN
		}

		enum Bond implements Security {
			MUNICIPAL, JUNK
		}
	}

	public Security randomSelection() {
		return Enums.random(values);
	}

	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			SecurityCategory category = Enums.random(SecurityCategory.class);
			System.out.println(category + ": " + category.randomSelection());
		}
	}
}

    Security接口的作用是将其所包含的enum组合成一个公共类型,这一点是有必要的。然后SecurityCategory才能将Security才能将Security中的enum作为其构造器的参数使用,以起到组织的效果。

    如果我们将这种方式应用于Food的例子,结果应该这样:

package enumerated.menu;

import com.buba.util.Enums;

public enum Meal2 {
	APPETIZER(Food.Appetizer.class), MAINCOURSE(Food.MainCourse.class), DESSERT(Food.Dessert.class),
	COFFEE(Food.Coffee.class);

	private Food[] values;

	private Meal2(Class kind) {
		values = kind.getEnumConstants();
	}

	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;
		}
	}

	public Food randomSelection() {
		return Enums.random(values);
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			for (Meal2 meal : Meal2.values()) {
				Food food = meal.randomSelection();
				System.out.println(food);
			}
			System.out.println("-------------");
		}
	}
}

    其实,这仅仅是重新组织了一下代码,不过多数情况下,这种方式使你的代码具有更清晰的结构。

如果本文对您有很大的帮助,还请点赞关注一下。

你可能感兴趣的:(JAVA)