Java学习--枚举
枚举类型enum,地位等同于class,interface
使用enum定义的枚举类型,也是一种变量类型,可用于声明变量
枚举的一些特征
1.它不能有public的构造函数,这样做可以保证客户代码没有办法新建一个enum的实例。 2.所有枚举值都是public static final的。注意这一点只是针对于枚举值,我们可以和在普通类里面定义变量一样定义其它任何类型的非枚举变量,这些变量可以用任何你想用的修饰符。 3.Enum默认实现了java.lang.Comparable接口。 4.Enum覆载了了toString方法,因此我们如果调用Color.Blue.toString()默认返回字符串”Blue”. 5.Enum提供了一个valueOf方法,这个方法和toString方法是相对应的。调用valueOf(“Blue”)将返回Color.Blue.因此我们在自己重写toString方法的时候就要注意到这一点,一把来说应该相对应地重写valueOf方法。 6.Enum还提供了values方法,这个方法使你能够方便的遍历所有的枚举值。 7.Enum还有一个ordnal的方法,这个方法返回枚举值在枚举类种的顺序,这个顺序根据枚举值声明的顺序而定,这里Color.Red.ordinal()返回0。
什么情况下使用枚举
当某一个事物的状态或者类别等仅限于有限的几种时,就可以使用枚举
枚举一般用来表示一组类型相同的常量
例如一年四季
public enum SeasonEnum {
SPRING, SUMMER, FALL, WINTER;
}
例如星期,一周有七天,这样就可以使用枚举来表示
public enum Weekdays {
MON, TUE, WED, THU, FRI, SAT, SUN;
}
例如一个英雄的状态 (走, 跑, 攻击, 防御, 死亡)
public enum HeroStatus {
WALKING, RUNNING, ATTACKING, DEFENDING, DEAD;
}
枚举的实现原理
枚举本质上是通过普通的类来实现的,只是编译器为我们进行了处理。
每个枚举类型都继承自 java.lang.Enum,并自动添加了 values 和 valueOf 方法。因为类只能单继承,而枚举类默认继承了ENUM类,所以无法继承其他的类,但可以实现接口
而每个枚举常量是一个静态常量字段,使用内部类实现,该内部类继承了该枚举类。
所有枚举常量都通过静态代码块来进行初始化,即在类加载期间就初始化。
另外通过把 clone、readObject、writeObject 这三个方法定义为 final 的,同时实现是抛出相应的异常。这样保证了每个枚举类型及枚举常量都是不可变的。可以利用枚举的这两个特性来实现线程安全的单例。
Enum 类的静态方法
values() 方法
返回该枚举类所有静态常量的数组
Enum 对象的常用方法
getDeclaringClass() 方法
返回和当前枚举常量的枚举类型对应的 class 对象
name() 和 toString()方法是一样的
返回枚举常量的名称,这个名称是字符串类型的.两个方法一样
ordinal() 方法
返回的枚举常量的序号,值得注意的是 Enum 对象的序号是从0开始计数的,
枚举类的常用操作
枚举类的遍历
switch 来匹配 Enum 中的内容
for (SeasonEnum season: SeasonEnum.values()) {
System.out.println(season.name());
System.out.println(season.ordinal());
switch (season) {
case SPRING:
System.out.println("this is spring");
break;
case SUMMER:
System.out.println("this is spring");
break;
case FALL:
System.out.println("this is spring");
break;
case WINTER:
System.out.println("this is spring");
break;
}
}
枚举类的优点
使用枚举类型的变量作为参数,可以使参数的值仅限于枚举类型的几种常量,保证了安全性
枚举类型可以遍历,便于操作
在Java 1.5之前,没有枚举类型时,我们是怎样表示枚举的。public class PlanetWithoutEnum { public static final int PLANET_MERCURY = 0;
public class PlanetWithoutEnum {
public static final int PLANET_MERCURY = 0;
public static final int PLANET_VENUS = 1;
public static final int PLANET_EARTH = 2;
public static final int PLANET_MARS = 3;
public static final int PLANET_JUPITER = 4;
public static final int PLANET_SATURN = 5;
public static final int PLANET_URANUS = 6;
public static final int PLANET_NEPTUNE = 7;
}
这种叫int枚举模式,当然你也可以使用String枚举模式,无论采用何种方式,这样的做法,在类型安全和使用方便性上都很差。
如果变量planet表示一个行星,使用者可以给这个值赋与一个不在我们枚举值里面的值,比如 planet = 9,这是哪个行星估计也只有天知道了;
如果方法的参数需要传入一个代表行星的整数,实际中代表八大行星的数只有0-7,但传入其他整数也不会报错,因为形参的类型为int,使用枚举就可以避免这种问题,保证了安全性
再者,我们很难计算出到底有多少个行星,我们也很难对行星进行遍历操作等等。
现在我们用枚举来创建我们的行星。
public enum Planet {
MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE;
}
使用枚举,我们实现了一个功能,就是任何一个Planet类型的变量,都可以由编译器来保证,传到给参数的任何非null对象一定属于这八个行星之一。
枚举的使用实例
Java允许我们给枚举类型添加任意的属性和方法,这里引言书中的代码,大家自行体会一下枚举的构造器、公共方法、枚举遍历等知识点。
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS(4.869e+24, 6.052e6),
EARTH(5.975e+24,6.378e6),
MARS(6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN(5.685e+26, 6.027e7),
URANUS(8.683e+25, 2.556e7),
NEPTUNE(1.024e+26,2.477e7);
private final double mass; // In kilograms
private final double radius; // In meters
private final double surfaceGravity; // In m / s^2
// Universal gravitational constant in m^3 / kg s^2
private static final double G = 6.67300E-11;
// Constructor
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double mass() {
return mass;
}
public double radius() {
return radius;
}
public double surfaceGravity() {
return surfaceGravity;
}
public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}
在公共方法里,使用switch去判断枚举类型,然后执行不同的操作
public enum OperationUseSwitch {
PLUS, MINUS, TIMES, pIDE;
double apply(double x, double y) {
switch (this) {
case PLUS:
return x + y;
case MINUS:
return x + y;
case TIMES:
return x + y;
case pIDE:
return x + y;
}
// 如果this不属于上面四种操作符,抛出异常
throw new AssertionError("Unknown operation: " + this);
}
}
这段代码确实实现了我们的需求,但是有两个弊端。
首先是我们不得不在最后抛出异常或者在switch里加上default,不然无法编译通过,但是很明显,程序的分支是不会进入异常或者default的。
其次,这段代码非常脆弱,如果我们添加了新的操作类型,却忘了在switch里添加相应的处理逻辑,执行新的运算操作时,就会出现问题。
还好,Java枚举提供了一种功能,叫做 特定于常量的方法实现。
我们只需要在枚举类型中声明一个抽象方法,然后在各个枚举常量中去覆盖这个方法,实现如下:
public enum Operation {
PLUS {
double apply(double x, double y) {
return x + y;
}
},
MINUS {
double apply(double x, double y) {
return x - y;
}
},
TIMES {
double apply(double x, double y) {
return x * y;
}
},
pIDE {
double apply(double x, double y) {
return x / y;
}
};
abstract double apply(double x, double y);
}
这样,也就再也不会出现添加新操作符后忘记添加对应的处理逻辑的情况了,因为编译器就会提示我们必须覆盖apply方法。
不过,这种 特定于常量的方法实现 有一个缺点,那就是你很难在枚举常量之间共享代码。
我们以星期X的枚举为例,周一到周五是工作日,执行一种逻辑,周六周日,休息日,执行另一种逻辑。
如果还是使用 特定于常量的方法实现,写出来的代码可能就是这样的:
public enum DayUseAbstractMethod {
MONDAY {
@Override
void apply() {
dealWithWeekDays();//伪代码
}
},
TUESDAY {
@Override
void apply() {
dealWithWeekDays();//伪代码
}
},
WEDNESDAY {
@Override
void apply() {
dealWithWeekDays();//伪代码
}
},
THURSDAY {
@Override
void apply() {
dealWithWeekDays();//伪代码
}
},
FRIDAY {
@Override
void apply() {
dealWithWeekDays();//伪代码
}
},
SATURDAY {
@Override
void apply() {
dealWithWeekEnds();//伪代码
}
},
SUNDAY {
@Override
void apply() {
dealWithWeekEnds();//伪代码
}
};
abstract void apply();
}
很明显,我们这段代码里面有相当多的重复代码。
那么要怎么优化呢,我们不妨这样想,星期一星期二等等是一种枚举,那么工作日和休息日,难道不也是一种枚举吗,我们能不能给Day的构造函数传入一个工作日休息日的DayType枚举呢?这也就是书中给出的一种叫策略枚举 的方法,代码如下:
public enum Day {
MONDAY(DayType.WEEKDAY),
TUESDAY(DayType.WEEKDAY),
WEDNESDAY(DayType.WEEKDAY),
THURSDAY(DayType.WEEKDAY),
FRIDAY(DayType.WEEKDAY),
SATURDAY(DayType.WEEKEND),
SUNDAY(DayType.WEEKEND);
private final DayType dayType;
Day(DayType daytype) {
this.dayType = daytype;
}
void apply() {
dayType.apply();
}
private enum DayType {
WEEKDAY {
@Override
void apply() {
System.out.println("hi, weekday");
}
},
WEEKEND {
@Override
void apply() {
System.out.println("hi, weekend");
}
};
abstract void apply();
}
}
通过策略枚举的方式,我们把Day的处理逻辑委托给了DayType,个中奥妙,读者可以细细体会。
枚举集合 EnumSet的使用
EnumSet提供了非常方便的方法来创建枚举集合,下面这段代码,感受一下
public class Text {
public enum Style {
BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
}
// Any Set could be passed in, but EnumSet is clearly best
public void applyStyles(Set<Style> styles) {
// Body goes here
for(Style style : styles){
System.out.println(style);
}
}
// Sample use
public static void main(String[] args) {
Text text = new Text();
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
}
}
这个例子里,我们使用了EnumSet.of方法,轻松创建了枚举集合。
枚举Map EnumMap的使用
假设对于香草(Herb),有一个枚举属性Type(一年生、多年生、两年生)
Herb:
public class Herb {
public enum Type { ANNUAL, PERENNIAL, BIENNIAL }
private final String name;
private final Type type;
Herb(String name, Type type) {
this.name = name;
this.type = type;
}
@Override
public String toString() {
return name;
}
}
现在,假设我们有一个Herb数组,我们需要对这个Herb数组按照Type进行分类存放。
所以接下来,我们需要创建一个Map,value肯定是Herb的集合了,那么用什么作为key呢?
有的人会使用枚举类型的ordinal()方法,这个函数返回int类型,表示枚举遍历在枚举类里的位置,这样做,缺点很明显,由于你的key的类型是int,不能保证传入的int一定能和枚举类里的变量对应上。
所以,在key的选择上,毫无疑问,只能使用枚举类型,也即Herb.Type。
最后还有一个问题,要使用什么Map? Java为枚举类型专门提供了一种Map,叫EnumMap,相比较与其他Map,这种Map在处理枚举类型上更快,有兴趣的同学可以研究一下这个map的内部实现。
下面让我们看看怎么使用EnumMap:
public static void main(String[] args) {
Herb[] garden = { new Herb("Basil", Type.ANNUAL),
new Herb("Carroway", Type.BIENNIAL),
new Herb("Dill", Type.ANNUAL),
new Herb("Lavendar", Type.PERENNIAL),
new Herb("Parsley", Type.BIENNIAL),
new Herb("Rosemary", Type.PERENNIAL) };
// Using an EnumMap to associate data with an enum - Page 162
Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>(
Herb.Type.class);
for (Herb.Type t : Herb.Type.values())
herbsByType.put(t, new HashSet<Herb>());
for (Herb h : garden)
herbsByType.get(h.type).add(h);
System.out.println(herbsByType);
}
总结
和int枚举相比,Enum枚举的在类型安全和使用便利上的优势是不言而喻的。
Enum为枚举提供了丰富的功能,如文章中提到的特定于常量的方法实现和策略枚举。
EnumSet和EnumMap是两个为枚举而设计的集合,在实际开发中,用到枚举集合时,请优先考虑这两个。
Java学习--枚举的更多相关文章
- Java学习——枚举类
Java学习——枚举类 摘要:本文主要介绍了Java的枚举类. 部分内容来自以下博客: https://www.cnblogs.com/sister/p/4700702.html https://bl ...
- 0035 Java学习笔记-注解
什么是注解 注解可以看作类的第6大要素(成员变量.构造器.方法.代码块.内部类) 注解有点像修饰符,可以修饰一些程序要素:类.接口.变量.方法.局部变量等等 注解要和对应的配套工具(APT:Annot ...
- 《Java学习笔记(第8版)》学习指导
<Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...
- 0013 Java学习笔记-面向对象-static、静态变量、静态方法、静态块、单例类
static可以修饰哪些成员 成员变量---可以修饰 构造方法---不可以 方法---可以修饰 初始化块---可以修饰 内部类(包括接口.枚举)---可以修饰 总的来说:静态成员不能访问非静态成员 静 ...
- Java学习笔记4
Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...
- java学习笔记10--枚举
java学习笔记10--枚举 在JDK1.5之前,java可以有两种方式定义新类型:类和接口.对于大部分面向对 象编程来说,这两种方法看起来似乎足够了,但是在一些特殊情况下,这些方法就不适合.例如,想 ...
- java学习笔记11--Annotation
java学习笔记11--Annotation Annotation:在JDK1.5之后增加的一个新特性,这种特性被称为元数据特性,在JDK1.5之后称为注释,即:使用注释的方式加入一些程序的信息. j ...
- Java学习路线图分析
Java学习路线分析图 第一阶段 技术名称 技术内容 J2SE(java基础部分) java开发前奏 计算机基本原理,Java语言发展简史以及开发环境的搭建,体验Java程序的开发,环境变量的设置, ...
- java学习(四)
学号 20189214 <Java程序设计>第四周学习总结 教材学习内容总结 枚举 枚举是JDK1.5版本新增的特性(泛型.For-each等如今被广泛应用的特性也是由JDK1.5时所新增 ...
随机推荐
- Git自动化合并多个Commit
目录 git rebase逻辑 git editor的修改 处理git-rebase-todo文件 Python实现 当我们有多个commit或者从开源处拿到多个commit时,想合成一个commit ...
- Django学习笔记之上下文处理器和中间件
上下文处理器 上下文处理器是可以返回一些数据,在全局模板中都可以使用.比如登录后的用户信息,在很多页面中都需要使用,那么我们可以放在上下文处理器中,就没有必要在每个视图函数中都返回这个对象. 在set ...
- Java 动态绑定
转载 http://www.cnblogs.com/ygj0930/p/6554103.html 一:绑定 把一个方法与其所在的类/对象 关联起来叫做方法的绑定.绑定分为静态绑定(前期绑定)和动态绑 ...
- python字符串截取、查找、分割
Python 截取字符串使用 变量[头下标:尾下标],就可以截取相应的字符串,其中下标是从0开始算起,可以是正数或负数,下标可以为空表示取到头或尾. # 例1:字符串截取 str = '1234567 ...
- 第一次使用mybatis
程序使用mybatis的步骤: 1.配置mybatis 涉及到的配置文件有conf.xml和与实体类对应的映射配置文件 (1) conf.xml:配置数据库信息和需要加载的映射文件 <confi ...
- v4l2框架
参考:https://www.cnblogs.com/tuotuteng/p/4648387.html http://blog.sina.com.cn/s/blog_c91863e60102w65w. ...
- JRockit检测Tomcat内存溢出JAVA内存泄漏问题
http://blog.csdn.net/liyanhui1001/article/details/8240473 公司的一个Java应用系统上线以来,基本每1天OutOfMemoryError: P ...
- MySQL误操作删除后,怎么恢复数据?
MySQL误操作删除后,怎么恢复数据?登陆查数据库mysql> select * from abc.stad;+----+-----------+| id | name |+----+----- ...
- 一个HTTP打趴80%面试者
面试多年,每当我问起面试者对HTTP的了解时,个个回答令我瞠目结舌,这些开发者都有3-5年的经验.请不要让我叫你野生程序员,是时候了解HTTP了,让我们当个正规军. 起因 面试官:请问你了解HTTP协 ...
- list 转成 tree
package com.zl; import java.util.ArrayList; import java.util.List; public class MenuItem { private S ...