Java 枚举类
转载自:Enum(枚举类)
一、什么情况下使用枚举类?
有的时候一个类的对象是有限且固定的,这种情况下我们使用枚举类就比较方便?
二、为什么不用静态常量来替代枚举类呢?
public static final int SEASON_SPRING = 1; public static final int SEASON_SUMMER = 2; public static final int SEASON_FALL = 3; public static final int SEASON_WINTER = 4;
枚举类更加直观,类型安全。使用常量会有以下几个缺陷:
1. 类型不安全。若一个方法中要求传入季节这个参数,用常量的话,形参就是int类型,开发者传入任意类型的int类型值就行,但是如果是枚举类型的话,就只能传入枚举类中包含的对象。
2. 没有命名空间。开发者要在命名的时候以SEASON_开头,这样另外一个开发者再看这段代码的时候,才知道这四个常量分别代表季节。
三、枚举类入门
先看一个简单的枚举类。
package enumcase; public enum SeasonEnum { SPRING,SUMMER,FALL,WINTER; }
- enum和class、interface的地位一样
- 使用enum定义的枚举类默认继承了java.lang.Enum,而不是继承Object类。枚举类可以实现一个或多个接口。
- 枚举类的所有实例都必须放在第一行展示,不需使用new 关键字,不需显式调用构造器。自动添加public static final修饰。
- 使用enum定义、非抽象的枚举类默认使用final修饰,不可以被继承。
- 枚举类的构造器只能是私有的。
四、枚举类介绍
枚举类内也可以定义属性和方法,可是是静态的和非静态的。
package enumcase; public enum SeasonEnum { SPRING("春天"),SUMMER("夏天"),FALL("秋天"),WINTER("冬天"); private final String name; private SeasonEnum(String name) { this.name = name; } public String getName() { return name; } }
实际上在第一行写枚举类实例的时候,默认是调用了构造器的,所以此处需要传入参数,因为没有显式申明无参构造器,只能调用有参数的构造器。
构造器需定义成私有的,这样就不能在别处申明此类的对象了。枚举类通常应该设计成不可变类,它的Field不应该被改变,这样会更安全,而且代码更加简洁。所以我们将Field用private final修饰。
五、枚举类实现接口
枚举类可以实现一个或多个接口。与普通类一样,实现接口的时候需要实现接口中定义的所有方法,若没有完全实现,那这个枚举类就是抽象的,只是不需显式加上abstract修饰,系统化会默认加上。
package enumcase; public enum Operation { PLUS{ @Override public double eval(double x, double y) { return x + y; } }, MINUS{ @Override public double eval(double x, double y) { return x - y; } }, TIMES{ @Override public double eval(double x, double y) { return x * y; } }, DIVIDE{ @Override public double eval(double x, double y) { return x / y; } }; /** * 抽象方法,由不同的枚举值提供不同的实现。 * @param x * @param y * @return */ public abstract double eval(double x, double y); public static void main(String[] args) { System.out.println(Operation.PLUS.eval(10, 2)); System.out.println(Operation.MINUS.eval(10, 2)); System.out.println(Operation.TIMES.eval(10, 2)); System.out.println(Operation.DIVIDE.eval(10, 2)); } }
Operatio类实际上是抽象的,不可以创建枚举值,所以此处在申明枚举值的时候,都实现了抽象方法,这其实是匿名内部类的实现,花括号部分是一个类体。我们可以看下编译以后的文件。
共生成了五个class文件,这样就证明了PLUS,MINUS,TIMES,DIVIDE是Operation的匿名内部类的实例。
六、switch语句里的表达式可以是枚举值
Java5新增了enum关键字,同时扩展了switch。
package enumcase; public class SeasonTest { public void judge(SeasonEnum s) { switch(s) { case SPRING: System.out.println("春天适合踏青。"); break; case SUMMER: System.out.println("夏天要去游泳啦。"); break; case FALL: System.out.println("秋天一定要去旅游哦。"); break; case WINTER: System.out.println("冬天要是下雪就好啦。"); break; } } public static void main(String[] args) { SeasonEnum s = SeasonEnum.SPRING; SeasonTest test = new SeasonTest(); test.judge(s); } }
case表达式中直接写入枚举值,不需加入枚举类作为限定。
有的人说,不推荐使用枚举。有的人说,枚举很好用。究竟怎么使用,如何使用,仁者见仁智者见智。总之,先学会再说~
重新认识java(十) ---- Enum(枚举类)
枚举类!
我们先来看看枚举类是什么。
Enum类!所以说,枚举类不能再继承其他类了,因为默认已经继承了Enum类。
并且,这个类是final的!所以它不能被继承!
回到我们刚才的那个疑问:
RED(30) {
@Override
public TrafficLamp getNextLamp() {
return GREEN;
}
}
为什么会有这么神奇的代码?现在你差不多懂了。因为RED本身就是一个TrafficLamp对象的引用。实际上,在初始化这个枚举类的时候,你可以理解为执行的是TrafficLamp RED = new TrafficLamp(30) ,但是因为TrafficLamp里面有抽象方法,还记得匿名内部类么?
我们可以这样来创建一个TrafficLamp引用:
TrafficLamp RED = new TrafficLamp(30){
@Override
public TrafficLamp getNextLamp() {
return GREEN;
}
};
而在枚举类中,我们只需要像上面那样写【RED(30){}】就可以了,因为java会自动的去帮我们完成这一系列操作。
如果你还是不太理解,那么你可以自己去反编译一下TrafficLamp这个类,看看jvm是怎么处理它的就明白了。
枚举类的其他用法
说一说枚举类的其他用法。
switch语句中使用
enum Signal {
GREEN, YELLOW, RED
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch (color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
实现接口
虽然枚举类不能继承其他类,但是还是可以实现接口的
public interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 接口方法
@Override
public String getInfo() {
return this.name;
}
// 接口方法
@Override
public void print() {
System.out.println(this.index + ":" + this.name);
}
}
使用接口组织枚举
public interface Food {
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO
}
enum Dessert implements Food {
FRUIT, CAKE, GELATO
}
}
使用枚举创建单例模式
使用枚举创建的单例模式:
public enum EasySingleton{
INSTANCE;
}
代码就这么简单,你可以使用EasySingleton.INSTANCE调用它,比起你在单例中调用getInstance()方法容易多了。
我们来看看正常情况下是怎样创建单例模式的:
用双检索实现单例:
下面的代码是用双检索实现单例模式的例子,在这里getInstance()方法检查了两次来判断INSTANCE是否为null,这就是为什么叫双检索的原因,记住双检索在java5之前是有问题的,但是java5在内存模型中有了volatile变量之后就没问题了。
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
你可以访问DoubleCheckedLockingSingleTon.getInstance()来获得实例对象。
用静态工厂方法实现单例:
public class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
你可以调用Singleton.getInstance()方法来获得实例对象。
上面的两种方式就是懒汉式和恶汉式单利的创建,但是无论哪一种,都不如枚举来的方便。而且传统的单例模式的另外一个问题是一旦你实现了serializable接口,他们就不再是单例的了。但是枚举类的父类【Enum类】实现了Serializable接口,也就是说,所有的枚举类都是可以实现序列化的,这也是一个优点。
总结
最后总结一下:
- 可以创建一个enum类,把它看做一个普通的类。除了它不能继承其他类了。(java是单继承,它已经继承了Enum),可以添加其他方法,覆盖它本身的方法
- switch()参数可以使用enum
- values()方法是编译器插入到enum定义中的static方法,所以,当你将enum实例向上转型为父类Enum是,values()就不可访问了。解决办法:在Class中有一个getEnumConstants()方法,所以即便Enum接口中没有values()方法,我们仍然可以通过Class对象取得所有的enum实例
- 无法从enum继承子类,如果需要扩展enum中的元素,在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组。达到将枚举元素进行分组。
- enum允许程序员为eunm实例编写方法。所以可以为每个enum实例赋予各自不同的行为。