深入学习java源码之Enum.valueOf()与Enum.ordinal()
枚举类的使用
枚举类型是强类型的,从而保证了系统安全性。枚举可以限定参数的个数,对调用者的行为能更加严格地进行控制。把一些运行期的参数检查放到了编译期。通俗的来讲就是枚举更加安全,提高了代码的可读性。
以前的静态常量的使用
七个静态变量来代表星期几,以后只要引用和静态变量就可以了,而不用自己输入012….你这么写:
public class Weekday {
public final static int SUN = 0;
public final static int MON = 1;
public final static int TUE = 2;
public final static int WED = 3;
public final static int THU = 4;
public final static int FRI = 5;
public final static int SAT = 6;
}
私有构造方法后,外界就不能创建该类的对象了,这样就避免了星期八星期九的出现,所有Weekday的对象都在该类内部创建。
public class Weekday {
private Weekday(){}
public final static Weekday SUN = new Weekday();
public final static Weekday MON = new Weekday();
public final static Weekday TUE = new Weekday();
public final static Weekday WED = new Weekday();
public final static Weekday THU = new Weekday();
public final static Weekday FRI = new Weekday();
public final static Weekday SAT = new Weekday();
public static Weekday getNextDay(Weekday nowDay){
if(nowDay == SUN) {
return MON;
}else if(nowDay == MON) {
return TUE;
}else if(nowDay == TUE) {
return WED;
}else if(nowDay == WED) {
return THU;
}else if(nowDay == THU) {
return FRI;
}else if(nowDay == FRI) {
return SAT;
}else {
return SUN;
}
}
public static void printNowDay(Weekday nowDay){
if(nowDay == SUN)
System.out.println("sunday");
else if(nowDay == MON)
System.out.println("monday");
else if(nowDay == TUE)
System.out.println("tuesday");
else if(nowDay == WED)
System.out.println("wednesday");
else if(nowDay == THU)
System.out.println("thursday");
else if(nowDay == FRI)
System.out.println("friday");
else
System.out.println("saturday");
}
}
class Test1{
public static void main(String[] args) {
Weekday nowday = Weekday.SUN;
Weekday.printNowDay(nowday);
Weekday nextDay = Weekday.getNextDay(nowday);
System.out.print("nextday ====> ");
Weekday.printNowDay(nextDay);
}
}
当你需要一个整形数据的时候,只需要Weekday.toInt(Weekday.SUN);
,看起来你好像完成了你的任务。
但是,你有没有发现,这样写,好麻烦啊。如果想要扩展一下功能,大量的ifelse会让人眼花缭乱。
1.可以看出,使用静态常量是无法存入更多的属性值
使用枚举可以存储code,name,color,feel等信息,而且并没有限制,但是使用静态常量就只能存储code和name两个值
2.还有就是在使用过程中,无法传入的参数进行限制
使用枚举时,传入的参数为为Season的枚举类型的数据,这也就限定了参数的范围,降低了代码出错的可能性
但是使用静态常量的话,传入的只是一个整型数据,如果使用时传递了一个1-4之外的数,程序时不会报错的,但是在运行时就会出错
总结:
枚举提高了代码的安全性和可读性,但是也占用了更多的内存空间
静态变量占用内存更小,但是可存储的数据有限,而且对于代码的可读性也不如枚举
枚举更适合于资源要求不严格或枚举种类不多且枚举需要存储多种属性时使用
静态常量适用于资源要求严格,且存储数据少的情况使用
枚举类
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
看起来和上面的静态变量使用方式差不多,而且默认的toString方法返回的就是对应的名字。
class Test2{
public static void main(String[] args) {
Weekday sun = Weekday.SUN;
System.out.println(sun); // 输出 SUN
}
}
就是因为在没有枚举类的时候,我们要定义一个有限的序列,比如星期几,男人女人,春夏秋冬,一般会通过上面那种静态变量的形式,但是使用那样的形式如果需要一些其他的功能,需要些很多奇奇怪怪的代码。所以,枚举类的出现,就是为了简化这种操作。没有那么多烦人的ifelse,世界都清净了。
public enum Weekday {
SUN(0),MON(1),TUS(2),WED(3),THU(4),FRI(5),SAT(6);
private int value;
private Weekday(int value){
this.value = value;
}
public static Weekday getNextDay(Weekday nowDay){
int nextDayValue = nowDay.value;
if (++nextDayValue == 7){
nextDayValue =0;
}
return getWeekdayByValue(nextDayValue);
}
public static Weekday getWeekdayByValue(int value) {
for (Weekday c : Weekday.values()) {
if (c.value == value) {
return c;
}
}
return null;
}
}
class Test2{
public static void main(String[] args) {
System.out.println("nowday ====> " + Weekday.SAT);
System.out.println("nowday int ====> " + Weekday.SAT.ordinal());
System.out.println("nextday ====> " + Weekday.getNextDay(Weekday.SAT)); // 输出 SUN
//输出:
//nowday ====> SAT
//nowday int ====> 6
//nextday ====> SUN
}
}
枚举类在定义的时候会自动为每个变量添加一个顺序,从0开始,假如你希望0代表星期天,1代表周一。。。并且你在定义枚举类的时候,顺序也是这个顺序,那你可以不用定义新的变量,就像这样:
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
我们对上面的代码做了一些改变:
首先,我们在每个枚举变量的后面加上了一个括号,里面是我们希望它代表的数字。
然后,我们定义了一个int变量,然后通过构造函数初始化这个变量。
你应该也清楚了,括号里的数字,其实就是我们定义的那个int变量。这句叫做自定义变量。
public enum Weekday {
MON(1),TUS(2),WED(3),THU(4),FRI(5),SAT(6),SUN(0);
private int value;
private Weekday(int value){
this.value = value;
}
}
请注意:这里有三点需要注意:
一定要把枚举变量的定义放在第一行,并且以分号结尾。
构造函数必须私有化。事实上,private是多余的,你完全没有必要写,因为它默认并强制是private,如果你要写,也只能写private,写public是不能通过编译的。
自定义变量与默认的ordinal属性并不冲突,ordinal还是按照它的规则给每个枚举变量按顺序赋值。
有关枚举的另一个简单的例子:
public enum Season {
Spring(1,"春","cyan","warmth"),Summer(2,"夏","green","hot"),
Autumn(3,"秋","yellow","cool"),Winter(4,"冬","white","cold");
private int code;
private String name;
private String color;
private String feel;
Season(int code, String name, String color, String feel) {
this.code = code;
this.name = name;
this.color = color;
this.feel = feel;
}
public int getCode() {
return code;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public String getFeel() {
return feel;
}
}
public class CompareEnumTest {
public static void main(String[] args) {
Emun(Season.Spring);
}
/**
* 枚举
*/
public static void Emun(Season season){
switch (season){
case Spring:
System.out.println(Season.Spring.getCode());
System.out.println(Season.Spring.getName());
System.out.println(Season.Spring.getColor());
System.out.println(Season.Spring.getFeel());
break;
case Summer:
System.out.println(Season.Summer.getCode());
System.out.println(Season.Summer.getName());
System.out.println(Season.Summer.getColor());
System.out.println(Season.Summer.getFeel());
break;
case Autumn:
System.out.println(Season.Autumn.getCode());
System.out.println(Season.Autumn.getName());
System.out.println(Season.Autumn.getColor());
System.out.println(Season.Autumn.getFeel());
break;
case Winter:
System.out.println(Season.Winter.getCode());
System.out.println(Season.Winter.getName());
System.out.println(Season.Winter.getColor());
System.out.println(Season.Winter.getFeel());
break;
}
}
}!
可以看出,使用枚举在进行参数传递的时候,使用的枚举类型Season的变量,使得代码的可读性提高
但是枚举就一定是完美的吗,其实不然
使用枚举会占用更多的空间
方法使用
public enum Weekday {
SUN,MON,TUS,WED,THU,FRI,SAT
}
class Test3{
public static void main(String[] args) {
System.out.println(Weekday.valueOf("mon".toUpperCase()));
//MON
for (Weekday w : Weekday.values()){
System.out.println(w + ".ordinal() ====>" +w.ordinal());
}
//SUN.ordinal() ====>0
//MON.ordinal() ====>1
//TUS.ordinal() ====>2
//WED.ordinal() ====>3
//THU.ordinal() ====>4
//FRI.ordinal() ====>5
//SAT.ordinal() ====>6
System.out.println("Weekday.MON.compareTo(Weekday.FRI) ===> " + Weekday.MON.compareTo(Weekday.FRI));
System.out.println("Weekday.MON.compareTo(Weekday.MON) ===> " + Weekday.MON.compareTo(Weekday.MON));
System.out.println("Weekday.MON.compareTo(Weekday.SUM) ===> " + Weekday.MON.compareTo(Weekday.SUN));
//Weekday.MON.compareTo(Weekday.FRI) ===> -4
//Weekday.MON.compareTo(Weekday.MON) ===> 0
//Weekday.MON.compareTo(Weekday.SUM) ===> 1
System.out.println("Weekday.MON.name() ====> " + Weekday.MON.name());
//Weekday.MON.name() ====> MON
}
}
Weekday.valueOf() 方法:
它的作用是传来一个字符串,然后将它转变为对应的枚举变量。前提是你传的字符串和定义枚举变量的字符串一抹一样,区分大小写。如果你传了一个不存在的字符串,那么会抛出异常。
Weekday.values()方法。
这个方法会返回包括所有枚举变量的数组。在该例中,返回的就是包含了七个星期的Weekday[]。可以方便的用来做循环。
枚举变量的toString()方法。
该方法直接返回枚举定义枚举变量的字符串,比如MON就返回【”MON”】。
枚举变量的.ordinal()方法。
默认请款下,枚举类会给所有的枚举变量一个默认的次序,该次序从0开始,类似于数组的下标。而.ordinal()方法就是获取这个次序(或者说下标)
枚举变量的compareTo()方法。
该方法用来比较两个枚举变量的”大小”,实际上比较的是两个枚举变量的次序,返回两个次序相减后的结果,如果为负数,就证明变量1”小于”变量2 (变量1.compareTo(变量2),返回【变量1.ordinal() - 变量2.ordinal()】)
枚举类的name()方法。
它和toString()方法的返回值一样,事实上,这两个方法本来就是一样的:
枚举类的其他用法
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接口,也就是说,所有的枚举类都是可以实现序列化的,这也是一个优点。
枚举与静态常量的对比
枚举提高了代码的安全性和可读性,但是也占用了更多的内存空间
静态变量占用内存更小,但是可存储的数据有限,而且对于代码的可读性也不如枚举
枚举更适合于资源要求不严格或枚举种类不多且枚举需要存储多种属性时使用
静态常量适用于资源要求严格,且存储数据少的情况使用
注解:
除了枚举和静态常量方式,还有一种就是注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
public @interface Season {
int Spring = 1;
int Summer = 2;
int Antumn = 3;
int Winter = 4;
}
public class CompareAnnotationTest {
public static void main(String[] args) {
annotation(1);
}
public static void annotation(@Season int season){
switch (season){
case StaticConstant.Season.SPRING:
System.out.println("SPRING do...");
break;
case StaticConstant.Season.SUMMER:
System.out.println("SUMMER do...");
break;
case StaticConstant.Season.ATUMN:
System.out.println("ATUMN do...");
break;
case StaticConstant.Season.WINTER:
System.out.println("WINTER do...");
break;
}
}
}
使用注解的不仅可以提高代码的可读性,还可以节省更多的空间,所以在开发中应该尽可能的使用注解,而不是枚举
Modifier and Type | Method and Description |
---|---|
protected Object |
clone() 抛出CloneNotSupportedException。 |
int |
compareTo(E o) 将此枚举与指定的对象进行比较以进行订购。 |
boolean |
equals(Object other) 如果指定的对象等于此枚举常量,则返回true。 |
protected void |
finalize() 枚举类不能有finalize方法。 |
类 |
getDeclaringClass() 返回与此枚举常量的枚举类型相对应的Class对象。 |
int |
hashCode() 返回此枚举常量的哈希码。 |
String |
name() 返回此枚举常量的名称,与其枚举声明中声明的完全相同。 |
int |
ordinal() 返回此枚举常数的序数(其枚举声明中的位置,其中初始常数的序数为零)。 |
String |
toString() 返回声明中包含的此枚举常量的名称。 |
static |
valueOf(类 返回具有指定名称的指定枚举类型的枚举常量。 |
java源码
package java.lang;
import java.io.Serializable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
public abstract class Enum>
implements Comparable, Serializable {
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String toString() {
return name;
}
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum> other = (Enum>)o;
Enum self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
@SuppressWarnings("unchecked")
public final Class getDeclaringClass() {
Class> clazz = getClass();
Class> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class)clazz : (Class)zuper;
}
public static > T valueOf(Class enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() { }
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}