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

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


当前回答

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

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

其他回答

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

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

在过去几次我需要这两个选择的时候,我一直在反复考虑。直到最近,我的偏好一直是密封的trait/case对象选项。

1) Scala枚举声明

object OutboundMarketMakerEntryPointType extends Enumeration {
  type OutboundMarketMakerEntryPointType = Value

  val Alpha, Beta = Value
}

2)封印属性+个案对象

sealed trait OutboundMarketMakerEntryPointType

case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType

case object BetaEntryPoint extends OutboundMarketMakerEntryPointType

虽然这两种方法都不能满足java枚举提供的所有功能,但以下是优点和缺点:

Scala枚举

优点: -使用选项实例化或直接假设准确的函数(从持久存储加载时更容易) -支持迭代所有可能的值

缺点: 不支持非穷举搜索的编译警告(使模式匹配不理想)

Case对象/密封特征

优点: 使用密封特征,我们可以预实例化一些值,而其他值可以在创建时注入 完全支持模式匹配(应用/取消应用定义的方法)

缺点: -从持久存储实例化-你经常需要在这里使用模式匹配或者定义你自己的所有可能的“enum值”列表

最终让我改变观点的是以下片段:

object DbInstrumentQueries {
  def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
    val symbol = rs.getString(tableAlias + ".name")
    val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
    val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
    val pointsValue = rs.getInt(tableAlias + ".points_value")
    val instrumentType = InstrumentType.fromString(rs.getString(tableAlias +".instrument_type"))
    val productType = ProductType.fromString(rs.getString(tableAlias + ".product_type"))

    Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
  }
}

object InstrumentType {
  def fromString(instrumentType: String): InstrumentType = Seq(CurrencyPair, Metal, CFD)
  .find(_.toString == instrumentType).get
}

object ProductType {

  def fromString(productType: String): ProductType = Seq(Commodity, Currency, Index)
  .find(_.toString == productType).get
}

.get调用是可怕的-使用枚举代替我可以简单地调用withName方法对枚举如下:

object DbInstrumentQueries {
  def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
    val symbol = rs.getString(tableAlias + ".name")
    val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
    val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
    val pointsValue = rs.getInt(tableAlias + ".points_value")
    val instrumentType = InstrumentType.withNameString(rs.getString(tableAlias + ".instrument_type"))
    val productType = ProductType.withName(rs.getString(tableAlias + ".product_type"))

    Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
  }
}

所以我认为我的首选是使用枚举当值打算从存储库访问和case对象/密封特征。

我更喜欢case对象(这是个人喜好的问题)。为了解决这种方法固有的问题(解析字符串并遍历所有元素),我添加了一些不完美但有效的行。

我把代码粘贴在这里,希望它有用,也希望其他人可以改进它。

/**
 * Enum for Genre. It contains the type, objects, elements set and parse method.
 *
 * This approach supports:
 *
 * - Pattern matching
 * - Parse from name
 * - Get all elements
 */
object Genre {
  sealed trait Genre

  case object MALE extends Genre
  case object FEMALE extends Genre

  val elements = Set (MALE, FEMALE) // You have to take care this set matches all objects

  def apply (code: String) =
    if (MALE.toString == code) MALE
    else if (FEMALE.toString == code) FEMALE
    else throw new IllegalArgumentException
}

/**
 * Enum usage (and tests).
 */
object GenreTest extends App {
  import Genre._

  val m1 = MALE
  val m2 = Genre ("MALE")

  assert (m1 == m2)
  assert (m1.toString == "MALE")

  val f1 = FEMALE
  val f2 = Genre ("FEMALE")

  assert (f1 == f2)
  assert (f1.toString == "FEMALE")

  try {
    Genre (null)
    assert (false)
  }
  catch {
    case e: IllegalArgumentException => assert (true)
  }

  try {
    Genre ("male")
    assert (false)
  }
  catch {
    case e: IllegalArgumentException => assert (true)
  }

  Genre.elements.foreach { println }
}

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

https://github.com/lloydmeta/enumeratum