Подтвердить что ты не робот

Создание Java-перечисления в Scala

Мое рабочее место экспериментировало при переходе с Java на Scala для некоторых задач, и оно хорошо работает для того, что мы делаем. Однако некоторые существующие методы ведения журнала ожидают a 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 Enum или преобразовать из 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
4b9b3361

Ответ 1

Если вам нужно перечисление java, вам нужно записать его на Java. Есть вещи, которые вы можете сделать в Scala для замены случаев использования Enum, но в Scala ничего нет, что реплицирует механику Java Enum.

Ответ 2

Перечисления Java

Для класса enum Counter будет лучшее имя, чем Counters - каждое значение перечисления представляет собой сингулярный счетчик.

Когда javac компилирует класс enum, он:

  • компилируется в обычный класс java (например, Counter), содержащий все конструкторы, методы, другие члены перечисления (если есть)
  • каждое значение enum (GOOD_THING, BAD_THING) получается полем public static (1) - с классом, равным классу в (1) (Counter):

    // Java Code:
    class Counter {
        public static Counter GOOD_THING;
        public static Counter BAD_THING;
    
        // constructors, methods, fields as defined in the enum ...
    
    }
    
  • логика инициализации в классе автоматически создает каждое значение enum как одноэлементный объект

Scala Параметры

а. Ссылка Java Enum От Scala

Импортировать счетчик, обратитесь к GOOD_THING и BAD_THING, как в java, и (если хотите) дополнительно вызовите методы класса 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, любой код, выполняющий сопоставление с образцом, не проверяется на типе, чтобы обеспечить исчерпывающие случаи.

В. Используйте 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 enum do - java enum определяется как класс с допустимыми конструкциями, методами и другими членами (т.е. Полное OO-моделирование по базовому типу enum). Поскольку базовый признак не является sealed, любой код, выполняющий сопоставление с образцом, не проверяется на типе, чтобы гарантировать, что исчерпывающие случаи охвачены.

С. Используйте классы Scala Case:

Может преобразовать enum непосредственно в объекты Case (т.е. объекты singleton, в отличие от 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?")
}
  • Преимущество над перечислением: каждое значение может иметь разные предки или разные признаки микшина (до тех пор, пока каждое значение соответствует типу Counter). Могут делать arbirtrarily сложное OO-моделирование для счетчика признаков и для каждого значения. Затем можно выполнить произвольно сложное сопоставление шаблонов, используя все различные параметры объекта case для каждого другого значения. Имея базовый признак sealed, любой код, выполняющий сопоставление с образцом, проверяется на типе, чтобы гарантировать, что исчерпывающие случаи закрыты. (Не подходит для ваших требований).
  • Недостаток над перечислением: не получайте методы перечисления "бесплатно" (т.е. значения, withName, application). Могут быть "исправлены" путем добавления пользовательских реализаций к базовому классу Counter (немного для справки, так как это ручное кодирование...).

Ответ 3

Хотя это, вероятно, не очень хорошая идея (см. другие сообщения для реальных хороших идей), можно расширить java.lang.Enum в Scala. Ваш код сработал бы, если бы вы поместили как класс, так и его сопутствующий объект в один и тот же блок компиляции (в 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 добавляет статические методы values и valueOf к новым классам enum и гарантирует, что имена и ординалы верны, а Scala не будет.

Даже если вы предпримете эти шаги самостоятельно, Java не будет доверять вашему перечислению, потому что класс не имеет модификатора enum. Это означает, что Class::isEnum скажет, что ваш класс не является перечислением, что скажется, например, на статическом методе Enum::valueOf. Оператор переключения Java также не будет работать с ними (хотя Scala соответствие шаблонов должно работать, если значения перечисления являются объектными объектами).

Ответ 4

Как объясняется в этой ветке, у Дотти будет enum для Scala 3.0 (середина 2020 года, семь лет спустя)

Scala также переработал Enums.
Они могут быть параметризованы и могут содержать пользовательские элементы.

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
}

Из " Мартина Одерского - экскурсия по Scala 3 " (июнь 2019 года):

Перечисления могут быть параметризованы.

enum Day(val mon: Int) {}

Перечни:

  • может иметь параметры
  • может определять поля и методы
  • может взаимодействовать с Java
enum Planet(mass: Double, radius: Double)
extends java.lang.Enum { 
  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).
Таким образом, случаи могут расширять базовый тип аргументами другого типа.

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