在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?

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


当前回答

当您需要遍历或过滤所有实例时,case类相对于枚举的另一个缺点。这是枚举(以及Java枚举)的内置功能,而case类并不自动支持这种功能。

换句话说:“没有简单的方法可以获得包含case类的全部枚举值集的列表”。

其他回答

我在这里有一个简单的库,允许你使用密封的trait /类作为枚举值,而不必维护自己的值列表。它依赖于一个简单的宏,不依赖于有bug的knownDirectSubclasses。

https://github.com/lloydmeta/enumeratum

2017年3月更新:正如Anthony Accioly所评论的,scala。枚举/枚举PR已关闭。

Dotty (Scala的下一代编译器)将占据主导地位,尽管Dotty发行于1970年,Martin Odersky的PR发行于1958年。


注意:现在(2016年8月,6年多之后)有一个移除scala的提案。编号:PR 5352

scala抨击。枚举,添加@enum注释 的语法

@enum
 class Toggle {
  ON
  OFF
 }

是一个可能的实现示例,目的是也支持符合某些限制(没有嵌套,递归或变化的构造函数参数)的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.js and 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).

如果你想要维护与其他JVM语言(如Java)的互操作性,那么最好的选择是编写Java枚举。这些功能在Scala和Java代码中都可以透明地工作,这在Scala中是做不到的。枚举或case对象。如果可以避免的话,让我们不要为GitHub上的每个新爱好项目都创建一个新的枚举库!

我见过让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)
    }
}

它允许你构造如下所示的case类:

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)
}

也许有人可以想出一个更好的技巧,而不是像我这样简单地向列表中添加一个each case类。这是我当时所能想到的。

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)
}

对象是懒惰的;通过使用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") {}
}

如果不介意作弊,可以使用反射API或谷歌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
}

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

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