深入了解和学习Java枚举Enum

深入了解和学习Java枚举Enum

枚举类概念

  • 一个类的对象是有限个,确定的,我们称此为枚举类。

  • 当需要定义和维护一组常量时,强烈建议使用枚举类。

  • 如果一个枚举类中只有一个对象,则可以作为单例模式的实现方式(用枚举实现单例模式)。

枚举类的由来

讲完上面的概念可能很多初学者会很懵逼,到底什么是枚举类啊,为什么要有枚举类啊,它跟普通类的区别是什么?

我们把枚举类这个概念抛开,因为枚举类是从JDK1.5才出现的,所以我们来看看那在JDK1.5之前我们是怎么设计这一个特殊的类的(包含固定实例数量的特殊类)

我们常常使用抽象类或者接口来维护一些常量(因为无法实例化对象),比如如下:

package org.example;

public abstract class CalendarConstant {

    public static final int YEAR = 1;

    public static final int MONTH = 2;
}

public interface CalendarConstant2 {
    int YEAR = 1;
    int MONTH = 2;
}

我们也可以参考 Java 类库中的 Calendar类, 就是使用了抽象类去维护一下常量,可以通过变量名很快的就知道对应的值的作用是什么,不需要我们一个个去记实际的值。这种写法,一来方便记忆,二来可读性高。

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {

   
    public static final int ERA = 0;

    public static final int YEAR = 1;

    public static final int MONTH = 2;

    public static final int WEEK_OF_YEAR = 3;

    public static final int WEEK_OF_MONTH = 4;

    //....

那如果。比如我们只知道一个状态值,我需要知道他的具体信息呢,比如HTTP的响应状态码,如果我们知道响应状态码是404,那我同时还需要知道他的具体表示是NOT_FOUND, 这种情况用上面两种定义常量的方式能做到吗?当然能做到了,我们只需要把返回的是一个对象,然后对象里面包含两个属性,一个是code,一个是status。然后我们也是可以通过这个常量来访问到这个具体的对象的。如下:

public abstract class HttpStatusConstant {
    public static final HttpStatus OK = new HttpStatus(200,"OK");
    public static final HttpStatus NOT_FOUND = new HttpStatus(404,"NOT_FOUND");
}
public class HttpStatus {

    private final String status;
    private final int code;

    public HttpStatus(int code, String status) {
        this.code = code;
        this.status = status;
    }

    public String getStatus() {
        return status;
    }


    public int getCode() {
        return code;
    }
}

有的人看到这里就会觉得这有点像枚举类了,别急我们继续看。

那如果我每次想定义这样的常量,我都需要写两个类,一个抽象类来管理常量,一个类来管理我们封装的对象,那真的好麻烦的,而且这个HttpStatus类还可以被其他地方用到,还有更简单更有针对性的写法吗?

当然有啦,我们往下看

public class HttpStatus2 {

    private final int code;
    private final String status;

    private HttpStatus2(int code, String status) {
        this.code = code;
        this.status = status;
    }

    public static final HttpStatus2 OK = new HttpStatus2(200, "OK");
    public static final HttpStatus2 NOT_FOUND = new HttpStatus2(404, "NOT_FOUND");


    public int getCode() {
        return code;
    }

    public String getStatus() {
        return status;
    }


    @Override
    public String toString() {
        return "HttpStatus2{" +
                "code=" + code +
                ", status='" + status + '\'' +
                '}';
    }
}

通过改造之后我们实际上就只有一个类了,而且这个类因为构造器是private私有的,所以不能实例化,然后可以通过常量名去获取对应的实例,这简直就跟枚举类是一样的啦。

我们来测试看看

public class TestMain {

    public static void main(String[] args) {
        System.out.println(HttpStatus2.OK);
        System.out.println(HttpStatus2.OK.getStatus());
        System.out.println(HttpStatus2.OK.getCode());
    }
}

结果如下:

HttpStatus2{code=200, status='OK'}
OK
200

所以我们现在就可以使用常量来获取枚举对象啦。而且这种可用性就提高了很多…

但程序员都是懒惰的,以后我们定义这些枚举对象我们都要写的这么麻烦吗?所以jdk1.5 中引入的新特性,在Java 中被 enum 关键字修饰的类型就是枚举类型。这里的枚举类,就跟我们上面自己定义的枚举类是一样的。

public enum HttpStatusEnum{
    OK(200,"OK"),
    NOT_FOUND(404,"NOT_FOUND");

    private final int code;
    private final String status;

    HttpStatusEnum(int code, String status){
        this.code = code;
        this.status = status;
    }

    public int getCode() {
        return code;
    }

    public String getStatus() {
        return status;
    }

}

枚举类创建注意:

  • 枚举实例必须在 enum关键字声明的类中显式的指定(首行开始的以第一个分号结束)

  • 枚举不允许使用new,clone,反射,序列化手动创建枚举实例

反编译JAVA枚举类

我们来看看这个枚举类 经过反编译之后的内容是什么?

javac -encoding UTF-8 HttpStatusEnum.java
javap -p HttpStatusEnum.class
public final class org.example.HttpStatusEnum extends java.lang.Enum<org.example.HttpStatusEnum> {
  public static final org.example.HttpStatusEnum OK;
  public static final org.example.HttpStatusEnum NOT_FOUND;
  private final int code;
  private final java.lang.String status;
  private static final org.example.HttpStatusEnum[] $VALUES;
  public static org.example.HttpStatusEnum[] values();
HttpStatusEnum valueOf(java.lang.String);
  private org.example.HttpStatusEnum(int, java.lang.String);
  public int getCode();
  public java.lang.String getStatus();
  static {};
}

通过编译我们可以看到,这个类跟我们之前自定义的类十分相似。

首先如果使用了enum关键字声明的类,它被定义为final的,也就是不能够被继承。然后他的构造方法也是private的,不能够被实例化出来的。然后定义的枚举类默认隐式继承于java.lang.Enum类。

枚举类的主要方法

接下来我们来介绍一下java.lang.Enum的主要方法

  • values(): 该方法可以返回当前枚举类型的对象数组,可以很方便的遍历所有枚举值。一般我们可以根据枚举类的相关属性通过此方法遍历获取对应的枚举对象及枚举值

  • valueOf(String str) : 根据枚举类名称获取枚举类对象

  • toString(): 默认使用 java.lang.Enum的 toString方法,返回当前对象常量的名称,枚举类推荐重写返回自定义友好描述

  • name(): 返回当前枚举对象名称,和toString作用上类似,当时toString支持重写,name方法是不能重写的,在本质上 toString 也是调用的 name方法,枚举定义 name 方法就是为了返回枚举对象名称,而 toString 应该根据需要进行重写

  • ordinal(): 返回当前枚举对象的序号, 实现了 Comparable 接口,表明它是支持排序的 可以通过 Collections.sort 进行自动排序比较此枚举与指定对象的顺序

  • compareTo(): 基于ordinal进行序号大小比较

枚举类对接口的实现方式

枚举类统一实现抽象方法

因为枚举类本身就是一个特殊的类,所以他当然能够实现接口了,这个可能之前用的会比较少,包括我之前也没怎么注意到这种方式。

我们首先来定义一个接口

public interface EnumInf {
    void show();
}

然后我们的枚举来实现这个接口,并统一重写这个show方法

public enum HttpStatusEnum implements EnumInf{
    OK(200,"OK"),
    NOT_FOUND(404,"NOT_FOUND");

    private final int code;
    private final String status;

    HttpStatusEnum(int code, String status){
        this.code = code;
        this.status = status;
    }

    public int getCode() {
        return code;
    }

    public String getStatus() {
        return status;
    }

    //枚举统一重写接口抽象方法
    @Override
    public void show() {
        System.out.println("来吧展示..");
    }
}

然后我们就可以通过HttpStatusEnum.OK.show();去调用我们的show方法。

枚举对象分别实现接口中的抽象方法

啊这…跟我们常用类实现没有什么区别,枚举也是可以统一实现的,那如果想针对不同的枚举对象进行不同状态的实现怎么办呢?

public enum HttpStatusEnum2 implements EnumInf{
    OK(200,"OK") {
        @Override
        public void show() {
            System.out.println("OK 展示");
        }
    },
    NOT_FOUND(404,"NOT_FOUND"){
        @Override
        public void show() {
            System.out.println("NOT_FOUND 展示");
        }
    };

    private final int code;
    private final String status;

    HttpStatusEnum2(int code, String status){
        this.code = code;
        this.status = status;
    }

    public int getCode() {
        return code;
    }

    public String getStatus() {
        return status;
    }

}

通过这种方式就可以轻而易举地定义每个枚举实例不同的行为方式.

EnumMap用法

public class EnumMapTest {

    public static void main(String[] args) {
        EnumMap<HttpStatusEnum, String> enumMap = new EnumMap<>(HttpStatusEnum.class);
        enumMap.put(HttpStatusEnum.OK, "成功的");
        enumMap.put(HttpStatusEnum.NOT_FOUND, "找不到的");
        System.out.println(enumMap);
        System.out.println(enumMap.get(HttpStatusEnum.OK));
    }
}

EnumMap 是专门为枚举类型量身定做的 Map 实现。虽然使用其它的 Map 实现(如HashMap)也能完成枚举类型实例到值得映射,但是使用 EnumMap 会更加高效:它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值。(计算机处理连续的资源使用局部内存效率更高)这使得 EnumMap 的效率非常高。

EnumSet用法

EnumSet 是枚举类型的高性能 Set 实现。它要求放入它的枚举常量必须属于同一枚举类型。

public class EnumSetTest {

    public static void main(String[] args) {
        //EnumSet.noneOf()方法创建一个空的set
        EnumSet<HttpStatusEnum> enumSet = EnumSet.noneOf(HttpStatusEnum.class);
        System.out.println(enumSet);
        enumSet.add(HttpStatusEnum.OK);
        enumSet.add(HttpStatusEnum.NOT_FOUND);
        enumSet.add(HttpStatusEnum.NOT_FOUND);
        System.out.println(enumSet);

        //EnumSet.allOf()方法创建一个满的set
        EnumSet<HttpStatusEnum> enumSet2 = EnumSet.allOf(HttpStatusEnum.class);
        System.out.println(enumSet2);
    }
}

枚举类与 Switch 的配合使用

枚举与switch是比较简单的,使用switch进行条件判断时,条件参数一般只能是整型,字符型。而枚举型确实也被switch所支持,在java 1.7后switch也对字符串进行了支持。

public class TestSwitch {

    public static void main(String[] args) {
        HttpStatusEnum ok = HttpStatusEnum.OK;

        switch (ok){
            case OK:
                System.out.println("ok");
                break;
            case NOT_FOUND:
                System.out.println("not found");
                break;
            default:
                System.out.println("none");
        }
    }
}

利用枚举实现单例模式

最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。

class Resource{
}
 
public enum SomeThing {
    INSTANCE;
    private final Resource instance;
    SomeThing() {
        instance = new Resource();
    }
    public Resource getInstance() {
        return instance;
    }
}
public class Main {
    public static void main(String[] args) {
        Resource instance = SomeThing.INSTANCE.getInstance();
        System.out.println(instance);
    }
}

利用Java枚举实现策略模式

我们在使用Java的枚举时往往会结合Switch来进行判断以实现不同值的处理,但是我们知道多用switch不是一种很好的代码风格,不利用维护和适应变化,因为这不符合开-闭原则。为此一种方法是用策略模式来重构原有的枚举实现。

public enum HandFeeCalculator {
 
    // 端游
    PC {
        public double count(double amount) {
            return amount * 5 / 100;
        }
    },
    // 页游
    PAGE {
        public double count(double amount) {
            return amount * 2 / 100;
        }
    },
    // 手游
    MOBILE {
        public double count(double amount) {
            return 0.0;
        }
    };

    public abstract double count(double amount);
    
    public static void main(String[] args) {
        // 交易金额
        double amount = 500.0;
        // 计算不同游戏类型的手续费
        System.out.println(HandFeeCalculator.PC.count(amount));
 
        System.out.println(HandFeeCalculator.PAGE.count(amount));
 
        System.out.println(HandFeeCalculator.MOBILE.count(amount));
    }
 
}

我们定义了一个抽象的count()方法,承担Strategy(策略)的角色, 之后在枚举的每个成员中,对这个抽象方法进行了实现,承担典型的ConcreteStrategy(具体策略)角色,而枚举类本身又承担了Context环境(调用入口,选择不同的枚举对象)角色的作 用,我们通过选择不同的枚举成员就可以达到计算不同手续费的目的…

参考

枚举详解之EnumSet、EnumMap用法

Java枚举深度解读,看这篇就够了

Java单例模式:为什么我强烈推荐你用枚举来实现单例模式

Java 利用枚举实现单例模式

揭秘设计模式:策略模式(Strategy)的枚举(Enum)实现

Java中的枚举类型(Enum)详解

java 枚举(enum) 全面解读

源代码

https://gitee.com/cckevincyh/java_enum_learning

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值