在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?
它们似乎提供了一些相同的好处。
在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?
它们似乎提供了一些相同的好处。
当前回答
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!
如果你愿意
关于非穷尽模式匹配的警告 分配给每个枚举值的Int ID,您可以选择控制它 一个不可变的枚举值列表,按照它们定义的顺序 从名称到enum值的不可变映射 从id到enum值的不可变映射 为所有或特定枚举值粘贴方法/数据,或为整个枚举粘贴方法/数据的位置 排序enum值(这样您就可以测试,例如,day是否< Wednesday) 扩展一个枚举以创建其他枚举的能力
那么下面的内容可能会让你感兴趣。欢迎您的反馈。
在这个实现中,有抽象的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()
}
请注意,您必须使用每个枚举值(调用其apply方法)才能使其具有生命。[我希望内部对象不懒惰,除非我特别要求它们懒惰。我认为。)
当然,我们可以向DayOfWeek、Val或个别案例对象添加方法/数据,如果我们愿意的话。
下面是如何使用这样的枚举:
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"
}
}
下面是你编译它时得到的结果:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
您可以将“day match”替换为“(day: @unchecked) match”,因为您不想要这样的警告,或者简单地在结尾包含一个笼统的情况。
当你运行上面的程序时,你会得到这样的输出:
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
注意,由于List和Maps是不可变的,所以可以轻松地删除元素以创建子集,而不会破坏枚举本身。
下面是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 )
}
}
}
下面是它的一个更高级的使用,它控制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)
}
其他回答
与枚举相比,使用case类的优点是:
当使用密封大小写类时,Scala编译器可以判断匹配是否完全指定,例如,当所有可能的匹配都支持在匹配声明中。对于枚举,Scala编译器无法判断。 Case类自然比支持名称和ID的基于值的枚举支持更多的字段。
使用枚举而不是case类的优点是:
枚举通常需要编写更少的代码。 对于Scala新手来说,枚举比较容易理解,因为它们在其他语言中很普遍
所以一般来说,如果你只需要一个简单的常量列表的名称,使用枚举。否则,如果你需要一些更复杂的东西,或者想要编译器告诉你是否指定了所有匹配的额外安全,用例类。
如果你想要维护与其他JVM语言(如Java)的互操作性,那么最好的选择是编写Java枚举。这些功能在Scala和Java代码中都可以透明地工作,这在Scala中是做不到的。枚举或case对象。如果可以避免的话,让我们不要为GitHub上的每个新爱好项目都创建一个新的枚举库!
我在这里有一个简单的库,允许你使用密封的trait /类作为枚举值,而不必维护自己的值列表。它依赖于一个简单的宏,不依赖于有bug的knownDirectSubclasses。
https://github.com/lloydmeta/enumeratum
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 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;
EUR //THIS IS ONLY CHANGE
case object GBP extends Currency; GBP //Inline looks better
}