在Scala中,Case对象与枚举之间的区别
在Scala中,有两种常用的方式来实现枚举类型:使用case objects和使用Enumeration。这两种方式都有自己的优势和劣势,选择哪种方式取决于具体的需求。
在上述代码中,通过比较不同方式的实现,可以看出case objects相对于Enumeration的一些优势。首先,case objects已经为它们的toString方法返回了它们的名称,因此不需要单独传递名称。其次,通过使用vals而不是列表,可以消除列表并重复名称。
然而,使用case objects也存在一些问题。比如,case objects是惰性加载的,只有在使用之前才会加载。这意味着,如果调用Currency.values方法,只能获取之前访问过的值,无法获取所有的值。此外,case objects在IDE中无法自动提示可用选项。
为了解决这些问题,可以使用trait Enum来定义枚举类型。通过在trait Enum中定义一个内部的trait Value,并在其中定义一个内部变量_values来存储所有的枚举值。然后,通过在trait Value中混入trait Enum,可以将枚举值添加到_values中。这样,就可以通过调用Enum.values方法获取所有的枚举值。
在上述代码的最后一个例子中,可以看到如何使用trait Enum来定义枚举类型。在这种方式下,枚举值需要在Currency对象之外定义。这样做可以更好地符合Scala的编码习惯。
使用case objects或者trait Enum都可以实现枚举类型。选择哪种方式取决于具体的需求。如果需要更简洁的语法和惰性加载的特性,可以使用case objects。如果需要更灵活的控制和更好的IDE支持,可以使用trait Enum。
在Scala中,有三种基本模式可以尝试复制Java的枚举。其中两种模式(直接使用Java的Enum和scala.Enumeration)都无法实现Scala的完全模式匹配。第三种模式("sealed trait + case object")可以实现完全模式匹配,但由于JVM类/对象初始化问题,导致生成的有序索引不一致。
为了解决这个问题,我创建了两个类(Enumeration和EnumerationDecorated),位于这个Gist中。由于Enumeration类非常大(超过400行),我没有将代码粘贴到本文中。
问题可以简化为三种基本模式:
A) 直接使用Java的Enum模式(在Scala/Java混合项目中)
B) 使用"sealed trait + case objects"模式
C) 使用scala.Enumeration模式
然而,这三种解决方案都无法满足枚举定义中列出的所有要求。因此,我尝试使用"case object"模式来解决这个问题。首先,我解决了JVM类/对象初始化的核心问题。最终,我创建了两个特质(Enumeration和EnumerationDecorated),实现了所有枚举定义中所需的功能。为了满足要求,需要在代码中明确指定枚举成员的有序集合。
这是使用我创建的新枚举特质的示例用法(位于这个Gist中):
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 }
该解决方案不仅满足了枚举定义中列出的所有要求,还提供了更好的编译时检查。然而,需要注意的是,枚举成员的名称必须在`decorationOrderedSet`中重复定义。
总结一下,因为Java的Enum和scala.Enumeration无法满足Scala的完全模式匹配,我尝试使用"case object"模式来解决这个问题。通过创建两个特质和一个有序集合,我成功地实现了所有枚举定义中所需的功能。这个解决方案提供了更好的编译时检查,并且可以在Scala 3.0中得到原生支持。
在Scala中,有两种常用的方式来定义枚举类型:使用枚举(Enumeration)或使用案例对象(case object)。这两种方式在使用上有一些区别和特点。
一种区别是,枚举(Enumeration)支持通过名称字符串来实例化枚举值。例如:
object Currency extends Enumeration { val GBP = Value("GBP") val EUR = Value("EUR") //etc. }
然后可以这样使用:
val ccy = Currency.withName("EUR")
这在希望将枚举持久化(例如保存到数据库)或从文件中读取数据时非常有用。然而,我发现在Scala中,枚举有些笨拙,并且感觉像是一个尴尬的附加功能,所以我现在更倾向于使用案例对象(case object)。案例对象(case object)比枚举更灵活:
sealed trait Currency { def name: String } case object EUR extends Currency { val name = "EUR" } //etc. case class UnknownCurrency(name: String) extends Currency
现在我有了以下优势:
trade.ccy match { case EUR => case UnknownCurrency(code) => }
关于"UnknownCurrency(code)"模式,有其他处理找不到货币代码字符串的方法,而不是“破坏”Currency类型的封闭集性质。UnknownCurrency作为Currency类型,现在可以悄悄地进入API的其他部分。建议将这种情况从Enumeration中排除,并让客户端处理Option[Currency]类型,清楚地指示确实存在匹配问题,并“鼓励”API的用户自行解决。
除了这些,案例对象与枚举相比的主要缺点是:
1. 无法轻松地遍历所有实例。在实际应用中,这是非常罕见的需求。
2. 无法轻松地从持久化的值实例化。除非是巨大的枚举(例如,所有货币),否则这不会带来巨大的开销。
另一个区别是,枚举默认是有序的,而案例对象枚举则不是。
对于案例对象,如果你关心与Java的互操作性,那么枚举将返回Enumeration.Value作为值,因此需要scala-library,并且会丢失实际的类型信息。
关于第一个观点,特别是这部分"...在实践中,我发现这种需求非常罕见":显然你很少进行UI工作。这是一个非常常见的用例,即显示一个(下拉)列表,其中包含可以选择的有效枚举成员。
我不理解在sealed trait示例中匹配项的类型trade.ccy。
案例对象是否生成比枚举更大(约为4倍)的代码占用空间?这对于需要小占用空间的scala.js项目非常有用的区别。