必须了解的高阶JAVA枚举特性!

JAVA枚举,比你想象中还要有用!

我经常发现自己在Java中使用枚举来表示某个对象的一组潜在值。

在编译时确定类型可以具有什么值的能力是一种强大的能力,它为代码提供了结构和意义。

当我第一次了解枚举时,当时我认为它们只是一个为常量命名的工具,可以很容易地被静态常量字符串ENUM_VAL_NAME所取代。

后来我发现我错了。事实证明,Java枚举具有相当高级的特性,可以使代码干净、不易出错,功能强大。

让我们一起来看看Java中的一些高级枚举特性,以及如何利用这些特性使代码更简单、更可读。

枚举是类!

在Java中,枚举是Object的一个子类。让我们看看所有枚举的基类,Enum(为简洁起见进行了修改)。

public abstract class Enum<E extends Enum<E>>
  implements Constable, Comparable<E>, 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();
 }

 public final int compareTo(E o) {
   Enum<?> other = (Enum<?>)o;
   Enum<E> self = this;
   if (self.getClass() != other.getClass() && // optimization
     self.getDeclaringClass() != other.getDeclaringClass())
     throw new ClassCastException();
   return self.ordinal - other.ordinal;
 }
}

我们可以看到,这基本上只是一个常规的抽象类,有两个字段,name和ordinal。

所以说枚举都是类,所以它们具有常规类的许多特性。

我们能够为枚举提供实例方法、构造函数和字段。我们可以重写toString(),但不能重写hashCode()或equals(Object other)。

接下来我们看下我们的枚举示例,Operation

 enum Operation {
  ADD,
  SUBTRACT,
  MULTIPLY
 }

这个枚举表示一个Operation可以对两个值执行,并将生成一个结果。关于如何实现此功能,您最初的想法可能是使用switch语句,如下所示:

 public int apply(Operation operation, int arg1, int arg2) {
  switch(operation) {
   case ADD:
    return arg1 + arg2;
   case SUBTRACT:
    return arg1 - arg2;
   case MULTIPLY:
    return arg1 * arg2;
   default:
    throw new UnsupportedOperationException();
 }
}

当然,这样子会有一些问题。

第一个问题是,如果我们将一个新操作添加到我们的枚举Operation中,编译器不会通知我们这个开关不能正确处理新操作。

更糟糕的是,如果一个懒惰的开发人员在另一个类中复制或重新编写这些代码,我们可能无法更新它。

第二个问题是默认情况default,每段程序里面都是必需的,尽管我们知道在正确的代码里它永远不会发生。

这是因为Java编译器知道上面的第一个问题,并且希望确保我们能够处理在不知情的情况下向Operation中添加了新枚举。

还好,Java8用函数式编程为我们提供了一个干净的解决方案。

函数枚举实现

因为枚举是类,所以我们可以创建一个枚举字段来保存执行操作的函数。

但是在我们找到解决方案之前,让我们先来看看一些重构。

首先,让我们把开关放在enum类中。

enum Operation {
 ADD,
 SUBTRACT,
 MULTIPLY;

 public static int apply(Operation operation, int arg1, int arg2) {
  switch(operation) {
   case ADD:
    return arg1 + arg2;
   case SUBTRACT:
    return arg1 - arg2;
   case MULTIPLY:
    return arg1 * arg2;
   default:
    throw new UnsupportedOperationException();
  }
 }
}

我们可以这样做:Operation.apply(Operation.ADD, 2, 3);

因为我们现在从Operation中调用方法,所以我们可以将其更改为实例方法并使用this,而不是用Operation.apply()来实现,如下所示:

public int apply(int arg1, int arg2) {
 switch(this) {
  case ADD:
   return arg1 + arg2;
  case SUBTRACT:
   return arg1 - arg2;
  case MULTIPLY:
   return arg1 * arg2;
  default:
   throw new UnsupportedOperationException();
 }
}

像这样使用:Operation.ADD.apply(2, 3);

看起来变好了。现在让我们更进一步,通过使用函数式编程完全消除switch语句。

enum Operation {
       ADD((x, y) -> x + y),
       SUBTRACT((x, y) -> x - y),
       MULTIPLY((x, y) -> x * y);

       Operation(BiFunction<Integer, Integer, Integer> operation) {
           this.operation = operation;
       }

       private final BiFunction<Integer, Integer, Integer> operation;

       public int apply(int x, int y) {
           return operation.apply(x, y);
       }

 }

这里我做的是:

  • 添加了一个字段 BiFunction<Integer, Integer, Integer> operation
  • 用BiFunction创建了用于Operation的构造函数。
  • 调用枚举定义中的构造函数,并用lambda指定BiFunction<Integer, Integer, Integer>。

这个java.util.function.BiFunction operation字段是对采用两个参数的函数(方法)的引用。

在我们的例子中,两个参数都是int型,返回值也是int型。不幸的是,Java参数化类型不支持原语,所以我们必须使用Integer。

因为BiFunction是用@functioninterface注释的,所以我们可以使用Lambda表示法定义一个。

因为我们的函数接受两个参数,所以我们可以使用(x,y)来指定它们。

然后我们定义了一个单行方法,它使用 ->x+y 返回一个值。这相当于下面的方法,只是更简洁而已。

 class Adder implements BiFunction<Integer, Integer, Integer> {
     @Override
     public Integer apply(Integer x, Integer y) {
         return x + y;
  }
 }

我们的新Operation实现采用相同的方式:Operation.ADD.apply(2, 3);.

但是,这种实现更好,因为编译器会告诉我们何时添加了新Operation,这要求我们更新新函数。如果没有这一点,如果我们在添加新Operation时还不记得更新switch语句,就有可能得到UnsupportedOperationException()。

关键要点

  • Enum枚举是Enum的扩展类。
  • Enum枚举可以有字段、构造函数和实例方法。
  • Enum枚举字段可以存储函数。与lambdas配合使用,可以创建干净、安全的特定于枚举的函数实现,并在编译时强制执行它们(而不是使用switch)。

下面是这个示例的GitHub地址。(https://github.com/alex-power/java-enum-example

本文参考:https://medium.com/javarevisited/advanced-java-enum-features-you-need-to-know-b516a191c7e2

以上就是必须了解的高阶JAVA枚举特性!的详细内容,更多关于高阶JAVA枚举特性的资料请关注我们其它相关文章!

(0)

相关推荐

  • 秒懂Java枚举类型(enum)

    理解枚举类型 枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性.安全性以及便捷性.下面先来看看什么是枚举?如何定义枚举? 枚举的定义 上述的常量定义常量的方式称为int枚举模式,这样的定义方式并没有什么错,但它存在许多不足,如在类型安全和使用方便性上并没有多少好处,如果存在定义int值相同的变量,混淆的几率还是很大的,编译器也不会提出任何警告,因此这种方式在枚举

  • java 枚举enum的用法(与在switch中的用法)

    实际开发中,很多人可能很少用枚举类型.更多的可能使用常量的方式代替.但枚举比起常量来说,含义更清晰,更容易理解,结构上也更加紧密.看其他人的博文都很详细,长篇大论的,这里理论的东西不说了,一起看看在实际开发中比较常见的用法,简单明了. 看看枚举类 /** * 操作码类 * @author kokJuis * @version 1.0 * @date 2017-3-6 */ public enum Code { SUCCESS(10000, "操作成功"), FAIL(10001, &q

  • Java枚举类型在switch语句正确使用方法详解

    很多人也许会尝试写下这样的代码: ResultStructureEnum type = ResultStructureEnum.valueOf(userType); switch (type) { case ResultStructureEnum.STUDENT: ... break; case ResultStructureEnum.TEACHER: ... break; case ResultStructureEnum.PARENT: ... break; ... } # 这样编译不会通过,

  • 一文搞懂JAVA 枚举(enum)

    Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等. Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割. 例如定义一个颜色的枚举类. enum Color { RED, GREEN, BLUE; } 以上枚举类 Color 颜色常量有 RED, GREEN, BLUE,分别表示红色,绿色,蓝色. 使用实例: enum Color { RED, GREEN, BLUE; } public c

  • 一篇文章让你三分钟学会Java枚举

    什么是枚举 至于枚举,我们先拿生活中的枚举来入手,然后再引申Java中的枚举,其实它们的意义很相似. 谈到生活中的枚举,假如我们在玩掷骰子的游戏,在我们手中有两个骰子,要求掷出两个骰子的点数和必须大于6的概率,那么在此情此景,我们就需要使用枚举法一一列举出骰子点数的所有可能,然后根据列举出来的可能,求出概率. 可能有的小伙伴发现,这就是数学啊?这就是数学中的概率学和统计学.对,我们的枚举法就是常用于概率统计中的. 枚举类enum是jdk1.5引入的,全称enumeration,和class.in

  • Java枚举类使用场景及实例解析

    为什么要用枚举类 什么场景会用到枚举,比如在表示一周的某一天,一年中的四季,这样一组常量的时候我们会用到枚举.在Java引入枚举类之前常用一组int常量来表示枚举,这种方式称为int枚举模式(int enum pattern). private static final int MONDAY = 1; private static final int TUESDAY = 2; private static final int WEDNESDAY = 3; private static final

  • Jackson优雅序列化Java枚举类过程解析

    1. 前言 在Java开发中我们为了避免过多的魔法值,使用枚举类来封装一些静态的状态代码.但是在将这些枚举的意思正确而全面的返回给前端却并不是那么顺利,我们通常会使用Jackson类库序列化对象为JSON,今天就来讲一个关于使用Jackson序列化枚举的通用性技巧. 2. 通用枚举范式 为了便于统一处理和规范统一的风格,建议指定一个统一的抽象接口,例如: /** * The interface Enumerator. */ public interface Enumerator { /** *

  • Java手动方式创建枚举类示例

    本文实例讲述了Java手动方式创建枚举类.分享给大家供大家参考,具体如下: 一 点睛 可以采用如下设计方式手动创建枚举类 通过private将构造器隐藏起来. 把这个类的所有可能实例都使用public static final属性来保存. 如果有必要,可以提供一些静态方法,允许其他程序根据特定参数来获取与之匹配实例. 二 代码 1 Season.java public class Season { // 把Season类定义成不可变的,将其成员变量也定义成final的 private final

  • 必须了解的高阶JAVA枚举特性!

    JAVA枚举,比你想象中还要有用! 我经常发现自己在Java中使用枚举来表示某个对象的一组潜在值. 在编译时确定类型可以具有什么值的能力是一种强大的能力,它为代码提供了结构和意义. 当我第一次了解枚举时,当时我认为它们只是一个为常量命名的工具,可以很容易地被静态常量字符串ENUM_VAL_NAME所取代. 后来我发现我错了.事实证明,Java枚举具有相当高级的特性,可以使代码干净.不易出错,功能强大. 让我们一起来看看Java中的一些高级枚举特性,以及如何利用这些特性使代码更简单.更可读. 枚举

  • 详解Java高阶语法Volatile

    背景:听说Volatile Java高阶语法亦是挺进BAT的必经之路. Volatile: volatile同步机制又涉及Java内存模型中的可见性.原子性和有序性,恶补基础一波. 可见性: 可见性简单的说是线程之间的可见性,一个线程修改的状态对另一个线程是可见对,也就是一个线程的修改结果另一个线程可以马上看到:但通常,我们无法确保执行读操作的线程能够时时的看到其他线程写入的值,So为了确保多个线程之间对内存写入操作可见性,必须使用同步机制:如用volatile修饰的变量就具有可见性,volat

  • python高级特性和高阶函数及使用详解

    python高级特性 1.集合的推导式 •列表推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:[exp for item in collection if codition] if codition - 可选 •字典推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:{key_exp:value_exp for item in collection if codition} •集合推导式 语法:{exp for item in collection if

  • Java枚举学习之定义和基本特性详解

    目录 枚举的定义 1.题目 2.解题思路 3.代码详解 枚举的基本特性 1.题目 2.解题思路 3.代码详解 4.多写一个知识点 增加枚举元素信息 1.题目 2.解题思路 3.代码详解 枚举的定义 1.题目 枚举是JAVA 5.0后增加的一个重要类型.可以用来表示一组取值范围固定的变量.使用enum关键字,可以定义枚举类型. 实现:使用反射查看枚举的修饰符,父类和自定义方法. 2.解题思路 创建一个枚举:Position 定义两个元素,来表示方位. 对于枚举的元素命名方式:全部为大写字母. 创建

  • Python的函数的一些高阶特性

    高阶函数英文叫Higher-order function.什么是高阶函数?我们以实际代码为例子,一步一步深入概念. 变量可以指向函数 以Python内置的求绝对值的函数abs()为例,调用该函数用以下代码: >>> abs(-10) 10 但是,如果只写abs呢? >>> abs <built-in function abs> 可见,abs(-10)是函数调用,而abs是函数本身. 要获得函数调用结果,我们可以把结果赋值给变量: >>> x

  • JavaScript高阶函数_动力节点Java学院整理

    高阶函数英文叫Higher-order function.那么什么是高阶函数? JavaScript的函数其实都指向某个变量.既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数. 一个最简单的高阶函数: function add(x, y, f) { return f(x) + f(y); } 当我们调用add(-5, 6, Math.abs)时,参数x,y和f分别接收-5,6和函数Math.abs,根据函数定义,我们可以推导计算过程为

  • 谈谈你可能并不了解的java枚举

    前言 枚举在java里也算个老生长谈的内容了,每当遇到一组需要类举的数据时我们都会自然而然地使用枚举类型: public enum Color { RED, GREEN, BLUE, YELLOW; public static void main(String[] args) { Color red = Color.RED; Color redAnother = Color.RED; Color blue = Color.BLUE; System.out.println(red.equals(r

  • swift 常用高阶函数分享

    map var arr = [1, 2, 3] //map函数是有返回值的,想要arr里面的值map过去需要arr重新接收新值 arr.map { (a : Int) -> Int in return a * 2 } //这种写法只是尾随闭包的简写.. arr = arr.map { $0 * 2 } flatMap //floatMap函数可以降维 var arr1 = [[1, 2], [4, 5], [6, 7]] var aaa = arr1.flatMap { $0 } //float

  • SprinBoot如何集成参数校验Validator及参数校验的高阶技巧

    目录 为什么需要参数校验 SpringBoot中集成参数校验 第一步,引入依赖 第二步,定义要参数校验的实体类 第三步,定义校验类进行测试 第四步,体验效果 参数异常加入全局异常处理器 体验效果 自定义参数校验 第一步,创建自定义注解 第二步,自定义校验逻辑 第三步,在字段上增加注解 第四步,体验效果 分组校验 第一步:定义分组接口 第二步,在模型中给参数分配分组 第三步,给需要参数校验的方法指定分组 第四步,体验效果 小结 前几天写了一篇<SpringBoot如何统一后端返回格式?老鸟们都是这

随机推荐