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

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


当前回答

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

换句话说:“没有简单的方法可以获得包含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

我见过让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类。这是我当时所能想到的。

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

简介: 尝试在Scala项目中重现Java Enum有三种基本模式。三种模式中的两种;直接使用Java Enum和scala。枚举,不能启用Scala的穷举模式匹配。第三个;“密封特质+格对象”,是否…但是有JVM类/对象初始化的复杂性,导致不一致的序号索引生成。

I have created a solution with two classes; Enumeration and 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 caseclassesobjects vs 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.

首先,让我们确保使用的是与枚举相同的基本概念。让我们主要根据Java 5(1.5)提供的Enum定义一个枚举:

It contains a naturally ordered closed set of named members There is a fixed number of members Members are naturally ordered and explicitly indexed As opposed to being sorted based on some inate member queriable criteria Each member has a unique name within the total set of all members All members can easily be iterated through based on their indexes A member can be retrieved with its (case sensitive) name It would be quite nice if a member could also be retrieved with its case insensitive name A member can be retrieved with its index Members may easily, transparently and efficiently use serialization Members may be easily extended to hold additional associated singleton-ness data Thinking beyond Java's Enum, it would be nice to be able to explicitly leverage Scala's pattern matching exhaustiveness checking for an enumeration

接下来,让我们来看看三种最常见的解决方案模式: A)实际上直接使用Java Enum模式(在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;
  }
}

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

如果一个成员也可以用它不区分大小写的名字来检索,那就太好了 7 -超越Java的Enum,如果能够显式地利用Scala的模式匹配耗尽性检查枚举就好了

对于我目前的项目,我没有在Scala/Java混合项目路径上冒险的好处。即使我可以选择做一个混合项目,如果/当我添加/删除枚举成员或编写一些新代码来处理现有的枚举成员时,第7项对于允许我捕获编译时问题也是至关重要的。 B)使用“密封特征+格对象”模式:

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

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

1.2 -成员自然有序并显式建立索引 2 -所有成员都可以根据它们的索引进行迭代 成员可以通过其名称(区分大小写)进行检索 如果一个成员也可以用它不区分大小写的名字来检索,那就太好了 成员可以用它的索引来检索

它确实符合枚举定义第5和6项,这是有争议的。对于5人来说,说它是有效的有点牵强。对于6来说,扩展以保存额外的关联单例数据并不容易。 C)使用scala。枚举模式(受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]
}

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

如果一个成员也可以用它不区分大小写的名字来检索,那就太好了 7 -超越Java的Enum,如果能够显式地利用Scala的模式匹配耗尽性检查枚举就好了

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


因此,给定上面的枚举定义,以上三个解决方案都不能工作,因为它们不能提供上面枚举定义中概述的所有内容:

Java Enum直接在混合Scala/Java项目 “密封trait + case对象” scala。枚举

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 Enum nor the scala.Enumeration solutions 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.


在这方面,我开始使用case对象路径,看看是否可以生成一个涵盖上述所有枚举定义的解决方案。第一个挑战是解决JVM类/对象初始化的核心问题(在这篇StackOverflow文章中有详细介绍)。我终于找到了解决办法。

我的解决方案有两个特点;Enumeration和enumerationented,并且由于Enumeration trait超过+400行长(许多注释解释上下文),我放弃将其粘贴到这个线程(这将使它延伸到页面相当大)。详情请直接跳到主旨。

下面是使用与上面相同的数据思想(此处提供完整的注释版本)并在enumerationdecoration中实现的解决方案。

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
}

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

所表达的一个关注点是枚举成员名必须重复(上面示例中的decorationOrderedSet)。虽然我确实把它减少到一次重复,但由于两个问题,我不知道如何让它更少:

这个特定对象/case对象模型的JVM对象/类初始化是未定义的(参见这个Stackoverflow线程) 方法getClass返回的内容。getDeclaredClasses有一个未定义的顺序(它不太可能与源代码中的case对象声明的顺序相同)

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

And given the design required this second list/set ordering val, given the ChessPiecesEnhancedDecorated example above, it was possible to add case object PAWN2 extends Member and 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.

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

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

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

https://github.com/lloydmeta/enumeratum