Scala 枚举介绍及深入应用
本文详细地总结了Scala枚举的几种实现方式,对我们更好地进行函数式编程有很好地指导和帮助。
Scala 枚举示例和特性
枚举(Enumerations)是一种语言特性,对于建模有限的实体集来说特别有用。一个经典的例子是将工作日建模为一个枚举:每个七天都有一个值。Scala
和许多其他语言一样,提供了一种表示枚举的方法:
object Weekday extends Enumeration {
val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}
现在我们可以准确清晰地表示工作日,而无需使用String
或Int
等基本类型。Scala
枚举还提供了一组有用的特性:
- 序列化和反序列化方法(Serialize and Deserialize methods),这些方法也会抛出异常:(:
scala> Weekday.Monday.toString
res0: String = Monday
scala> Weekday.withName("Monday")
res1: Weekday.Value = Monday
scala> Weekday.withName("Mondai")
java.util.NoSuchElementException: No value found for 'Mondai'
at scala.Enumeration.withName(Enumeration.scala:124)
... 32 elided
- 提供可读性(human-readable value)的值:
object Weekday extends Enumeration {
val Monday = Value("Mo.")
val Tuesday = Value("Tu.")
val Wednesday = Value("We.")
val Thursday = Value("Th.")
val Friday = Value("Fr.")
val Saturday = Value("Sa.")
val Sunday = Value("Su.")
}
scala> Weekday.Monday.toString
res0: String = Mo.
- 列出所有可能的值:
scala> Weekday.values
res0: Weekday.ValueSet = Weekday.ValueSet(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
- 排序(Ordering)。默认情况下,枚举值是按照声明的顺序排序的,排序顺序可以通过覆盖(overridden)原来枚举值的方式改变:
object Weekday extends Enumeration {
val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}
// 按照枚举值声明的顺序排序
scala> Weekday.values.toList.sorted
res0: List[Weekday.Value] = List(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
object Weekday extends Enumeration {
val Monday = Value(1)
val Tuesday = Value(2)
val Wednesday = Value(3)
val Thursday = Value(4)
val Friday = Value(5)
val Saturday = Value(6)
val Sunday = Value(0)
}
// 按照枚举对应的数字值排序
scala> Weekday.values.toList.sorted
res1: List[Weekday.Value] = List(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
scala.Enumeration的问题
然而,这种方法有一些问题。主要有两个缺点:
- 擦除(erasure)后枚举具有相同的类型:
object Weekday extends Enumeration {
val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}
object OtherEnum extends Enumeration {
val A, B, C = Value
}
def test(enum: Weekday.Value) = {
println(s"enum: $enum")
}
def test(enum: OtherEnum.Value) = {
println(s"enum: $enum")
}
<console>:25: error: double definition:
def test(enum: Weekday.Value): Unit at line 21 and
def test(enum: OtherEnum.Value): Unit at line 25
have same type after erasure: (enum: Enumeration#Value)Unit
def test(enum: OtherEnum.Value) = {
^
- 在编译期间没有详尽的匹配检查(matching check)。下面的示例将在没有任何警告的情况下编译,但是在对周一和周日以外的工作日匹配时会抛出scala.MatchError异常:
def nonExhaustive(weekday: Weekday.Value) {
weekday match {
case Monday => println("I hate Mondays")
case Sunday => println("The weekend is already over? :( ")
}
}
在Scala中,我们严重依赖于编译器强大的类型系统,使用这种方法,编译器不能找到非穷尽模式匹配子句,也不能对不同的枚举使用重载方法。
为了避免这种问题,我们可以其他办法实现枚举:
- 使用密封盒对象(sealed case objects)
- 分项(itemized)
- enumeratum
Sealed case objects
如果您决定使用sealed case objects
,Scala
编译器可以解决Scala
枚举中存在的两个问题。编译器既可以检测非穷尽模式匹配,也可以避免类型擦除问题。
sealed trait Weekday
case object Monday extends Weekday
case object Tuesday extends Weekday
case object Wednesday extends Weekday
case object Thursday extends Weekday
case object Friday extends Weekday
case object Saturday extends Weekday
case object Sunday extends Weekday
def test(weekday: Weekday) = {
weekday match {
case Monday => println("I hate Mondays")
case Sunday => println("The weekend is already over? :( ")
}
}
<console>:15: warning: match may not be exhaustive.
It would fail on the following inputs: Friday, Saturday, Thursday, Tuesday, Wednesday
weekday match {
^
test: (weekday: Weekday)Unit
另一个非常好的特性是,可以在枚举值中包含更多字段(Scala enumerations only provides an index and a name),仅仅使用sealed abstract class
而不是sealed trait
。
sealed abstract class Weekday( val name: String,
val abbreviation: String,
val isWorkDay: Boolean)
case object Monday extends Weekday("Monday", "Mo.", true)
case object Tuesday extends Weekday("Tuesday", "Tu.", true)
case object Wednesday extends Weekday("Wednesday", "We.", true)
case object Thursday extends Weekday("Thursday", "Th.", true)
case object Friday extends Weekday("Friday", "Fr.", true)
case object Saturday extends Weekday("Saturday", "Sa.", false)
case object Sunday extends Weekday("Sunday", "Su.", false)
sealed case objects的问题
但是这种方式也有它自己的问题:
- 没有检索所有枚举值的简单方法
- 没有默认的序列化/反序列化方法
- 枚举值之间没有默认的排序——这可以通过包含一些关于值的信息来手动实现,示例如下:
sealed abstract class Weekday( val name: String,
val abbreviation: String,
val isWeekDay: Boolean,
val order: Int) extends Ordered[Weekday] {
def compare(that: Weekday) = this.order - that.order
}
case object Monday extends Weekday("Monday", "Mo.", true, 2)
case object Tuesday extends Weekday("Tuesday", "Tu.", true, 3)
case object Wednesday extends Weekday("Wednesday", "We.", true, 4)
case object Thursday extends Weekday("Thursday", "Th.", true, 5)
case object Friday extends Weekday("Friday", "Fr.", true, 6)
case object Saturday extends Weekday("Saturday", "Sa.", false, 7)
case object Sunday extends Weekday("Sunday", "Su.", false, 1)
scala> Monday < Tuesday
res0: Boolean = true
分项(itemized)
itemized是一个OSS lib,它是rbrick的一部分,rbricks是一种可组合的、占用空间小的Scala
库的集合。
itemized
为枚举提供了密封特质层次结构(sealed trait hierarchies)的宏和类型类,回到我们之前的例子:
import io.rbricks.itemized.annotation.enum
@enum trait Weekday {
object Monday
object Tuesday
object Wednesday
object Thursday
object Friday
object Saturday
object Sunday
}
除上面的以外,itemized
还有其他的一些特性:
- 列出所有枚举值
- 默认的序列化/反序列化方法
scala> import io.rbricks.itemized.ItemizedCodec
scala> ItemizedCodec[Weekday].fromRep("Monday")
res0: Option[Weekday] = Some(Monday)
scala> val weekday: Weekday = Planet.Monday
scala> import io.rbricks.itemized.ItemizedCodec.ops._
scala> weekday.toRep
res1: String = Earth
itemized的问题
尽管itemized
可以让我们用注解方式创建类型安全的枚举,但是它也有一些不足:
- 无法向枚举值添加更多字段(add more fields to enumeration values)。由于部分工作是由宏完成的,在这一点上,无法传递这些值
- 尽管它提供了索引值,但枚举值仍然没有默认顺序(order on enumeration values)
enumeratum
Enumeratum是一个类型安全且功能强大的Scala
枚举实现,它提供了详尽的模式匹配警告。
import enumeratum._
sealed trait Weekday extends EnumEntry
object Weekday extends Enum[Weekday] {
val values = findValues // mandatory due to Enum extension
case object Monday extends Weekday
case object Tuesday extends Weekday
case object Wednesday extends Weekday
case object Thursday extends Weekday
case object Friday extends Weekday
case object Saturday extends Weekday
case object Sunday extends Weekday
}
def test(weekday: Weekday) = {
weekday match {
case Weekday.Monday => println("I hate Mondays")
case Weekday.Sunday => println("The weekend is already over? :( ")
}
}
<console>:18: warning: match may not be exhaustive.
It would fail on the following inputs: Friday, Saturday, Thursday, Tuesday, Wednesday
weekday match {
^
test: (weekday: Weekday)Unit
除了非详尽的模式匹配警告,enumeratum
还提供:
- 列出可能的值(因为这些值需要在
Enum
继承上实现) - 默认的序列化/反序列化方法(有和没有异常抛出)
scala> Weekday.withName("Monday")
res0: Weekday = Monday
scala> Weekday.withName("Momday")
java.util.NoSuchElementException: Momday is not a member of Enum (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
at enumeratum.Enum$$anonfun$withName$1.apply(Enum.scala:82)
at enumeratum.Enum$$anonfun$withName$1.apply(Enum.scala:82)
at scala.Option.getOrElse(Option.scala:121)
at enumeratum.Enum$class.withName(Enum.scala:81)
at Weekday$.withName(<console>:13)
... 43 elided
scala> Weekday.withNameOption("Monday")
res2: Option[Weekday] = Some(Monday)
scala> Weekday.withNameOption("Momday")
res3: Option[Weekday] = None
- 向枚举添加额外的值。它非常类似于我们给简单的密封盒对象添加额外的值
sealed abstract class Weekday( val name: String,
val abbreviation: String,
val isWorkDay: Boolean) extends EnumEntry
case object Weekday extends Enum[Weekday] {
val values = findValues
case object Monday extends Weekday("Monday", "Mo.", true)
case object Tuesday extends Weekday("Tuesday", "Tu.", true)
case object Wednesday extends Weekday("Wednesday", "We.", true)
case object Thursday extends Weekday("Thursday", "Th.", true)
case object Friday extends Weekday("Friday", "Fr.", true)
case object Saturday extends Weekday("Saturday", "Sa.", false)
case object Sunday extends Weekday("Sunday", "Su.", false)
}
- 排序可以通过与封闭层次(sealed hierarchies)结构相同的方式实现。只需与有序[]特质(trait)混合,并实现比较方法。
sealed abstract class Weekday(val order: Int) extends EnumEntry with Ordered[Weekday] {
def compare(that: Weekday) = this.order - that.order
}
object Weekday extends Enum[Weekday] {
val values = findValues
case object Monday extends Weekday(2)
case object Tuesday extends Weekday(3)
case object Wednesday extends Weekday(4)
case object Thursday extends Weekday(5)
case object Friday extends Weekday(6)
case object Saturday extends Weekday(7)
case object Sunday extends Weekday(1)
}
- 支持一些库和框架,这些库和框架在更大的应用程序上非常有用。项目文档链接:https://github.com/lloydmeta/enumeratum#table-of-contents
总结
如果您刚刚开始学习Scala
,我建议使用scala.Enumeration
的方式实现枚举。当您觉得使用更多Scala
特性更舒服时,以及开始享受编译器安全性时,可以试试其他方式实现枚举。我的两个建议是:
- 如果您不想依赖于外部库,就使用
sealed hierarchies
- 使用
enumeratum
,因为它提供了这里提到的所有特性
枚举特性总结
- 详尽的模式匹配
- 没有类型擦除
- 安全的序列化/反序列化的默认方法
- 列出所有可能的值
- 在枚举值上添加额外的字段
- 排序
如果您想看到更多的替代方法,请查看Scala
枚举的后续内容—Scala Enumerations - Return of the (Java) Jedi
Scala 枚举介绍及深入应用的更多相关文章
- Scala 入门介绍
1 基础 1.1 Scala 解释器 REPL - 交互式解释器环境 R(read).E(evaluate).P(print).L(loop) 输入值,交互式解释器会读取输入内容并对它求值,再返回结果 ...
- Scala语言介绍一
为什么学习scala语言 Scala是基于JVM的语言,与java语言类似,java语言是基于JVM的面向对象的语言,Scala也是基于JVM,同时支持面向对象和面向函数的编程语言.Spark底层的源 ...
- Scala枚举--Enumeration
object Color extends Enumeration(2){ val Red,Green,Blue = Value val Yellow = Value("YELLOW" ...
- scala.的Enumeration枚举示例(转)
简介 在scala中没有枚举类型,但在标准类库中提供了Enumeration类来产出枚举.扩展Enumeration类后,调用value方法类初始化枚举中的可能值. 内部类value实际上是一个抽象类 ...
- Scala详解
1 快速入门... 4 1.1 分号... 4 1.2 常变量声明... 4 1.2.1 val常量... 4 1.2.2 ...
- Scala 基础(十六):泛型、类型约束-上界(Upper Bounds)/下界(lower bounds)、视图界定(View bounds)、上下文界定(Context bounds)、协变、逆变和不变
1 泛型 1)如果我们要求函数的参数可以接受任意类型.可以使用泛型,这个类型可以代表任意的数据类型. 2)例如 List,在创建 List 时,可以传入整型.字符串.浮点数等等任意类型.那是因为 Li ...
- Scala快速概览
IDEA工具安装及scala基本操作 目录 一. 1. 2. 3. 4. 二. 1. 2. 3. 三. 1. 2. 3. 4. 5. 6. 7. 四. 1. (1) (2) (3) (4) (5) ( ...
- OC中的枚举类型
背景 一直对OC中的位移操作枚举不太理解,查找到两篇介绍OC中枚举的文章,觉得很不错. 什么是位移操作枚举呢? typedef NS_OPTIONS(NSUInteger, UIViewAutores ...
- 了解Scala 宏
前情回顾 了解Scala反射介绍了反射的基本概念以及运行时反射的用法, 同时简单的介绍了一下编译原理知识, 其中我感觉最为绕的地方, 就属泛型的几种使用方式了. 而最抽象的概念, 就是对于符号和抽象树 ...
随机推荐
- 一篇文章带你了解Cloud Native
背景 Cloud Native表面看起来比较容易理解,但是细思好像又有些模糊不清:Cloud Native和Cloud关系是啥?它用来解决什么问题?它是一个新技术还是一个新的方法?什么样的APP符合“ ...
- 食物链-HZUN寒假集训
食物链 总时间限制: 1000ms 内存限制: 65536kB 描述 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动 ...
- 如何用plugman编辑和添加cordova插件
1.安装工具 进入nodejs, 安装工具plugman,管理插件,输入命令npm install -g plugman 等待下载安装 2.使用plugman命令生成插件框架 cmd 进入用于生成插件 ...
- java web 开发实战经典(一)
一.jsp三种Scriptlet(脚本小程序) 1.<% %> :定义局部变量.编写语句等. <% String str = "hello world!";// ...
- ArcCore重构-生成%_offset.h文件
基于官方arc-stable-9c57d86f66be,AUTOSAR版本3.1.5 基本问题 ArcCore中,需要生成asm_offset.h和arch_offset.h这两个头文件,定义着代 ...
- Flask表单(Flask-WTF)
1.request.from获取POST表单数据 # hello.py #coding:utf-8 from flask import Flask,request,render_template ap ...
- Java (三、数组)
Java 数组 数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同. Java 语言中提供的数组是用来存储固定大小的同类型元素. 声明数组变量 首先必须声明数组 ...
- 适合Python 新手的5大练手项目,你练了么?
接下来就给大家介绍几种适合新手的练手项目. 0.算法系列-排序与查找 Python写swap很方便,就一句话(a, b = b, a),于是写基于比较的排序能短小精悍.刚上手一门新语言练算法最合适不过 ...
- Caffe 编译后 make runtest 出现locale::facet::_S_create_c_locale 错误
You might need to append LC_ALL="en_US.UTF-8" to file: /etc/default/locale and reboot your ...
- mysql的SQL_NO_CACHE(在查询时不使用缓存)和sql_cache用法
转自:http://www.169it.com/article/5994930453423417575.html 为了测试sql语句的效率,有时候要不用缓存来查询. 使用 SELECT SQL_NO_ ...