案例对象与 Scala 中的枚举

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1898932/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:47:33  来源:igfitidea点击:

Case objects vs Enumerations in Scala

scalaenumerationcase-class

提问by Alex Miller

Are there any best-practice guidelines on when to use case classes(or case objects) vs extending Enumeration in Scala?

是否有关于何时使用案例类(或案例对象)与在 Scala 中扩展枚举的最佳实践指南?

They seem to offer some of the same benefits.

它们似乎提供了一些相同的好处。

采纳答案by oxbow_lakes

One big difference is that Enumerations come with support for instantiating them from some nameString. For example:

一个很大的区别是Enumerations 支持从一些nameString实例化它们。例如:

object Currency extends Enumeration {
   val GBP = Value("GBP")
   val EUR = Value("EUR") //etc.
} 

Then you can do:

然后你可以这样做:

val ccy = Currency.withName("EUR")

This is useful when wishing to persist enumerations (for example, to a database) or create them from data residing in files. However, I find in general that enumerations are a bit clumsy in Scala and have the feel of an awkward add-on, so I now tend to use case objects. A case objectis more flexible than an enum:

当希望保留枚举(例如,到数据库)或从驻留在文件中的数据创建它们时,这很有用。但是,我发现一般来说,Scala 中的枚举有点笨拙,并且感觉像是一个笨拙的附加组件,所以我现在倾向于使用case objects。Acase object比枚举更灵活:

sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.

case class UnknownCurrency(name: String) extends Currency

So now I have the advantage of...

所以现在我的优势是...

trade.ccy match {
  case EUR                   =>
  case UnknownCurrency(code) =>
}

As @chaotic3quilibriumpointed out (with some corrections to ease reading):

正如@chaotic3quilibrium指出的那样(为了便于阅读,做了一些更正):

Regarding "UnknownCurrency(code)" pattern, there are other ways to handle not finding a currency code string than "breaking" the closed set nature of the Currencytype. UnknownCurrencybeing of type Currencycan now sneak into other parts of an API.

It's advisable to push that case outside Enumerationand make the client deal with an Option[Currency]type that would clearly indicate there is really a matching problem and "encourage" the user of the API to sort it out him/herself.

关于“UnknownCurrency(code)”模式,除了“打破”类型的封闭集性质之外,还有其他方法可以处理找不到货币代码字符串CurrencyUnknownCurrency是类型的Currency现在可以潜入的API的其他部分。

建议将这种情况推到外面Enumeration,让客户端处理一种Option[Currency]类型,该类型会清楚地表明确实存在匹配问题,并“鼓励”API 用户自己解决问题。

To follow up on the other answers here, the main drawbacks of case objects over Enumerations are:

要跟进这里的其他答案,case objects的主要缺点Enumeration是:

  1. Can't iterate over all instances of the "enumeration". This is certainly the case, but I've found it extremely rare in practice that this is required.

  2. Can't instantiate easily from persisted value. This is also true but, except in the case of huge enumerations (for example, all currencies), this doesn't present a huge overhead.

  1. 无法遍历 "enumeration" 的所有实例。确实如此,但我发现在实践中很少需要这样做。

  2. 无法从持久值轻松实例化。这也是正确的,但是,除了大量枚举(例如,所有货币)之外,这不会带来巨大的开销。

回答by chaotic3quilibrium

UPDATE:A new macro based solutionhas been created which is far superior to the solution I outline below. I strongly recommend using this new macro based solution. And it appears plans for Dotty will make this style of enum solution part of the language.Whoohoo!

更新:已经创建了 一个新的基于宏的解决方案,它远优于我在下面概述的解决方案。我强烈建议使用这个新的基于宏的解决方案Dotty 的计划似乎将使这种风格的枚举解决方案成为语言的一部分。呜呼!

Summary:
There are three basic patterns for attempting to reproduce the Java Enumwithin a Scala project. Two of the three patterns; directly using Java Enumand scala.Enumeration, are not capable of enabling Scala's exhaustive pattern matching. And the third one; "sealed trait + case object", does...but has JVM class/object initialization complicationsresulting in inconsistent ordinal index generation.

简介:
尝试Enum在 Scala 项目中重现 Java 有三种基本模式。三种模式中的两种;直接使用 JavaEnumscala.Enumeration, 不能启用 Scala 的详尽模式匹配。第三个;“密封 trait + case 对象”,确实......但有JVM 类/对象初始化并发症,导致不一致的序数索引生成。

I have created a solution with two classes; Enumerationand EnumerationDecorated, located in this Gist. I didn't post the code into this thread as the file for Enumeration was quite large (+400 lines - contains lots of comments explaining implementation context).

Details:
The question you're asking is pretty general; "...when to use caseclassesobjectsvs extending [scala.]Enumeration". And it turns out there are MANY possible answers, each answer depending on the subtleties of the specific project requirements you have. The answer can be reduced down to three basic patterns.

我创建了一个包含两个类的解决方案;EnumerationEnumerationDecorated,位于本Gist 中。我没有将代码发布到这个线程中,因为枚举文件非常大(+400 行 - 包含大量解释实现上下文的注释)。

详细信息:
您问的问题很笼统;“...何时使用caseobjects与扩展[scala.]Enumeration”。事实证明,有许多可能的答案,每个答案都取决于您所拥有的特定项目要求的微妙之处。答案可以简化为三种基本模式。

To start, let's make sure we are working from the same basic idea of what an enumeration is. Let's define an enumeration mostly in terms of the Enumprovided as of Java 5 (1.5):

首先,让我们确保我们从枚举的基本概念开始工作。让我们主要根据EnumJava 5 (1.5) 提供的来定义枚举:

  1. It contains a naturally ordered closed set of named members
    1. There is a fixed number of members
    2. Members are naturally ordered and explicitly indexed
      • As opposed to being sorted based on some inate member queriable criteria
    3. Each member has a unique name within the total set of all members
  2. All members can easily be iterated through based on their indexes
  3. A member can be retrieved with its (case sensitive) name
    1. It would be quite nice if a member could also be retrieved with its case insensitive name
  4. A member can be retrieved with its index
  5. Members may easily, transparently and efficiently use serialization
  6. Members may be easily extended to hold additional associated singleton-ness data
  7. Thinking beyond Java's Enum, it would be nice to be able to explicitly leverage Scala's pattern matching exhaustiveness checking for an enumeration
  1. 它包含一个自然有序的封闭命名成员集
    1. 有固定数量的成员
    2. 成员自然排序并明确索引
      • 与根据某些固有的成员可查询条件进行排序相反
    3. 每个成员在所有成员的总集合中都有一个唯一的名称
  2. 所有成员都可以根据他们的索引轻松迭代
  3. 可以使用其(区分大小写)名称检索成员
    1. 如果还可以使用不区分大小写的名称检索成员,那就太好了
  4. 可以使用其索引检索成员
  5. 成员可以轻松、透明和高效地使用序列化
  6. 成员可以很容易地扩展以保存其他相关的单例数据
  7. 超越 Java 的思考Enum,能够显式利用 Scala 的模式匹配穷举检查枚举会很好

Next, let's look at boiled down versions of the three most common solution patterns posted:

A)Actually directly using Java Enumpattern (in a mixed Scala/Java project):

接下来,让我们看一下发布的三种最常见解决方案模式的简化版本:

A)实际上直接使用JavaEnum模式(在混合 Scala/Java 项目中):

public enum ChessPiece {
    KING('K', 0)
  , QUEEN('Q', 9)
  , BISHOP('B', 3)
  , KNIGHT('N', 3)
  , ROOK('R', 5)
  , PAWN('P', 1)
  ;

  private char character;
  private int pointValue;

  private ChessPiece(char character, int pointValue) {
    this.character = character; 
    this.pointValue = pointValue;   
  }

  public int getCharacter() {
    return character;
  }

  public int getPointValue() {
    return pointValue;
  }
}

The following items from the enumeration definition are not available:

枚举定义中的以下项目不可用:

  1. 3.1 - It would be quite nice if a member could also be retrieved with its case insensitive name
  2. 7 - Thinking beyond Java's Enum, it would be nice to be able to explicitly leverage Scala's pattern matching exhaustiveness checking for an enumeration
  1. 3.1 - 如果成员也可以使用不区分大小写的名称检索,那就太好了
  2. 7 - 超越 Java 的枚举,能够显式地利用 Scala 的模式匹配穷举检查枚举会很好

For my current projects, I don't have the benefit of taking the risks around the Scala/Java mixed project pathway. And even if I could choose to do a mixed project, item 7 is critical for allowing me to catch compile time issues if/when I either add/remove enumeration members, or am writing some new code to deal with existing enumeration members.


B)Using the "sealed trait+ case objects" pattern:

对于我当前的项目,我没有在 Scala/Java 混合项目路径周围承担风险的好处。即使我可以选择做一个混合项目,如果/当我添加/删除枚举成员,或者正在编写一些新代码来处理现有的枚举成员时,第 7 项对于允许我捕获编译时问题至关重要。


B)使用“ sealed trait+case objects”模式:

sealed trait ChessPiece {def character: Char; def pointValue: Int}
object ChessPiece {
  case object KING extends ChessPiece {val character = 'K'; val pointValue = 0}
  case object QUEEN extends ChessPiece {val character = 'Q'; val pointValue = 9}
  case object BISHOP extends ChessPiece {val character = 'B'; val pointValue = 3}
  case object KNIGHT extends ChessPiece {val character = 'N'; val pointValue = 3}
  case object ROOK extends ChessPiece {val character = 'R'; val pointValue = 5}
  case object PAWN extends ChessPiece {val character = 'P'; val pointValue = 1}
}

The following items from the enumeration definition are not available:

枚举定义中的以下项目不可用:

  1. 1.2 - Members are naturally ordered and explicitly indexed
  2. 2 - All members can easily be iterated through based on their indexes
  3. 3 - A member can be retrieved with its (case sensitive) name
  4. 3.1 - It would be quite nice if a member could also be retrieved with its case insensitive name
  5. 4 - A member can be retrieved with its index
  1. 1.2 - 成员自然排序并明确索引
  2. 2 - 所有成员都可以根据他们的索引轻松迭代
  3. 3 - 可以使用成员(区分大小写)名称检索成员
  4. 3.1 - 如果成员也可以使用不区分大小写的名称检索,那就太好了
  5. 4 - 可以使用其索引检索成员

It's arguable it really meets enumeration definition items 5 and 6. For 5, it's a stretch to claim it's efficient. For 6, it's not really easy to extend to hold additional associated singleton-ness data.


C)Using the scala.Enumerationpattern (inspired by this StackOverflow answer):

有争议的是,它确实符合枚举定义第 5 项和第 6 项。对于 5,声称它是有效的有点牵强。对于 6,扩展以保存其他关联的单例数据并不容易。


C)使用scala.Enumeration模式(受此StackOverflow 答案启发):

object ChessPiece extends Enumeration {
  val KING = ChessPieceVal('K', 0)
  val QUEEN = ChessPieceVal('Q', 9)
  val BISHOP = ChessPieceVal('B', 3)
  val KNIGHT = ChessPieceVal('N', 3)
  val ROOK = ChessPieceVal('R', 5)
  val PAWN = ChessPieceVal('P', 1)
  protected case class ChessPieceVal(character: Char, pointValue: Int) extends super.Val()
  implicit def convert(value: Value) = value.asInstanceOf[ChessPieceVal]
}

The following items from the enumeration definition are not available (happens to be identical to the list for directly using the Java Enum):

枚举定义中的以下项目不可用(恰好与直接使用 Java 枚举的列表相同):

  1. 3.1 - It would be quite nice if a member could also be retrieved with its case insensitive name
  2. 7 - Thinking beyond Java's Enum, it would be nice to be able to explicitly leverage Scala's pattern matching exhaustiveness checking for an enumeration
  1. 3.1 - 如果成员也可以使用不区分大小写的名称检索,那就太好了
  2. 7 - 超越 Java 的枚举,能够显式地利用 Scala 的模式匹配穷举检查枚举会很好

Again for my current projects, item 7 is critical for allowing me to catch compile time issues if/when I either add/remove enumeration members, or am writing some new code to deal with existing enumeration members.

同样对于我当前的项目,如果/当我添加/删除枚举成员,或者正在编写一些新代码来处理现有枚举成员时,第 7 项对于允许我捕获编译时问题至关重要。



So, given the above definition of an enumeration, none of the above three solutions work as they do not provide everything outlined in the enumeration definition above:

因此,鉴于枚举的上述定义,上述三种解决方案都不起作用,因为它们没有提供上述枚举定义中概述的所有内容:

  1. Java Enum directly in a mixed Scala/Java project
  2. "sealed trait + case objects"
  3. scala.Enumeration
  1. 直接在混合 Scala/Java 项目中使用 Java Enum
  2. “密封特征+案例对象”
  3. Scala.Enumeration

Each of these solutions can be eventually reworked/expanded/refactored to attempt to cover some of each one's missing requirements. However, neither the Java Enumnor the scala.Enumerationsolutions can be sufficiently expanded to provide item 7. And for my own projects, this is one of the more compelling values of using a closed type within Scala. I strongly prefer compile time warnings/errors to indicate I have a gap/issue in my code as opposed to having to glean it out of a production runtime exception/failure.

这些解决方案中的每一个最终都可以重新设计/扩展/重构,以尝试覆盖每个人缺少的一些需求。然而,JavaEnumscala.Enumeration解决方案都不能充分扩展以提供第 7 项。对于我自己的项目,这是在 Scala 中使用封闭类型的更引人注目的价值之一。我非常喜欢使用编译时警告/错误来表明我的代码中存在漏洞/问题,而不是必须从生产运行时异常/失败中收集它。



In that regard, I set about working with the case objectpathway to see if I could produce a solution which covered all of the enumeration definition above. The first challenge was to push through the core of the JVM class/object initialization issue (covered in detail in this StackOverflow post). And I was finally able to figure out a solution.

在这方面,我开始着手使用该case object路径,看看我是否可以生成一个涵盖上述所有枚举定义的解决方案。第一个挑战是解决 JVM 类/对象初始化问题的核心(在StackOverflow 帖子中有详细介绍)。我终于能够想出一个解决方案。

As my solution is two traits; Enumerationand EnumerationDecorated, and since the Enumerationtrait is over +400 lines long (lots of comments explaining context), I am forgoing pasting it into this thread (which would make it stretch down the page considerbly). For details, please jump directly to the Gist.

因为我的解决方案是两个特征;EnumerationEnumerationDecorated,并且由于Enumerationtrait 超过 400 行(很多解释上下文的注释),我放弃将它粘贴到这个线程中(这会使其在页面上大幅向下延伸)。有关详细信息,请直接跳转到Gist

Here's what the solution ends up looking like using the same data idea as above (fully commented version available here) and implemented in EnumerationDecorated.

以下是使用与上述相同的数据思想(此处提供完整评论版本)并在EnumerationDecorated.

import scala.reflect.runtime.universe.{TypeTag,typeTag}
import org.public_domain.scala.utils.EnumerationDecorated

object ChessPiecesEnhancedDecorated extends EnumerationDecorated {
  case object KING extends Member
  case object QUEEN extends Member
  case object BISHOP extends Member
  case object KNIGHT extends Member
  case object ROOK extends Member
  case object PAWN extends Member

  val decorationOrderedSet: List[Decoration] =
    List(
        Decoration(KING,   'K', 0)
      , Decoration(QUEEN,  'Q', 9)
      , Decoration(BISHOP, 'B', 3)
      , Decoration(KNIGHT, 'N', 3)
      , Decoration(ROOK,   'R', 5)
      , Decoration(PAWN,   'P', 1)
    )

  final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase {
    val description: String = member.name.toLowerCase.capitalize
  }
  override def typeTagMember: TypeTag[_] = typeTag[Member]
  sealed trait Member extends MemberDecorated
}

This is an example usage of a new pair of enumeration traits I created (located in this Gist) to implement all of the capabilities desired and outlined in the enumeration definition.

这是我创建的一对新枚举特征(位于此 Gist 中)的示例用法,以实现枚举定义中所需和概述的所有功能。

One concern expressed is that the enumeration member names must be repeated (decorationOrderedSetin the example above). While I did minimize it down to a single repetition, I couldn't see how to make it even less due to two issues:

表达的一个问题是枚举成员名称必须重复(decorationOrderedSet在上面的示例中)。虽然我确实将它最小化为一次重复,但由于两个问题,我无法看到如何使它更小:

  1. JVM object/class initialization for this particular object/case object model is undefined (see this Stackoverflow thread)
  2. The content returned from the method getClass.getDeclaredClasseshas an undefined order (and it is quite unlikely to be in the same order as the case objectdeclarations in the source code)
  1. 此特定对象/案例对象模型的 JVM 对象/类初始化未定义(请参阅此 Stackoverflow 线程
  2. 从方法返回的内容getClass.getDeclaredClasses具有未定义的顺序(并且不太可能case object与源代码中的声明顺序相同)

Given these two issues, I had to give up trying to generate an implied ordering and had to explicitly require the client define and declare it with some sort of ordered set notion. As the Scala collections do not have an insert ordered set implementation, the best I could do was use a Listand then runtime check that it was truly a set. It's not how I would have preferred to have achieved this.

考虑到这两个问题,我不得不放弃尝试生成隐含的排序,而不得不明确要求客户端使用某种有序集合概念来定义和声明它。由于 Scala 集合没有插入有序集合实现,我能做的最好的事情是使用 aList然后运行时检查它是否真的是一个集合。这不是我希望实现这一目标的方式。

And given the design required this second list/set ordering val, given the ChessPiecesEnhancedDecoratedexample above, it was possible to add case object PAWN2 extends Memberand then forget to add Decoration(PAWN2,'P2', 2)to decorationOrderedSet. So, there is a runtime check to verify that the list is not only a set, but contains ALL of the case objects which extend the sealed trait Member. That was a special form of reflection/macro hell to work through.


Please leave comments and/or feedback on the Gist.

鉴于设计需要第二个列表/集合排序val,鉴于ChessPiecesEnhancedDecorated上面的示例,可以添加case object PAWN2 extends Member然后忘记添加Decoration(PAWN2,'P2', 2)decorationOrderedSet. 因此,有一个运行时检查来验证列表不仅是一个集合,而且包含所有扩展sealed trait Member. 这是一种特殊形式的反射/宏地狱。


请在Gist上留下评论和/或反馈。

回答by GatesDA

Case objects already return their name for their toString methods, so passing it in separately is unnecessary. Here is a version similar to jho's (convenience methods omitted for brevity):

Case 对象已经为它们的 toString 方法返回了它们的名称,因此单独传递它是不必要的。这是一个类似于 jho 的版本(为简洁起见省略了方便的方法):

trait Enum[A] {
  trait Value { self: A => }
  val values: List[A]
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency
  case object GBP extends Currency
  val values = List(EUR, GBP)
}

Objects are lazy; by using vals instead we can drop the list but have to repeat the name:

对象是惰性的;通过使用 vals 我们可以删除列表但必须重复名称:

trait Enum[A <: {def name: String}] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed abstract class Currency(val name: String) extends Currency.Value
object Currency extends Enum[Currency] {
  val EUR = new Currency("EUR") {}
  val GBP = new Currency("GBP") {}
}

If you don't mind some cheating, you can pre-load your enumeration values using the reflection API or something like Google Reflections. Non-lazy case objects give you the cleanest syntax:

如果您不介意作弊,您可以使用反射 API 或 Google Reflections 之类的东西预先加载您的枚举值。非惰性大小写对象为您提供最简洁的语法:

trait Enum[A] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency
  case object GBP extends Currency
}

Nice and clean, with all the advantages of case classes and Java enumerations. Personally, I define the enumeration values outside of the object to better match idiomatic Scala code:

漂亮而干净,具有案例类和 Java 枚举的所有优点。就个人而言,我在对象之外定义枚举值以更好地匹配惯用的 Scala 代码:

object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency

回答by Aaron

The advantages of using case classes over Enumerations are:

与枚举相比,使用 case 类的优点是:

  • When using sealed case classes, the Scala compiler can tell if the match is fully specified e.g. when all possible matches are espoused in the matching declaration. With enumerations, the Scala compiler cannot tell.
  • Case classes naturally supports more fields than a Value based Enumeration which supports a name and ID.
  • 当使用密封的 case 类时,Scala 编译器可以判断是否完全指定了匹配,例如,当所有可能的匹配都包含在匹配声明中时。对于枚举,Scala 编译器无法分辨。
  • 与支持名称和 ID 的基于值的枚举相比,案例类自然支持更多的字段。

The advantages of using Enumerations instead of case classes are:

使用枚举而不是 case 类的优点是:

  • Enumerations will generally be a bit less code to write.
  • Enumerations are a bit easier to understand for someone new to Scala since they are prevalent in other languages
  • 枚举通常会少写一点代码。
  • 枚举对于 Scala 新手来说更容易理解,因为它们在其他语言中很普遍

So in general, if you just need a list of simple constants by name, use enumerations. Otherwise, if you need something a bit more complex or want the extra safety of the compiler telling you if you have all matches specified, use case classes.

所以一般来说,如果您只需要按名称列出简单常量,请使用枚举。否则,如果您需要更复杂的东西或希望编译器的额外安全性告诉您是否指定了所有匹配项,请使用案例类。

回答by AmigoNico

UPDATE: The code below has a bug, described here. The test program below works, but if you were to use DayOfWeek.Mon (for example) before DayOfWeek itself, it would fail because DayOfWeek has not been initialized (use of an inner object does not cause an outer object to be initialized). You can still use this code if you do something like val enums = Seq( DayOfWeek )in your main class, forcing initialization of your enums, or you can use chaotic3quilibrium's modifications. Looking forward to a macro-based enum!

更新:下面的代码有一个错误,描述在这里。下面的测试程序可以工作,但是如果您在 DayOfWeek 本身之前使用 DayOfWeek.Mon(例如),它将失败,因为 DayOfWeek 尚未初始化(使用内部对象不会导致外部对象被初始化)。如果您val enums = Seq( DayOfWeek )在主类中执行诸如强制初始化枚举之类的操作,您仍然可以使用此代码,或者您可以使用 chaotic3quilibrium 的修改。期待基于宏的枚举!



If you want

如果你想

  • warnings about non-exhaustive pattern matches
  • an Int ID assigned to each enum value, which you can optionally control
  • an immutable List of the enum values, in the order they were defined
  • an immutable Map from name to enum value
  • an immutable Map from id to enum value
  • places to stick methods/data for all or particular enum values, or for the enum as a whole
  • ordered enum values (so you can test, for example, whether day < Wednesday)
  • the ability to extend one enum to create others
  • 关于非详尽模式匹配的警告
  • 分配给每个枚举值的 Int ID,您可以选择控制
  • 一个不可变的枚举值列表,按照它们被定义的顺序
  • 从名称到枚举值的不可变映射
  • 从 id 到枚举值的不可变映射
  • 为所有或特定枚举值或整个枚举粘贴方法/数据的地方
  • 有序枚举值(因此您可以测试,例如,是否天 < 星期三)
  • 扩展一个枚举以创建其他枚举的能力

then the following may be of interest. Feedback welcome.

那么以下内容可能会引起人们的兴趣。欢迎反馈。

In this implementation there are abstract Enum and EnumVal base classes, which you extend. We'll see those classes in a minute, but first, here's how you would define an enum:

在这个实现中有抽象的 Enum 和 EnumVal 基类,你可以扩展它们。我们将在一分钟内看到这些类,但首先,您将如何定义枚举:

object DayOfWeek extends Enum {
  sealed abstract class Val extends EnumVal
  case object Mon extends Val; Mon()
  case object Tue extends Val; Tue()
  case object Wed extends Val; Wed()
  case object Thu extends Val; Thu()
  case object Fri extends Val; Fri()
  case object Sat extends Val; Sat()
  case object Sun extends Val; Sun()
}

Note that you have to use each enum value (call its apply method) to bring it to life. [I wish inner objects weren't lazy unless I specifically ask for them to be. I think.]

请注意,您必须使用每个枚举值(调用它的 apply 方法)才能使其生效。[我希望内在对象不要懒惰,除非我特别要求它们。我认为。]

We could of course add methods/data to DayOfWeek, Val, or the individual case objects if we so desired.

如果我们愿意,我们当然可以向 DayOfWeek、Val 或单个案例对象添加方法/数据。

And here's how you would use such an enum:

以下是您将如何使用这样的枚举:

object DayOfWeekTest extends App {

  // To get a map from Int id to enum:
  println( DayOfWeek.valuesById )

  // To get a map from String name to enum:
  println( DayOfWeek.valuesByName )

  // To iterate through a list of the enum values in definition order,
  // which can be made different from ID order, and get their IDs and names:
  DayOfWeek.values foreach { v => println( v.id + " = " + v ) }

  // To sort by ID or name:
  println( DayOfWeek.values.sorted mkString ", " )
  println( DayOfWeek.values.sortBy(_.toString) mkString ", " )

  // To look up enum values by name:
  println( DayOfWeek("Tue") ) // Some[DayOfWeek.Val]
  println( DayOfWeek("Xyz") ) // None

  // To look up enum values by id:
  println( DayOfWeek(3) )         // Some[DayOfWeek.Val]
  println( DayOfWeek(9) )         // None

  import DayOfWeek._

  // To compare enums as ordinals:
  println( Tue < Fri )

  // Warnings about non-exhaustive pattern matches:
  def aufDeutsch( day: DayOfWeek.Val ) = day match {
    case Mon => "Montag"
    case Tue => "Dienstag"
    case Wed => "Mittwoch"
    case Thu => "Donnerstag"
    case Fri => "Freitag"
 // Commenting these out causes compiler warning: "match is not exhaustive!"
 // case Sat => "Samstag"
 // case Sun => "Sonntag"
  }

}

Here's what you get when you compile it:

这是你编译时得到的:

DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination            Sat
missing combination            Sun

  def aufDeutsch( day: DayOfWeek.Val ) = day match {
                                         ^
one warning found

You can replace "day match" with "( day: @unchecked ) match" where you don't want such warnings, or simply include a catch-all case at the end.

您可以在不希望出现此类警告的情况下将“日匹配”替换为“(日:@unchecked)匹配”,或者在最后包含一个包罗万象的案例。

When you run the above program, you get this output:

当你运行上面的程序时,你会得到这样的输出:

Map(0 -> Mon, 5 -> Sat, 1 -> Tue, 6 -> Sun, 2 -> Wed, 3 -> Thu, 4 -> Fri)
Map(Thu -> Thu, Sat -> Sat, Tue -> Tue, Sun -> Sun, Mon -> Mon, Wed -> Wed, Fri -> Fri)
0 = Mon
1 = Tue
2 = Wed
3 = Thu
4 = Fri
5 = Sat
6 = Sun
Mon, Tue, Wed, Thu, Fri, Sat, Sun
Fri, Mon, Sat, Sun, Thu, Tue, Wed
Some(Tue)
None
Some(Thu)
None
true

Note that since the List and Maps are immutable, you can easily remove elements to create subsets, without breaking the enum itself.

请注意,由于 List 和 Maps 是不可变的,因此您可以轻松删除元素以创建子集,而不会破坏枚举本身。

Here is the Enum class itself (and EnumVal within it):

这是 Enum 类本身(以及其中的 EnumVal):

abstract class Enum {

  type Val <: EnumVal

  protected var nextId: Int = 0

  private var values_       =       List[Val]()
  private var valuesById_   = Map[Int   ,Val]()
  private var valuesByName_ = Map[String,Val]()

  def values       = values_
  def valuesById   = valuesById_
  def valuesByName = valuesByName_

  def apply( id  : Int    ) = valuesById  .get(id  )  // Some|None
  def apply( name: String ) = valuesByName.get(name)  // Some|None

  // Base class for enum values; it registers the value with the Enum.
  protected abstract class EnumVal extends Ordered[Val] {
    val theVal = this.asInstanceOf[Val]  // only extend EnumVal to Val
    val id = nextId
    def bumpId { nextId += 1 }
    def compare( that:Val ) = this.id - that.id
    def apply() {
      if ( valuesById_.get(id) != None )
        throw new Exception( "cannot init " + this + " enum value twice" )
      bumpId
      values_ ++= List(theVal)
      valuesById_   += ( id       -> theVal )
      valuesByName_ += ( toString -> theVal )
    }
  }

}

And here is a more advanced use of it which controls the IDs and adds data/methods to the Val abstraction and to the enum itself:

这是它的更高级用法,它控制 ID 并将数据/方法添加到 Val 抽象和枚举本身:

object DayOfWeek extends Enum {

  sealed abstract class Val( val isWeekday:Boolean = true ) extends EnumVal {
    def isWeekend = !isWeekday
    val abbrev = toString take 3
  }
  case object    Monday extends Val;    Monday()
  case object   Tuesday extends Val;   Tuesday()
  case object Wednesday extends Val; Wednesday()
  case object  Thursday extends Val;  Thursday()
  case object    Friday extends Val;    Friday()
  nextId = -2
  case object  Saturday extends Val(false); Saturday()
  case object    Sunday extends Val(false);   Sunday()

  val (weekDays,weekendDays) = values partition (_.isWeekday)
}

回答by lloydmeta

I have a nice simple lib here that allows you to use sealed traits/classes as enum values without having to maintain your own list of values. It relies on a simple macro that is not dependent on the buggy knownDirectSubclasses.

我在这里有一个很好的简单库,它允许您使用密封的特征/类作为枚举值,而无需维护自己的值列表。它依赖于一个不依赖于 buggy 的简单宏knownDirectSubclasses

https://github.com/lloydmeta/enumeratum

https://github.com/lloydmeta/enumeratum

回答by VonC

Update March 2017: as commented by Anthony Accioly, the scala.Enumeration/enumPR has been closed.

2017 年 3 月更新:正如Anthony Accioly评论的那样,scala.Enumeration/enumPR 已关闭。

Dotty(next generation compiler for Scala) will take the lead, though dotty issue 1970and Martin Odersky's PR 1958.

Dotty(Scala 的下一代编译器)将处于领先地位,尽管dotty issue 1970Martin Odersky 的 PR 1958



Note: there is now (August 2016, 6+ years later) a proposal to remove scala.Enumeration: PR 5352

注意:现在(2016 年 8 月,6 年多之后)提议删除scala.EnumerationPR 5352

Deprecate scala.Enumeration, add @enumannotation

The syntax

弃用scala.Enumeration,添加@enum注释

语法

@enum
 class Toggle {
  ON
  OFF
 }

is a possible implementation example, intention is to also support ADTs that conform to certain restrictions (no nesting, recursion or varying constructor parameters), e. g.:

是一个可能的实现示例,目的是还支持符合某些限制(无嵌套、递归或变化的构造函数参数)的 ADT,例如:

@enum
sealed trait Toggle
case object ON  extends Toggle
case object OFF extends Toggle

Deprecates the unmitigated disaster that is scala.Enumeration.

Advantages of @enum over scala.Enumeration:

  • Actually works
  • Java interop
  • No erasure issues
  • No confusing mini-DSL to learn when defining enumerations

Disadvantages: None.

This addresses the issue of not being able to have one codebase that supports Scala-JVM, Scala.jsand Scala-Native (Java source code not supported on Scala.js/Scala-Native, Scala source code not able to define enums that are accepted by existing APIs on Scala-JVM).

弃用彻底的灾难scala.Enumeration

@enum 相对于 scala.Enumeration 的优点:

  • 实际有效
  • Java互操作
  • 没有擦除问题
  • 定义枚举时无需学习混淆的 mini-DSL

缺点:无。

这解决了无法拥有一个支持 Scala-JVMScala.js和 Scala-Native 的代码库的问题(Java 源代码不支持Scala.js/Scala-Native,Scala 源代码无法定义被 Scala-JVM 上的现有 API 接受的枚举)。

回答by user142435

Another disadvantage of case classes versus Enumerations when you will need to iterate or filter across all instances. This is a built-in capability of Enumeration (and Java enums as well) while case classes don't automatically support such capability.

当您需要迭代或过滤所有实例时,案例类与枚举的另一个缺点。这是 Enumeration(以及 Java 枚举)的内置功能,而 case 类不会自动支持这种功能。

In other words: "there's no easy way to get a list of the total set of enumerated values with case classes".

换句话说:“没有简单的方法来获取具有案例类的枚举值的总集列表”。

回答by Connor Doyle

If you are serious about maintaining interoperability with other JVM languages (e.g. Java) then the best option is to write Java enums. Those work transparently from both Scala and Java code, which is more than can be said for scala.Enumerationor case objects. Let's not have a new enumerations library for every new hobby project on GitHub, if it can be avoided!

如果您非常想保持与其他 JVM 语言(例如 Java)的互操作性,那么最好的选择是编写 Java 枚举。这些在 Scala 和 Java 代码中都是透明的,这对于scala.Enumeration或 case 对象来说是不可描述的。如果可以避免的话,我们不要在 GitHub 上为每个新的爱好项目都创建一个新的枚举库!

回答by jho

I've seen various versions of making a case class mimic an enumeration. Here is my version:

我见过使 case 类模仿枚举的各种版本。这是我的版本:

trait CaseEnumValue {
    def name:String
}

trait CaseEnum {
    type V <: CaseEnumValue
    def values:List[V]
    def unapply(name:String):Option[String] = {
        if (values.exists(_.name == name)) Some(name) else None
    }
    def unapply(value:V):String = {
        return value.name
    }
    def apply(name:String):Option[V] = {
        values.find(_.name == name)
    }
}

Which allows you to construct case classes that look like the following:

这允许您构造如下所示的案例类:

abstract class Currency(override name:String) extends CaseEnumValue {
}

object Currency extends CaseEnum {
    type V = Site
    case object EUR extends Currency("EUR")
    case object GBP extends Currency("GBP")
    var values = List(EUR, GBP)
}

Maybe someone could come up with a better trick than simply adding a each case class to the list like I did. This was all I could come up with at the time.

也许有人可以想出更好的技巧,而不是像我那样简单地将每个案例类添加到列表中。我当时能想到的只有这些。