【问题标题】:How to add a method to Enumeration in Scala?如何在 Scala 中向枚举添加方法?
【发布时间】:2010-12-03 14:38:03
【问题描述】:

在 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 中做到这一点?

编辑/有用的链接:

【问题讨论】:

标签: java scala enums enumeration


【解决方案1】:

这是一个通过扩展 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) 

【讨论】:

  • 您也可以使用super.Val 扩展,而无需nextIdname 参数。像这样:protected case class Val(val mass: Double, val radius: Double) extends super.Val。这样您就可以在没有值名称的情况下创建值,如下所示:val Mercury = Val(3.303e+23, 2.4397e6)
  • 我不明白在构造函数参数中复制名称有什么好处?枚举不应该知道它们的值的名称吗?官方文档说他们有scala-lang.org/api/current/index.html#scala.Enumeration
  • 投票赞成修改,但可以通过演示如何从 val 声明中检索名称来改进答案。
  • 您还需要import scala.language.implicitConversions 来禁止编译器警告。
【解决方案2】:

Chris' solution 的基础上,您可以通过隐式转换获得更好的语法:

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

【讨论】:

  • 现在你甚至可以使用隐式类而不是类 + 隐式定义:隐式类 SuitValue(...) {...}
【解决方案3】:

Scala enumerations 不同于 Java 枚举。

目前,没有办法向它添加方法(以理智的方式)。有一些变通方法,但没有任何方法适用于所有情况并且看起来不像语法垃圾。

我尝试了类似的方法(向类的枚举实例添加方法,同时能够在运行时创建新实例,并且在类的 objects 和 new 实例之间具有工作等效关系),但是被错误#4023 停止(“getClasses/getDeclaredClasses 似乎错过了一些(REPL)或所有(scalac)类(对象)声明”)。

看看我提出的这些相关问题:

老实说,我不会使用Enumeration。这是一个源自 Scala 1.0 (2004) 的类,其中包含一些奇怪的东西,而且没有多少人(除了编写它的人)知道如何在没有教程的情况下使用它。

如果我绝对需要枚举,我会用 Java 编写该类。

【讨论】:

  • @axel22:如果他们能够解决错误 #4023,我最终将能够创建一个 Enum,它将基于类/对象的方法的所有优点与 @987654329 的所有优点相结合@接近...
  • 是否可以定义从特定枚举到包含该方法的类的隐式转换?
  • @soc: 期待已久的 scala 反射库或许可以解决声明内部类的问题...
  • @soc:至少当他们写 scala.reflect 时,他们必须测试一切是否正常。
  • @Ken:我希望他们能早点修复它:-)
【解决方案4】:

如果您不需要遍历枚举值或做一些其他类似枚举的事情,我建议您使用 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 表达式中的一个或多个案例时,编译器会警告您。

【讨论】:

  • 虽然这提供了详尽的模式匹配的好处,但它通过要求明确定义整数值引入了人为错误的可能性。 IOW,如果有人要添加一个 case 对象 Four,然后使用 case Four => "3" 扩展匹配,则反向映射将不起作用,因为现在有两个 case 对象具有相同的序数值。
【解决方案5】:

详细说明Aaron's solution,这是Scala 2.10 中更紧凑的形式,使用implicit classes

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

【讨论】:

    【解决方案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】:

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

    【讨论】:

      【解决方案8】:
      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")
          }
        }
      }
      

      【讨论】:

      【解决方案9】:

      在查看了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" }
      }
      

      由于使用了匿名类,因此似乎很难摆脱“新”。 如果有人知道怎么做,请告诉我:)

      【讨论】:

        【解决方案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】:

          answer, which tells that Scala enums do not support args/methods-customized values 似乎是错误的。其他答案(其中一些涉及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))
          

          所有值共享的函数可以这样定义(可在arg parser 中使用)

          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
          

          其他用户争论密封特征+宏的情况,Iteration over a sealed trait in Scala?

          【讨论】:

          • 在对选项进行了广泛的研究之后,我发布了对该领域的更完整的概述,包括对“密封特征+案例对象”模式的解决方案,其中我解决了 JVM 类/对象初始化排序问题:stackoverflow.com/a/25923651/501113
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-12-12
          • 1970-01-01
          • 2022-01-18
          • 1970-01-01
          • 2015-11-29
          • 2021-07-18
          • 1970-01-01
          相关资源
          最近更新 更多