Как добавить метод к перечислению в Scala?
В Java вы могли:
public enum Enum {
ONE {
public String method() {
return "1";
}
},
TWO {
public String method() {
return "2";
}
},
THREE {
public String method() {
return "3";
}
};
public abstract String method();
}
Как вы это делаете в Scala?
EDIT/Полезные ссылки:
Ответы
Ответ 1
object Unit extends Enumeration {
abstract class UnitValue(var name: String) extends Val(name) {
def m: Unit
}
val G = new UnitValue("g") {
def m {
println("M from G")
}
}
val KG = new UnitValue("kg") {
def m {
println("M from KG")
}
}
}
Ответ 2
Вот пример добавления атрибутов в scala enums путем расширения класса Enumeration.Val.
object Planet extends Enumeration {
protected case class Val(val mass: Double, val radius: Double) extends super.Val {
def surfaceGravity: Double = Planet.G * mass / (radius * radius)
def surfaceWeight(otherMass: Double): Double = otherMass * surfaceGravity
}
implicit def valueToPlanetVal(x: Value) = x.asInstanceOf[Val]
val G: Double = 6.67300E-11
val Mercury = Val(3.303e+23, 2.4397e6)
val Venus = Val(4.869e+24, 6.0518e6)
val Earth = Val(5.976e+24, 6.37814e6)
val Mars = Val(6.421e+23, 3.3972e6)
val Jupiter = Val(1.9e+27, 7.1492e7)
val Saturn = Val(5.688e+26, 6.0268e7)
val Uranus = Val(8.686e+25, 2.5559e7)
val Neptune = Val(1.024e+26, 2.4746e7)
}
scala> Planet.values.filter(_.radius > 7.0e6)
res16: Planet.ValueSet = Planet.ValueSet(Jupiter, Saturn, Uranus, Neptune)
Ответ 3
Основываясь на решении Chris, вы можете добиться более приятного синтаксиса с неявным преобразованием:
object Suit extends Enumeration {
val Clubs, Diamonds, Hearts, Spades = Value
class SuitValue(suit: Value) {
def isRed = !isBlack
def isBlack = suit match {
case Clubs | Spades => true
case _ => false
}
}
implicit def value2SuitValue(suit: Value) = new SuitValue(suit)
}
Затем вы можете вызвать, например, Suit.Clubs.isRed
.
Ответ 4
Scala перечисления отличаются от перечислений Java.
В настоящий момент нет способа добавить к нему методы (разумным способом). Есть несколько обходов, но ничего, что работает во всех случаях и не похоже на синтаксический мусор.
Я попробовал что-то похожее (добавление методов к перечисляемому экземпляру класса, будучи в состоянии создавать новые экземпляры во время выполнения и иметь рабочее эквивалентное отношение между экземплярами object
и new
класса), но было остановился на ошибке # 4023 ( "getClasses/getDeclaredClasses, кажется, пропустил некоторые (REPL) или все (scalac) классы (объекты), объявленные" ).
Посмотрите на эти связанные вопросы:
Честно говоря, я бы не использовал Enumeration
. Это класс, исходящий из Scala 1.0 (2004), и в нем есть странные вещи, и не многие люди (кроме тех, кто его написал) понимают, как использовать его без учебника.
Если мне понадобится перечисление, я просто напишу этот класс в Java.
Ответ 5
Если вам не нужно перебирать значения enum или делать какие-либо другие перечислимые вещи, я бы посоветовал использовать ADT вместо Enumeration
.
sealed abstract class Enum {
def method: String = this match {
case One => "1"
case Two => "2"
case Three => "3"
}
}
case object One extends Enum
case object Two extends Enum
case object Three extends Enum
Этот подход имеет одно преимущество перед Enumeration
, что компилятор предупредит вас, когда вы забудете один или несколько случаев в выражении match
.
Ответ 6
Вы можете сделать это:
object Suit extends Enumeration {
val Clubs, Diamonds, Hearts, Spades = Value
def isRed(suit : Value) = !isBlack(suit)
def isBlack(suit : Value) = suit match {
case Clubs | Spades => true
case _ => false
}
}
Очевидно, это не идеально, но вы можете:
Suit.isBlack(Suit.Clubs)
Ответ 7
Разрабатывая решение Aaron, еще более компактную форму в Scala 2.10, используя неявные классы:
object Suit extends Enumeration {
val Clubs, Diamonds, Hearts, Spades = Value
implicit class SuitValue(suit: Value) {
def isRed = !isBlack
def isBlack = suit match {
case Clubs | Spades => true
case _ => false
}
}
}
а затем вы можете использовать его следующим образом: Suit.Clubs.isRed
Ответ 8
Scala Перечисление не позволяет добавлять свойства и/или методы к значениям в вашем перечислении. С помощью этого нового MyEnumeration вы можете.
abstract class MyEnumeration {
// "Value" must be the name of the class defining your values type Value
type Value
// Contains your values in definition order
private val vals = collection.mutable.LinkedHashMap[String, Value]()
// A mixin for your values class to automatically collect the values
protected trait ValuesCollector { self: Value =>
private val ordinal = vals.size
vals += (fieldNames(ordinal) -> self)
def getName = fieldNames(ordinal)
override def toString = getName
}
def apply(ordinal: Int) = vals(fieldNames(ordinal))
def apply(fldName: String) = vals(fldName)
def values = vals.values
def namedValues: collection.Map[String, Value] = vals
// Getting the field names through reflection.
// Copied from scala.Enumeration
private val fieldNames = getClass.getMethods filter (m =>
m.getParameterTypes.isEmpty &&
classOf[ValuesCollector].isAssignableFrom(m.getReturnType) &&
m.getDeclaringClass != classOf[MyEnumeration]) map (_.getName)
}
Здесь вы видите пример Планеты в Scala.
object Planet extends MyEnumeration {
case class Value(val mass: Double, val radius: Double) extends ValuesCollector {
// universal gravitational constant (m3 kg-1 s-2)
private val G = 6.67300E-11;
def surfaceGravity = G * mass / (radius * radius)
def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity
}
val MERCURY = Value(3.303e+23, 2.4397e6)
val VENUS = Value(4.869e+24, 6.0518e6)
val EARTH = Value(5.976e+24, 6.37814e6)
val MARS = Value(6.421e+23, 3.3972e6)
val JUPITER = Value(1.9e+27, 7.1492e7)
val SATURN = Value(5.688e+26, 6.0268e7)
val URANUS = Value(8.686e+25, 2.5559e7)
val NEPTUNE = Value(1.024e+26, 2.4746e7)
val PLUTO = Value(1.27e+22, 1.137e6)
}
object PlanetTest {
def main(args: Array[String]) {
val earthWeight = 175
val mass = earthWeight/Planet.EARTH.surfaceGravity
for (p <- Planet.values) println("Your weight on %s is %f" format (p, p.surfaceWeight(mass)))
/* Your weight on MERCURY is 66.107583
* Your weight on VENUS is 158.374842
* Your weight on EARTH is 175.000000
* Your weight on MARS is 66.279007
* Your weight on JUPITER is 442.847567
* Your weight on SATURN is 186.552719
* Your weight on URANUS is 158.397260
* Your weight on NEPTUNE is 199.207413
* Your weight on PLUTO is 11.703031
*/
}
}
Ответ 9
После проверки исходного кода scala.Eneration, я получил следующее:
object MyEnum extends Enumeration {
val ONE = new Val { def method = "1" }
val TWO = new Val { def method = "2" }
val THREE = new Val { def method = "3" }
}
Кажется, сложно избавиться от "нового", так как используется анонимный класс.
Если кто-нибудь знает, как это сделать, дайте мне знать:)
Ответ 10
Если вам абсолютно необходимо иметь методы для значения перечисления и нужно иметь возможность перебирать значения, вы можете сделать что-то вроде этого:
object BatchCategory extends Enumeration {
class BatchCategory extends Val {
val isOfficial, isTest, isUser = false
}
val OFFICIAL = new BatchCategory { override val isOfficial = true }
val TEST = new BatchCategory { override val isTest = true }
val USER = new BatchCategory { override val isUser = true }
// Needed to get BatchCategory from Enumeration.values
implicit def valueToBatchCategory(v: Value): BatchCategory = v match {
case bc: BatchCategory => bc
case x => throw new IllegalArgumentException("Value is not a BatchCategory: " + x)
}
def valueOf(catStr: String): BatchCategory = {
BatchCategory.values.
find { v => val s = v.toString; s.take(1) == catStr || s == catStr }.
getOrElse(throw new IllegalArgumentException("Unknown category '" + catStr + "' ! "))
}
def main(args: Array[String]) {
BatchCategory.values.foreach(v => println(v + " isOfficial=" + v.isOfficial))
}
}
печатает
OFFICIAL isOfficial=true
TEST isOfficial=false
USER isOfficial=false
Это было сделано для некоторого устаревшего кода, который не мог быть перенесен в более эффективную стратегию перечисления, кроме Enumeration.
Ответ 11
Ответ в котором говорится, что Scala enums не поддерживает значения, настроенные на использование аргументов/методов, кажется неправильным. Другие ответы (некоторые из них включают implicit
) показывают, что он может это сделать, но они создают впечатление, требующее дублирования имен: ваше значение объявляло имя как поле Java-объекта, а во-вторых, имя передается конструктору значения как строке, тогда как целая точка Enums - создать итерируемое имя → карту значений и Scala может обойтись без избыточности:
object Ops1 extends Enumeration {
protected case class OpsVal(f: Int => Int) extends super.Val(/*nextId*/)
val ZERO = new FuncVal (x => 0)
val DOUBLE = new FuncVal (x => 2 * x )
implicit def convert(v: Value) = v.asInstanceOf[OpsVal]
}
// implicit is not needed
Ops1.ZERO.f(1) //> res0: Int = 0
// implicit is needed
Ops1.values map (v => (v + "=>" + v.f(1)))//> res1: scala.collection.immutable.SortedSet[String] = TreeSet(DOUBLE=>2, ZERO=>0)
Я думаю, что приведенное выше является более кратким, чем
object Ops2 extends Enumeration {
protected abstract class OpsVal extends Val() {
def f(a: Int): Int
}
val ZERO = new OpsVal { def f(x: Int) = 0 }
val DOUBLE = new OpsVal { def f(x: Int) = 2 * x }
implicit def convert(valu: Value) = valu.asInstanceOf[OpsVal]
}
Ops2.ZERO.f(1) // implicit is not needed //> res2: Int = 0
// implicit is needed
Ops2_3.values map (v => (v, v(1))) //> res7: scala.collection.immutable.SortedSet[(e.Ops2_3.Value, Int)] = TreeSet
//| ((ZERO,0), (DOUBLE,2))
Так как для каждого значения существует единственный метод, мы можем преобразовать их в функции
object Ops2_3 extends Enumeration {
protected case class FuncVal(f: Int => Int) extends Val {
def apply(x: Int) = f(x) // no need to extend Function1 explicitly
}
val ZERO = new FuncVal (x => 0)
val DOUBLE = new FuncVal (x => 2 * x )
implicit def convert(v: Value) = v.asInstanceOf[FuncVal]
}
Ops2_3.ZERO(1) // implicit is not needed //> res6: Int = 0
// implicit is needed
Ops2_3.values map (v => (v, v(1))) //> res7: scala.collection.immutable.SortedSet[(e.Ops2_3.Value, Int)] = TreeSet
//| ((ZERO,0), (DOUBLE,2))
Функции, общие для всех значений, могут быть определены следующим образом (можно использовать в анализаторе аргументов)
val args: Array[String] = "-silent -samples 100 -silent ".split(" +").toArray
//> args : Array[String] = Array(-silent, -samples, 100, -silent)
object Opts extends Enumeration {
val nopar, silent, samples = new Val() {
def apply() = args.contains(toString)
def asInt(default: Int) = { val i = args.indexOf(toString) ; if (i == -1) default else args(i+1).toInt}
def asInt: Int = asInt(-1)
override def toString = "-" + super.toString
}
}
Opts.nopar() //> res0: Boolean = false
Opts.samples.asInt //> res1: Int = 100
Другие пользователи приводят аргументы за случаи запечатанных признаков + макросы, Итерация по запечатанному признаку в Scala?