Differences between scala and java enumerations

2019-05-01 15:50发布

I read an answer on SO where someone said that scala enumerations are useless, and you should just use java enumerations instead if you really need too.

Although I have used java enumerations before, I can't say I fully understand them as I don't code in java during my day job.

Can someone explain the differences between scala and java enumerations, and where exactly the shortcomings in scala enums are?

2条回答
再贱就再见
2楼-- · 2019-05-01 16:01

The main advantage of Scala's Enumeration is the regularity of the syntax.

If you want to add behavior to elements of your enum, just extend its Val class.

Odersky likes them for their simplest use case as named int-valued constants. And he just promised on the mailing list, for the first time, that improved support is on the horizon.

But I recently used them to replace a list of strings, since the set of values is a bit set, nicer than a set of strings for look-up.

Instead of

val stuff = List("foo", "bar", "baz")

object stuff extends Enumeration { val foo, bar, baz = Value }

or

scala> object stuff extends Enumeration { val foo, bar, baz = Value }
defined object stuff

scala> val junk = new Enumeration { val foo, bar, baz = Value }
junk: Enumeration{val foo: this.Value; val bar: this.Value; val baz: this.Value} = 1

scala> stuff.values contains junk.foo
<console>:10: error: type mismatch;
 found   : junk.Value
 required: stuff.Value
              stuff.values contains junk.foo
                                         ^

Behavior:

scala> trait Alias { def alias: String }
defined trait Alias

scala> object aliased extends Enumeration {
     | class Aliased extends Val with Alias {
     |   def alias = toString.permutations.drop(1).next }
     | val foo, bar, baz = new Aliased }
defined object aliased

scala> abstract class X { type D <: Enumeration
     | def f(x: D#Value) = x match { case a: Alias => a.alias
     |   case _ => x.toString } }
defined class X

scala> class Y extends X { type D = aliased.type }
defined class Y

scala> new Y().f(aliased.bar)
res1: String = bra

scala> new Y().f(stuff.foo)
<console>:13: error: type mismatch;
 found   : stuff.Value
 required: aliased.Value
              new Y().f(stuff.foo)
                              ^

scala> new X { type D = junk.type }.f(junk.foo)
warning: there was one feature warning; re-run with -feature for details
res4: String = foo

ValueSet is a bit set:

scala> stuff.values contains aliased.bar
<console>:11: error: type mismatch;
 found   : aliased.Aliased
 required: stuff.Value
              stuff.values contains aliased.bar
                                            ^

scala> stuff.foo + aliased.bar
<console>:11: error: type mismatch;
 found   : aliased.Aliased
 required: stuff.Value
              stuff.foo + aliased.bar
                                  ^

scala> stuff.foo + stuff.bar
res8: stuff.ValueSet = stuff.ValueSet(foo, bar)

Other stuff that seems to work in today's Scala:

scala> def f[E <: Enumeration](e: E)(v: e.Value) = e.ValueSet.empty + v
f: [E <: Enumeration](e: E)(v: e.Value)e.ValueSet

scala> f(stuff)(stuff.foo)
res14: stuff.ValueSet = stuff.ValueSet(foo)

scala> def g[E <: Enumeration](e: E)(a: Any) = a match { case _: e.Value => true case _ => false }
g: [E <: Enumeration](e: E)(a: Any)Boolean

scala> g(stuff)(stuff.foo)
res15: Boolean = true

scala> g(stuff)(junk.foo)    // checking outer pointers
warning: there was one feature warning; re-run with -feature for details
res16: Boolean = false

scala> g(stuff)(aliased.foo)
res17: Boolean = false

It looks like Scala is not entirely friendly to Java enums:

scala> Thread.State.NEW.ordinal
[snip]
scala.reflect.internal.FatalError: 
  Unknown type: <notype>(NEW), <notype> [class scala.reflect.internal.Types$UniqueConstantType, class scala.reflect.internal.Types$NoType$] TypeRef? false
     while compiling: <console>
        during phase: icode
     library version: version 2.11.2
    compiler version: version 2.11.2
  reconstructed args: 

  last tree to typer: Apply(method ordinal)
       tree position: line 8 of <console>
            tree tpe: Int
              symbol: final method ordinal in class Enum
   symbol definition: final def ordinal(): Int (a MethodSymbol)
      symbol package: java.lang
       symbol owners: method ordinal -> class Enum
           call site: constructor $read$$iw$$iw in package $line4
查看更多
聊天终结者
3楼-- · 2019-05-01 16:11

The main weakness of Scala enums is the difficulty in adding methods and fields to them. In Java, it's trivial to use an enum as a Class with a fixed number of instances (which is, for example, why they're a good choice for implementing singletons).

In Scala however, Enums are just a set of possible values; adding methods and fields to them is a much more hacky process. So if you want anything beyond trivial behavior from the enum, Java enums are a much more convenient tool.

For example a Java enum might look like this:

public enum Month{
    january(31),
    february(28),
    ...
    december(31);

    public final int daysInMonth;
    Month(int daysInMonth){
        this.daysInMonth = daysInMonth;
    }
}

However, in Scala, you would have to do this:

object Month extends Enumeration{
    protected case class Val(val daysInMonth:Integer) extends super.Val{}
    implicit def valueToMonth(x:Value) = x.asInstanceOf[Val]
    val january = Val(31)
    val february = Val(28)
    ....
}

This doesn't look that bad in a trivial example like this, but it does add some confusing syntax, and adds the overhead of another class which will need to be implicitly converted to for most uses of the enum.

The main advantage I see to the Java enum is that you write exactly what you mean; an enum, with some methods and fields on it. In scala, you're writing something other than what you mean; you need to create an enum, which contains a class which has the methods and fields you want, and also defines a conversion from that class to the enum. It's a less idiomatic way to express the same idea.

As was pointed out in the comments, Scala does offer Case Classes, which can be used as an alternative to enums in many cases, and have a much cleaner syntax. But there still are some situations in which Case Classes aren't sufficient (such as when you want to iterate over all values), so regular enums still have their place. Correction: using macros does make it possible to iterate over Case Classes, but doing so has its own added complexity, while enums (in both Scala and Java) are much more straightforward to iterate over.

查看更多
登录 后发表回答