简体   繁体   English

如何在Scala中向Enumeration添加方法?

[英]How to add a method to Enumeration in Scala?

In Java you could: 在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();
}

How do you do this in Scala? 你是如何在Scala中做到这一点的?

EDIT / Useful links: 编辑/有用的链接:

Here is an example of adding attributes to scala enums by extending the Enumeration.Val class. 以下是通过扩展Enumeration.Val类向scala枚举添加属性的示例。

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) 

Building on Chris' solution , you can achieve somewhat nicer syntax with an implicit conversion: 基于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)
} 

Then you can call, for example, Suit.Clubs.isRed . 然后你可以打电话,例如, Suit.Clubs.isRed

Scala enumerations are distinct from Java enumerations. Scala 枚举与Java枚举不同。

At the moment, there is no way add methods to it (in a sane way). 目前,没有办法添加方法(以理智的方式)。 There are some work-arounds, but nothing which works in all cases and doesn't look like syntactic garbage. 有一些解决方法,但没有任何东西适用于所有情况看起来不像语法垃圾。

I tried something similiar (adding methods to enumerated instance of a class, while being able to create new instances at runtime and having a working equivalence relationship between the object s and the new instances of the class), but was stopped by bug #4023 ("getClasses/getDeclaredClasses seems to miss some (REPL) or all (scalac) classes (objects) declared"). 我尝试了类似的东西(在枚举类的实例中添加方法,同时能够在运行时创建新实例并在object和类的new实例之间具有工作等效关系),但是被错误#4023 ( “getClasses / getDeclaredClasses似乎错过了一些(REPL)或所有(scalac)类(对象)声明的”)。

Have a look at these related questions by me: 我看看这些相关问题:

Honestly, I wouldn't use Enumeration . 老实说,我不会使用Enumeration This is a class originating from Scala 1.0 (2004) and it has weird stuff in it and not many people (except those who have written it) understands how to use it without an tutorial first. 这是一个源自Scala 1.0(2004)的类,它有很奇怪的东西,并没有很多人(除了那些编写它的人)在没有教程的情况下理解如何使用它。

If I would absolutely need an enumeration I would just write that class in Java. 如果我绝对需要枚举,我会用Java编写该类。

If you don't need to iterate over enum values or do some other enum-ish stuff, I'd advise using ADTs instead of Enumeration . 如果你不需要迭代枚举值或做一些其他的枚举,我建议使用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

This approach has one advantage over Enumeration that compiler will warn you when you forget one or more cases in the match expression. 这种方法比Enumeration有一个优点,即编译器会在您忘记match表达式中的一个或多个案例时发出警告。

Elaborating on Aaron's solution , an even more compact form in Scala 2.10, using implicit classes : 详细阐述了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
      }
   }
} 

and then you can use it like this: Suit.Clubs.isRed 然后你就可以这样使用它: Suit.Clubs.isRed

You can do this: 你可以这样做:

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

Obviously this is not perfect but you can then do: 显然这不是完美的,但你可以这样做:

Suit.isBlack(Suit.Clubs)

Scala's Enumeration does not allow properties and/or methods to be added to values in your enumeration. Scala的Enumeration不允许将属性和/或方法添加到枚举中的值。 With this new MyEnumeration you can. 有了这个新的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)

}

Here you see the Planet example in Scala. 在这里,您可以看到Scala中的Planet示例。

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

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

If you absolutely need to have methods per enumeration value and need to be able to iterate over the values, you can do something like this: 如果你绝对需要为每个枚举值设置方法并且需要能够迭代值,你可以这样做:

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

prints 版画

OFFICIAL isOfficial=true
TEST isOfficial=false
USER isOfficial=false

This was done for some legacy code that couldn't be moved to a saner enum strategy besides Enumeration. 这是针对一些遗留代码完成的,除了Enumeration之外,这些代码无法移动到更健全的枚举策略。

After checking out the source code of scala.Enumeration, I got this: 看完scala.Enumeration的源代码后,我得到了这个:


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

It seems hard to get rid of the 'new' since anonymized class is used. 由于使用了匿名类,似乎很难摆脱'新'。 If anyone knows how to do it, let me know :) 如果有人知道怎么做,请告诉我:)

The answer, which tells that Scala enums do not support args/methods-customized values seems wrong. 答案,告诉Scala枚举不支持args /方法 - 自定义值似乎是错误的。 Other answers (some of them involve implicit ) demonstrate that it can do but they create impression that demands name duplication: your value has declared name as java object field and, secondly, the name is supplied to the value constructor as string whereas the whole point of Enums is to create a iterable name->value map and scala can do without redundancy: 其他答案(其中一些涉及implicit )表明它可以做,但它们创建需要名称重复的印象:您的值已声明名称为java对象字段,其次,名称作为字符串提供给值构造函数而整点Enums的目的是创建一个可迭代的name-> value map,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)

I think that the above is more concise than 我认为上面的内容比简洁更简洁

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))

Since there is a single method per value, we can convert them into the functions 由于每个值只有一个方法,我们可以将它们转换为函数

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))

Functions, shared among all values, can be defined like this (usable in arg parser ) 在所有值之间共享的函数可以像这样定义(在arg解析器中可用)

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

Other users argue for cases of sealed traits + macros, Iteration over a sealed trait in Scala? 其他用户争论密封特征+宏的情况, 在Scala的密封特征上进行迭代?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM