colevk
colevk

Reputation: 688

Creating a Java Enum in Scala

My workplace has been experimenting in moving from Java to Scala for some tasks, and it works well for what we're doing. However, some preexisting logging methods expect a java.lang.Enum. The logging method is defined in the (Java) base class, and the subclasses can define their own enums, which the logger will track across all instances in multiple threads/machines.

It works like this in Java:

public class JavaSubClass extends JavaBaseClass {
    enum Counters {
        BAD_THING,
        GOOD_THING
    }

    public void someDistributedTask() {
        // some work here
        if(terribleThing) {
            loggingMethod(Counters.BAD_THING)
        } else {
            loggingMethod(Counters.GOOD_THING)
            // more work here
        }
    }
}

Then, when the task has finished, we can see that

BAD_THING: 230
GOOD_THING: 10345

Is there any way to replicate this in Scala, either by creating Java Enums or converting from Enumeration to Enum? I have tried extending Enum directly, but it seems to be sealed, as I get the error in the console:

error: constructor Enum in class Enum cannot be accessed in object $iw
Access to protected constructor Enum not permitted because
enclosing object $iw is not a subclass of 
class Enum in package lang where target is defined

Upvotes: 19

Views: 17850

Answers (4)

VonC
VonC

Reputation: 1324757

As explained in this thread, Dotty will have enum for Scala 3.0 (fall-2020)

Scala redesigned Enums as well.
They can be parameterized and can contain custom members.

// Scala 2 way:
object Day extends Enumeration {
  type Day = Value
  val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}

// replaced with:
enum Day {
  case Mon, Tue, Wed, Thu, Fri, Sat, Sun
}

From "Martin Odersky -- A tour of Scala 3" (June 2019):

Enums can be parameterized.

enum Day(val mon: Int) {}

Enums:

  • can have parameters
  • can define fields and methods
  • can interop with Java
enum Planet(mass: Double, radius: Double) extends java.lang.Enum[Planet] { 
  private final val G = 6.67300E-11 
  def surfaceGravity = G * mass / (radius * radius) 

  case MERCURY extends Planet(3.303e+23, 2.4397e6) 
  case VENUS extends Planet(4.869e+24, 6.0518e6) 
  case EARTH extends Planet(5.976e+24, 6.37814e6) 
  case MARS extends Planet(6.421e+23, 3.3972e6)
  ... 
} 

Enums can have type parameters, making them algebraic data types (ADTs)

enum Option[+T] { 
  case Some(x: T) 
  case None 
}

Enums compile to sealed hierarchies of case classes and objects.

sealed abstract class Option[+T] 

object  Option { 

  case class Some[+T](x: T) extends Option[T] 
  object Some { 
    def apply[T](x: T): Option[T] = Some(x) 
  } 

  val None = new Option[Nothing] { ... } }
}

Enums can be GADTs (generalized ADTs).
So cases can extend the base type with different type arguments.

enum Tree[T] { 
  case True extends Tree[Boolean] 
  case False extends Tree[Boolean] 
  case IsZero(n: Tree[Int]) extends Tree[Boolean] 
  case Zero extends Tree[Int] 
  case Succ(n: Tree[Int]) extends Tree[Int] 
  case If(cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]) extends Tree[T] 
} 

Upvotes: 4

James_pic
James_pic

Reputation: 3307

Whilst it's probably not a good idea (see other posts for actual good ideas), it is possible to extend java.lang.Enum in Scala. Your code would have worked, if you'd put both the class and its companion object in the same compilation unit (in the REPL, each statement is executed in its own compilation unit, unless you use :paste mode).

If you use :paste mode, and paste in the following code, Scala will happily compile it:

sealed class AnEnum protected(name: String, ordinal: Int) extends java.lang.Enum[AnEnum](name, ordinal)
object AnEnum {
  val ENUM1 = new AnEnum("ENUM1",0)
  case object ENUM2 extends AnEnum("ENUM2", 1) // both vals and objects are possible
}

However, Java interop will probably not be satisfactory. The Java compiler adds static values and valueOf methods to new enum classes, and ensures that the names and ordinals are correct, which Scala will not.

Even if you take these steps yourselves, Java won't trust your enum because the class doesn't have the ENUM modifier. This means that Class::isEnum will say your class isn't an enum, which will affect the static Enum::valueOf method, for example. Java's switch statement won't work with them either (although Scala's pattern matching should work, if the enum values are case objects).

Upvotes: 6

Glen Best
Glen Best

Reputation: 23115

Java Enums

For an enum class Counter would be a better name than Counters - each enum value represents a singular counter.

When javac compiles an enum class, it:

  1. compiles to a normal java class (E.g. Counter) containing all of the constructors, methods, other members of the enum (if any)
  2. each enum value (GOOD_THING, BAD_THING) is made a public static field of (1) - with class equal to the class in (1) (Counter):

    // Java Code:
    class Counter {
        public static Counter GOOD_THING;
        public static Counter BAD_THING;
    
        // constructors, methods, fields as defined in the enum ...
    
    }
    
  3. initialization logic in the class automatically constructs each enum value as a singleton object

Scala Options

A. Reference Java Enum From Scala

Import Counter, refer to GOOD_THING and BAD_THING just like in java, and (if you like) additionally call Enum class methods:

// Scala Code:
import JavaSubClass.Counter;

def someDistributedTask = {
    // some work here
    if (terribleThing) {
        loggingMethod(Counter.BAD_THING)
    } else {
        loggingMethod(Counter.GOOD_THING)
        // more work here
    }
}

// Other things you can do:
val GoodThing = Counter.valueOf("GOOD_THING")

Counter.values() foreach { // do something }

counter match {
  case Counter.GOOD_THING => "Hoorah"
  case Counter.BAD_THING => "Pfft"
  case _ => throw new RuntimeException("someone added a new value?")
}

Advantages: Can do everything that java enumerations do, plus supports pattern matching. Disadvanges: Because the base trait is not sealed, any code doing pattern matching is not typed-checked to ensure exhaustive cases are covered.

B. Use Scala Enumeration

Convert java enum to equivalent scala Enumeration:

// Scala Code:
object Counter extends Enumeration {
  type Counter = Value
  val GoodThing = Value("GoodThing") 
  val BadThing = Value("BadThing")
}

Use it:

// Scala Code:
import someScalaPackage.Counter;

def someDistributedTask = {
    // some work here
    if (terribleThing) {
        loggingMethod(Counter.BadThing)
    } else {
        loggingMethod(Counter.GoodThing)
        // more work here
    }
}

// Other things you can do:
val GoodThing = Counter.withName("GoodThing")
val label = Counter.BadThing.toString

Counter.values foreach { // do something }

myCounter match {
  case Counter.GOOD_THING => "Bully!"
  case Counter.BAD_THING => "Meh"
  case _ => throw new RuntimeException("someone added a new value?")
}

Advantages: Scala's Enumeration methods are as rich as Java Enum's, plus supports pattern matching. Disadvanges: Can't do everything that java enums do - java enum's are defined as a class with arbitrary constructors, methods and other members allowable (i.e. full OO modelling on the enum basetype). Because the base trait is not sealed, any code doing pattern matching is not typed-checked to ensure exhaustive cases are covered.

C. Use Scala Case Classes:

Can convert enums directly into Case Objects (i.e. singleton objects as opposed to Case Class, which is not singleton):

sealed trait Counter
object Counter {
  case object GoodThing extends Counter;
  case object BadThing extends Counter; 
}

Use it:

// Scala Code:
import someScalaPackage.Counter;

def someDistributedTask = {
    // some work here
    if (terribleThing) {
        loggingMethod(Counter.BadThing)
    } else {
        loggingMethod(Counter.GoodThing)
        // more work here
    }
}

// Other things you can do:
// NO!!   val GoodThing = Counter.withName("GoodThing")
val label = Counter.BadThing.toString

// NO!!   Counter.values foreach { // do something }

myCounter match {
  case Counter.GOOD_THING => "Bully!"
  case Counter.BAD_THING => "Meh"
  case _ => throw new RuntimeException("someone added a new value?")
}
  • Advantage over Enumeration: each value can have a different ancestor or different mixin traits (as long as each value conforms to type Counter). Can do arbirtrarily complex OO modelling for the trait Counter and for each Value. Can then do arbitrarily complex pattern matching using all the different case object parameters for each different value. By having base trait sealed, any code doing pattern matching is typed-checked to ensure exhaustive cases are covered. (Not useful for your requirements).
  • Disadvantage over Enumeration: don't get Enumeration methods 'for free' (i.e. values, withName, application). Can be 'fixed' by adding custom implementations to base class Counter (a bit of a contridiction, since it's manual coding...).

Upvotes: 27

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297205

If you need a java enumeration, then you need to write it in Java. There are things you can do in Scala to replace the use cases of Enum, but there's nothing in Scala that replicates the Java mechanics of Enum.

Upvotes: 19

Related Questions