前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊一聊Java枚举(enmu)的使用

聊一聊Java枚举(enmu)的使用

作者头像
终身幼稚园
发布2020-05-18 18:09:11
1.3K0
发布2020-05-18 18:09:11
举报
文章被收录于专栏:终身幼稚园终身幼稚园

一、声明和使用基本枚举

枚举可以被认为是一个密封类的语法糖,该类在编译时仅实例化了若干次,以定义一组常量。

例子:一个简单的枚举列举出不同的季节:

代码语言:javascript
复制
public enum Season { 
    WINTER,
    SPRING,
    SUMMER,
    FALL
}

我们可以在单独文件中声明枚举, 也可以在类中声明

代码语言:javascript
复制
public class Day {
    private Season season;
    public String getSeason() { 
        return season.name();
    }
    public void setSeason(String season) { 
        this.season = Season.valueOf(season);
    }

    private enum Season { 
       WINTER,
       SPRING,
       SUMMER,
       FALL
    }
}

枚举的每个常量默认情况下都是public、static和final的。由于每个常量都是静态的,因此可以使用enum名称直接访问它们。

枚举常数可以作为方法参数传递:

代码语言:javascript
复制
public static void display(Season s) {
    System.out.println(s.name());//name()是一个内置方法,它获取枚举常量
}
代码语言:javascript
复制
display(Season.WINTER); // 打印 "WINTER"

可以使用values()方法获得枚举常量的数组。保证返回数组中的值按照声明顺序:

代码语言:javascript
复制
Season[] seasons = Season.values();

注意:这个方法每次调用时都会分配一个新的值数组。

迭代枚举常量:

代码语言:javascript
复制
public static void enumIterate() {
for (Season s : Season.values()) {
        System.out.println(s.name());
    }
}

可以在switch语句中使用枚举:

代码语言:javascript
复制
public static void enumSwitchExample(Season s) {
    switch(s) {
        case WINTER:
            System.out.println("It's pretty cold"); 
            break;
        case SPRING:
            System.out.println("It's warming up"); 
            break;
        case SUMMER:
            System.out.println("It's pretty hot"); 
            break;
        case FALL:
            System.out.println("It's cooling down"); 
            break;
    } 
}

可以通过==来对比枚举:

代码语言:javascript
复制
 Season.FALL == Season.WINTER; // false
 Season.SPRING == Season.SPRING; // true

也可以通过equals对比:

代码语言:javascript
复制
Season.FALL.equals(Season.FALL); // true 
Season.FALL.equals(Season.WINTER); // false
代码语言:javascript
复制
二、枚举的构造函数

枚举不能具有公共构造函数;但是,私有构造函数是可以接受的(枚举的构造函数默认是包私有的):

代码语言:javascript
复制
public enum Coin {
   PENNY(1), NICKEL(5), DIME(10), QUARTER(25); 
   //注意,上面的括号和构造函数参数匹配
   private int value;
   Coin(int value) { 
       this.value = value;
   }
   public int getValue() { 
       return value;
   } 
}
代码语言:javascript
复制
int p = Coin.NICKEL.getValue(); // p = 5

如果你要实现一个枚举作为一个类,可以这样写:

代码语言:javascript
复制
public class Coin<T extends Coin<T>> implements Comparable<T>, Serializable { 
    public static final Coin PENNY = new Coin(1);
    public static final Coin NICKEL = new Coin(5);
    public static final Coin DIME = new Coin(10);
    public static final Coin QUARTER = new Coin(25); 
    
    private int value;
    private Coin(int value) { 
        this.value = value;
    }
    public int getValue() {
        return value;
    } 
}
代码语言:javascript
复制
int p = Coin.NICKEL.getValue(); // p = 5

枚举常量在技术上是可变的,因此可以添加setter来更改枚举常量的内部结构。然而,这被认为是非常不好的做法,应该避免。

最佳实践是使枚举字段不可变,使用final:

代码语言:javascript
复制
public enum Coin {
    PENNY(1), NICKEL(5), DIME(10), QUARTER(25);
    private final int value;
    Coin(int value){
        this.value = value;
    }
...
}

也可以在同一个枚举中定义多个构造函数。这样做的话,你在enum声明中传递的参数将决定调用哪个构造函数:

代码语言:javascript
复制
public enum Coin {
    PENNY(1, true), NICKEL(5, false), DIME(10), QUARTER(25);
    private final int value;
    private final boolean isCopperColored;
    Coin(int value){ 
        this(value, false);
    }
    Coin(int value, boolean isCopperColored){ 
        this.value = value; 
        this.isCopperColored = isCopperColored;
    }
...
}

注意:所有非基本枚举字段都应该实现Serializable,因为枚举类实现了序列化。

三、带有抽象方法的枚举

枚举可以定义抽象方法,每个枚举成员都需要实现抽象方法。

代码语言:javascript
复制
 enum Action { 
   DODGE {
       public boolean execute(Player player) { 
           return player.isAttacking();
       } 
   },
   ATTACK {
       public boolean execute(Player player) {
           return player.hasWeapon(); 
       }
   }, 
   JUMP {
       public boolean execute(Player player) {
           return player.getCoordinates().equals(new Coordinates(0, 0));
       } 
   };
   public abstract boolean execute(Player player); 
}

这允许每个enum成员为给定的操作定义自己的行为,而不必在顶级定义中的方法中切换类型。

注意,此模式是使用多态性和/或实现接口通常实现的一种简短形式。

四、实现接口

下面代码, 是一个enum,也是一个可调用函数,它根据预编译的正则表达式模式测试字符串输入。

代码语言:javascript
复制
import java.util.regex.Pattern;
import java.util.function.Predicate;

enum RegEx implements Predicate<String> {
   UPPER("[A-Z]+"), LOWER("[a-z]+"), NUMERIC("[+-]?[0-9]+");
   private final Pattern pattern;

   private RegEx(final String pattern) {
       this.pattern = Pattern.compile(pattern);
   }

   @Override
   public boolean test(final String input) {
       return this.pattern.matcher(input).matches();
   }
}
代码语言:javascript
复制
public class Main {
    public static void main(String[] args) {
        System.out.println(RegEx.UPPER.test("ABC"));
        System.out.println(RegEx.LOWER.test("abc"));
        System.out.println(RegEx.NUMERIC.test("+111"));
    }
}

enum的每个成员也可以实现该方法:

代码语言:javascript
复制
import java.util.function.Predicate;

enum Acceptor implements Predicate<String> {
    NULL {
        @Override
        public boolean test(String s) {
            return s == null;
        }
    },
    EMPTY {
        @Override
        public boolean test(String s) {
            return s.equals("");
        }
    },
    NULL_OR_EMPTY {
        @Override
        public boolean test(String s) {
            return NULL.test(s) || EMPTY.test(s);
        }
    };
}
代码语言:javascript
复制
public class Main {
    public static void main(String[] args) {
        System.out.println(Acceptor.NULL.test(null)); // true System.out.println(Acceptor.EMPTY.test("")); // true System.out.println(Acceptor.NULL_OR_EMPTY.test(" ")); // false
    }
}

五、使用单元素枚举实现单例模式

枚举常量在第一次引用枚举时实例化。因此,这允许使用单元素枚举实现单例设计模式。

代码语言:javascript
复制
public enum Attendant { 
   INSTANCE;
   private Attendant() {
   // 初始化
   }
   public void sayHello() { 
       System.out.println("Hello!");
   } 
}
代码语言:javascript
复制
public class Main {
    public static void main(String... args) { 
        Attendant.INSTANCE.sayHello();// instantiated at this point
    } 
}

这种方法实现单例模式有以下优点:

  • 线程安全
  • 单一实例化的保证
  • 开箱即用的序列化

如实现接口一节所示,这个单例也可能实现一个或多个接口。

六、使用方法和静态块

枚举可以像任何类一样包含方法。为了了解它是如何工作的,我们将像这样声明一个枚举:

代码语言:javascript
复制
public enum Direction { 
    NORTH, SOUTH, EAST, WEST;
}

写一个方法,返回枚举在相反的方向:

代码语言:javascript
复制
public enum Direction { 
    NORTH, SOUTH, EAST, WEST;
    public Direction getOpposite(){ 
        switch (this){
            case NORTH: 
                return SOUTH;
            case SOUTH: 
                return NORTH;
            case WEST: 
                return EAST;
            case EAST: 
                return WEST;
            default: 
                return null;
        } 
    }
}

可以通过使用字段和静态初始化块进一步改进:

代码语言:javascript
复制
public enum Direction { 
    NORTH, SOUTH, EAST, WEST;
    private Direction opposite;
    public Direction getOpposite(){ 
        return opposite;
    }
    static {
        NORTH.opposite = SOUTH; 
        SOUTH.opposite = NORTH; 
        WEST.opposite = EAST; 
        EAST.opposite = WEST;
    } 
}

在本例中,反向存储在一个私有实例字段opposite中,该字段在第一次使用方向时被静态初始化。

在这种特殊的情况下(因为NORTH引用SOUTH,反之亦然),我们不能在这里将枚举与构造函数一起使用(构造函数NORTH(SOUTH)、SOUTH(NORTH)、SOUTH(NORTH)、EAST(WEST)、WEST(EAST)会更优雅,并且允许将opposite声明为final,但它是自引用的,因此是不允许的)。

七、枚举的多态性

当一个方法需要接受一组“可扩展”的枚举值时,可以像在普通类上应用多态性一样,创建一个接口,在枚举应使用的任何地方都可以使用这个接口:

代码语言:javascript
复制
public interface ExtensibleEnum { 
    String name();
}

这样,任何由接口(implementing)标记的枚举都可以用作参数,从而允许我们创建方法可接受的可变数量的枚举。这可能很有用,例如,在api中存在一个默认的(不可修改的)枚举,并且这些api的用户希望用更多的值“扩展”枚举。

一组默认enum值可以定义如下:

代码语言:javascript
复制
public enum DefaultValues implements ExtensibleEnum { 
    VALUE_ONE, VALUE_TWO;
}

然后可以这样定义附加值:

代码语言:javascript
复制
public enum ExtendedValues implements ExtensibleEnum { 
    VALUE_THREE, VALUE_FOUR;
}

演示如何使用枚举的示例——注意printEnum()如何接受来自这两种枚举类型的值:

代码语言:javascript
复制
private void printEnum(ExtensibleEnum val) { 
    System.out.println(val.name());
}
代码语言:javascript
复制
printEnum(DefaultValues.VALUE_ONE); // VALUE_ONE
printEnum(DefaultValues.VALUE_TWO); // VALUE_TWO
printEnum(ExtendedValues.VALUE_THREE); // VALUE_THREE
printEnum(ExtendedValues.VALUE_FOUR); // VALUE_FOUR

八、按名称获取枚举常数

我们定义一个一周的枚举:

代码语言:javascript
复制
enum DayOfWeek {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}

枚举使用内置方法valueOf(),该方法可用于根据常量的名称查找该常量:

代码语言:javascript
复制
String dayName = DayOfWeek.SUNDAY.name(); 
assert dayName.equals("SUNDAY");
DayOfWeek day = DayOfWeek.valueOf(dayName); 
assert day == DayOfWeek.SUNDAY;

这也可以使用动态枚举类型:

代码语言:javascript
复制
Class<DayOfWeek> enumType = DayOfWeek.class; 
DayOfWeek day = Enum.valueOf(enumType, "SUNDAY"); 
assert day == DayOfWeek.SUNDAY;

如果指定的枚举没有具有匹配名称的常量,这两个valueOf()方法都会抛出IllegalArgumentException。

Guava库提供了一个helper方法Enums.getIfPresent(),它返回一个可选的

Guava,以消除显式异常处理:

代码语言:javascript
复制
DayOfWeek defaultDay = DayOfWeek.SUNDAY;
DayOfWeek day = Enums.getIfPresent(DayOfWeek.class, "INVALID").or(defaultDay); 
assert day == DayOfWeek.SUNDAY;

总结

enum 类型提出给 JAVA 编程带了了极大的便利,让程序的控制更加的容易,也不容易出现错误。所以在遇到需要控制程序流程时候,可以多想想是否可以利用 enum 来实现。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 终身幼稚园 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 三、带有抽象方法的枚举
  • 四、实现接口
  • 五、使用单元素枚举实现单例模式
  • 六、使用方法和静态块
  • 七、枚举的多态性
  • 八、按名称获取枚举常数
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档