前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java枚举细节

Java枚举细节

作者头像
w4ngzhen
发布2023-10-16 21:38:50
1920
发布2023-10-16 21:38:50
举报
文章被收录于专栏:编译思想编译思想
枚举的简单使用

在java中,我们可以使用enum关键字来定义枚举:

代码语言:javascript
复制
public enum Color {
    RED, GREEN, BLUE;
}

就像上面一样,我们定义了一个名为Color的枚举类,包含了RED、GREEN、BLUE三个常量。当我们使用枚举类的时候,直接通过枚举类名.枚举常量即可。就像如下的形式:

代码语言:javascript
复制
void f(Color c) {
    switch (c) {
        case RED: System.out.println("It's RED"); break;
        case GREEN: System.out.println("It's GREEN"); break;
        default: System.out.println("It's BLUE");
    }
}
// 调用
f(Color.RED);
// 输出 "It's RED"
枚举的本质

虽然Java提供枚举类的定义,但是实际上他并不是Java中一个新的对象类型,我们通过对Color枚举类进行反编译,得到如下的反编译结果:

代码语言:javascript
复制
$ javap Color.class 
// 反编译结果
Compiled from "Color.java"
public final class EnumerationAndAnnotation.Color extends java.lang.Enum<EnumerationAndAnnotation.Color> {
  public static final EnumerationAndAnnotation.Color RED;
  public static final EnumerationAndAnnotation.Color GREEN;
  public static final EnumerationAndAnnotation.Color BLUE;
  public static EnumerationAndAnnotation.Color[] values();
  public static EnumerationAndAnnotation.Color valueOf(java.lang.String);
  static {};
}

我们可以看到,枚举类实际上在编译的过程中,被编译器进行调整,它并不是一个新的类型,本质上依然是一个类(Color),这个类继承了java.lang.Enum,而对于每一个枚举常量,实际上是public static final修饰的枚举类的静态实例对象。

同时注意,编译器会为我们添加两个新的static方法:values() 和 valueOf(java.lang.String),其实分别作用是返回枚举类中定义的所有的枚举常量,以及根据枚举名来获取枚举常量(注意,这里就是定义枚举常量的枚举名)。

当然,由于每一个枚举常量实际上是实现了java.lang.Enum的枚举类的一个静态实例对象,而这个过程是编译器为我们进行的,所以,自然,我们可以在枚举类中定义任何的方法、变量,以及构造函数的定义:

代码语言:javascript
复制
public enum Color {
    RED("RED"), GREEN("GREEN"), BLUE("BLUE");
    
    // 定义类中的实例变量
    private String colorName;
    // 定义构造函数,注意上面的枚举常量必须满足这种构造方式
    Color(String colorName) {
        this.colorName = colorName;
    }
    // 定义实例对象的方法
    public void printColorName() {
        System.out.println("Color name is " + colorName);
    }
    // 重写toString方法
    @Override
    public String toString() {
        return colorName;
    }
}

自然,我们可以枚举常量当作一个枚举的实例化对象,调用枚举类中的方法:

代码语言:javascript
复制
public static void main(String[] args) {
    System.out.println(Color.RED); // RED
    Color.RED.printColorName();	// Color name is RED
}
为什么枚举构造器不能访问枚举的静态域

查看下面这段代码:

代码语言:javascript
复制
public enum Color {
    RED, GREEN, BLUE;
    private static int value = 0;
    Color() {
        System.out.println(value); // 编译错误!构造器无法访问静态变量
    }
}

我们可以看到这里编译不通过,提示我们枚举构造器不能够访问枚举的静态域(以及静态变量)。我们知道,一般的类中,静态域以及静态变量是优于实例对象的变量、方法的初始化的。这里简要复习一下类中变量的加载机制:

代码语言:javascript
复制
// 辅助静态变量的初始化
class Init {
    public Init(String init) {
        System.out.println("Init()" + init);
    }
}
class Fa {
    // 父类静态变量
    static final Init initFa = new Init("Fa's static");
    // 父类实例变量
    Init initFa2 = new Init("Fa's no static");
    // 父类静态域
    static {
        System.out.println("Fa's static");
    }
    // 父类构造方法
    Fa() {
        System.out.println("Fa()");
    }
}
class Su extends Fa {
    // 子类静态域
    static {
        System.out.println("Su's static");
    }
    // 子类静态变量
    static final Init initSu = new Init("Su's static");
    // 子类实例变量
    Init initSu2 = new Init("Su's no static");
    // 子类构造函数
    Su() {
        System.out.println("Su()");
    }
}

接下来,我们new出Su实例对象,并观察输出结果:

代码语言:javascript
复制
public static void main(String[] args) {
    new Su();
}
/* 输出
Init()Fa's static 	// 1
Fa's static			// 2
Su's static			// 3
Init()Su's static	// 4
Init()Fa's no static// 5
Fa()				// 6
Init()Su's no static// 7
Su()				// 8
*/

我们可以看到,有static修饰的始终优于实例对象的相关的初始化的,在输出中 1 - 4 是static修饰部分,5 - 8是实例域部分。此外,在继承情形下,父类由于子,输出中 1- 2 是父类static域的初始化,3 - 4 是子类static域的初始化。在static域加载完成之后,才开始加载父类非static域,最后加载子类的非static域。注意,都为static修饰的情况下,加载顺序根绝定义时候的顺序而来,1、2与3、4就可以看出。

看到这里,也许会有点疑问,既然静态域加载优于实例域(包含构造函数),那为什么在枚举类中就不行呢?让我们回到前面对枚举类的反编译,其实答案就出来了。反编译的过程我们可以看到,我们的枚举常量实际上是我们枚举类的静态实例化对象,在编译器的修改下,我们运行加载枚举类的过程中,枚举常量是static修饰的,其他静态域也是static修饰的,枚举常量又排在其他静态域的前面,按照上面的额初始化顺序,首先就会调用构造器实例化枚举常量对象,此时,枚举类中的其他静态域都还没来得及初始化,自然在构造函数中不能访问静态域了。有人可能想说,那我静态域放到枚举常量前面,让他先加载怎么样?很遗憾,Java不允许这样做:

代码语言:javascript
复制
public enum Color { // 编译不通过!!!
    private static int value = 0;
    RED, GREEN, BLUE;
}
使用抽象函数为枚举统一方法

上面的内容探讨了枚举类的一些基础,这里提一些关于使用枚举的代码策略设计。 有的时候,我们想要给枚举常量定义某一些通用的方法,同时,针对不同的枚举,该通用方法呈现不同的具体内容。例如,我现在有一个如下的Color枚举类,当针对不同的Color常量的时候,能有一个方式给我返回该颜色的十六进制颜色码。也许你会如下来实现

代码语言:javascript
复制
public enum Color {
    RED, GREEN, BLUE;
    public String getHexCode() {
        switch (this) {
            case RED:
                return "#FF0000";
            case GREEN:
                return "#00FF00";
            default:
                return "#0000FF";
        }
    }
}

以上的方式较为简洁与易读,但是存在问题:枚举常量越来越多,case会不断增长,如果编写代码的过程由于不注意,增加颜色常量的时候,忘记了增加对应的case,那么编译是不会有任何的问题的,但是却隐含的将增加的颜色常量也返回的是BLUE的十六进制颜色码。

鉴于上述的问题,我们需要某种方式来防止我们犯错,能够想到的,就是通过编译器来告诉我们。于是,我们在枚举类中定一个抽象方法getHexCode,于是乎,对于每一个枚举常量,编译器会提示我们实现具体实例的getHexCode:

代码语言:javascript
复制
public enum Color {
    RED {
        @Override
        public String getHexCode() {
            return "#FF0000";
        }
    }, GREEN {
        @Override
        public String getHexCode() {
            return "#00FF00";
        }
    }, BLUE {
        @Override
        public String getHexCode() {
            return "#0000FF";
        }
    };

    abstract public String getHexCode();
}

在这样的定义下,代码牺牲了一定的简洁性,但是易读性丝毫不输于最开始的方式。针对Color枚举类,我们定义了抽象方法,表明了对于Color中的每一个枚举常量,都应该有getHexCode方法,返回自己的十六进制颜色码。如果我们新添加了枚举常量,而没有实现该方法,编译器会报错警告我们。

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-05-182,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 枚举的简单使用
  • 枚举的本质
  • 为什么枚举构造器不能访问枚举的静态域
  • 使用抽象函数为枚举统一方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com