【问题标题】:Creating a Java Enum in Scala在 Scala 中创建 Java 枚举
【发布时间】:2013-12-05 22:40:02
【问题描述】:

我的工作场所一直在尝试从 Java 迁移到 Scala 来完成某些任务,并且它非常适合我们正在做的事情。但是,一些预先存在的日志记录方法需要 java.lang.Enum。记录方法在 (Java) 基类中定义,子类可以定义自己的枚举,记录器将跟踪多个线程/机器中的所有实例。

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

然后,当任务完成后,我们可以看到

BAD_THING: 230
GOOD_THING: 10345

有没有办法在 Scala 中复制这一点,无论是通过创建 Java Enums 还是从 Enumeration 转换为 Enum?我曾尝试直接扩展Enum,但它似乎已被密封,因为我在控制台中收到错误:

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

【问题讨论】:

  • 即使您正在迁移到 scala,您仍然可以编写 java 枚举并从您的 scala 代码中使用它们。恕我直言,Scala 并不容易创建枚举;一个密封的特征和一组对象要简单得多,我相信这是首选。
  • 在对 Scala 中围绕“枚举”的所有选项进行了广泛研究之后,我在另一个 StackOverflow 线程上发布了对该领域的更完整概述。它包括对“密封特征+案例对象”模式的解决方案,我已经解决了 JVM 类/对象初始化排序问题:stackoverflow.com/a/25923651/501113

标签: java scala enums


【解决方案1】:

Java 枚举

对于枚举类,Counter 将是比 Counters 更好的名称 - 每个枚举值都代表一个单一的计数器。

当 javac 编译 enum 类时,它:

  1. 编译为包含所有构造函数、方法、枚举的其他成员(如果有)的普通 java 类(例如 Counter
  2. 每个 enum 值 (GOOD_THING, BAD_THING) 都被设为 (1) 的 public static 字段 - 类等于 (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. 类中的初始化逻辑自动将每个enum值构造为单例对象

Scala 选项

A.从 Scala 中引用 Java 枚举

导入计数器,像java中一样引用GOOD_THING和BAD_THING,并且(如果你喜欢)另外调用Enum类方法:

// 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?")
}

优点:可以做java枚举做的所有事情,并且支持模式匹配。 缺点:因为基本特征不是sealed,所以任何进行模式匹配的代码都不会进行类型检查以确保涵盖详尽的案例。

B.使用 Scala 枚举

将 java enum 转换为等效的 scala Enumeration:

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

使用它:

// 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?")
}

优点:Scala 的Enumeration 方法与Java 的Enum 一样丰富,并且支持模式匹配。 缺点:不能做 java enums 所做的所有事情 - java enum 被定义为具有任意构造函数、方法和其他允许成员的类(即基于 enum 基本类型的完整 OO 建模)。因为基本特征不是sealed,所以任何进行模式匹配的代码都不会进行类型检查以确保涵盖详尽的案例。

C.使用 Scala 案例类:

可以将enums 直接转换为Case Objects(即单例对象而不是Case Class,后者不是单例):

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

使用它:

// 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?")
}
  • 优于枚举:每个值可以有不同的祖先或不同的混合特征(只要每个值符合类型计数器)。可以对特征计数器和每个值进行任意复杂的 OO 建模。然后可以对每个不同的值使用所有不同的案例对象参数进行任意复杂的模式匹配。通过具有基本特征sealed,任何进行模式匹配的代码都经过类型检查,以确保涵盖详尽的案例。 (对您的要求没有用处)。
  • 枚举的缺点:不要“免费”获取枚举方法(即值、withName、应用程序)。可以通过向基类 Counter 添加自定义实现来“修复”(有点矛盾,因为它是手动编码...)。

【讨论】:

  • 在对 Scala 中围绕“枚举”的所有选项进行了广泛研究(实际上与您的非常相似)之后,我在另一个 StackOverflow 线程上发布了对该领域的更完整概述。它包括对“密封特征+案例对象”模式的解决方案,我已经解决了 JVM 类/对象初始化排序问题:stackoverflow.com/a/25923651/501113
【解决方案2】:

如果您需要 java 枚举,那么您需要用 Java 编写它。您可以在 Scala 中做一些事情来替换 Enum用例,但 Scala 中没有任何东西可以复制 Enum 的 Java 机制。

【讨论】:

  • 在对 Scala 中围绕“枚举”的所有选项进行了广泛研究之后,我在另一个 StackOverflow 线程上发布了对该领域的更完整概述。它包括对“密封特征+案例对象”模式的解决方案,我已经解决了 JVM 类/对象初始化排序问题:stackoverflow.com/a/25923651/501113
【解决方案3】:

虽然这可能不是一个好主意(有关实际的好主意,请参阅其他帖子),但 可以在 Scala 中扩展 java.lang.Enum。如果您将类及其伴随对象放在同一个编译单元中(在 REPL 中,每条语句都在其自己的编译单元中执行,除非您使用 :paste 模式),您的代码将可以工作。

如果你使用:paste 模式,并粘贴以下代码,Scala 会愉快地编译它:

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
}

但是,Java 互操作可能不会令人满意。 Java 编译器将静态 valuesvalueOf 方法添加到新的 enum 类中,并确保名称和序数正确,而 Scala 不会。

即使您自己采取这些步骤,Java 也不会信任您的枚举,因为该类没有 ENUM 修饰符。这意味着Class::isEnum 会说您的类不是枚举,例如,这将影响静态Enum::valueOf 方法。 Java 的 switch 语句也不适用于它们(尽管 Scala 的模式匹配应该可以工作,如果枚举值是 case 对象)。

【讨论】:

    【解决方案4】:

    正如 in this thread 解释的那样,Dottyhave enum 用于 Scala 3.0(2020 年秋季)

    Scala 也重新设计了 Enums
    它们可以参数化并且可以包含自定义成员。

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

    来自“Martin Odersky -- A tour of Scala 3”(2019 年 6 月):

    枚举可以参数化。

    enum Day(val mon: Int) {}
    

    枚举:

    • 可以有参数
    • 可以定义字段和方法
    • 可以与 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)
      ... 
    } 
    

    枚举可以有类型参数,使它们成为代数数据类型 (ADT)

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

    枚举编译为案例类和对象的密封层次结构。

    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] { ... } }
    }
    

    枚举可以是 GADT(广义 ADT)。
    因此,case 可以使用不同的类型参数扩展基本类型。

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-02-27
    • 1970-01-01
    • 1970-01-01
    • 2015-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-07
    相关资源
    最近更新 更多