Scala class that handles enumerations generically

时光怂恿深爱的人放手 提交于 2019-12-11 09:54:21

问题


I want to create a generic class that holds the value of an enumeration, and also allows access to the possible values of the enumeration. Think of a property editor for example - you need to know the current value of the property, and you also need to be able to know what other values are legal for the property. And the type of enumeration should not be known in advance, you should be able to work with any kind of enumeration.

My first thought was something like this:

class EnumerationProperty[T <: Enumeration](value:T)

However, that doesn't work because for enumerations T isn't a type, it's an object. Other variations I have tried are:

class EnumerationProperty[T <: Enumeration](value:T.Value)
class EnumerationProperty[T <: Enumeration.Value](value:T)

(I won't go into the details of why these don't work because I suspect the reasons aren't interesting.)


回答1:


In your use case (a property editor) I think the best solution is to rely on scala reflection available in scala 2.10. Given a class's TypeTag, you can get the full type (without erasure) of all of its members plus their values, and from that populate your property editor. For enumerations, use the TypeTag to get their values using the following method: Using Scala 2.10 reflection how can I list the values of Enumeration?


Now, maybe you don't want or can't use scala reflection, and from now on I'll suppose this is the case. If that is true then you are opening a whole can of worm :) TL;DR: This is not possible using a standard Enumeration, so you'll probably have to explcitly wrap the enumeration values as shown in Ptharien's Flame's answer (or roll your own fork of Enumeration). Below I detail all my attempts, please bear with me.

Unfortunately, and for some unknown reason to me (though I suspect it has to do with serialization issues), Enumeration.Value has no field pointing to its Enumeration instance. Given how Enumeration is implemented, this would be trivial to implement, but of course wehave no say, short of forking Enumeration and modifying our version (which is actually what I did for this very purpose, plus to add proper support for serialization and reflection - but I diggress).

If we can't modify Enumeration, maybe we can just extend it? Looking at the implementation again, something like this would seem to work:

class EnumerationEx extends Enumeration {
  protected class ValEx(i: Int, name: String) extends Val(i, name) {
    @transient val enum: Enumeration = EnumerationEx.this
  }
  override protected def Value(i: Int, name: String): Value = new ValEx(i, name)  
}

object Colors extends EnumerationEx {
  val Red, Green, Blue = Value
}

The downside would be that it only works for enumerations that explicitly extend EnumerationEx instead of Enumeration, but it would be better than nothing.

Unfortunately, this does not compile simply because def Value ... is declared final in Enumeration so there is no way to override it. (Note again that forking Enumeration would allow to circunvent this. Actually, why not do it, as we are already down the path of using a custom Enumeration anwyay. I'll let you judge).

So here is another take on it:

class EnumerationEx extends Enumeration {
  class ValueWithEnum( inner: Value ) {
    @transient val enum: Enumeration = EnumerationEx.this
  }
  implicit def valueToValue( value: Value ): ValueWithEnum = new ValueWithEnum( value )
}

And indeed it works as expected. Or so it seems.

scala> object Colors extends EnumerationEx {
     |   val Red, Green, Blue = Value
     | }
defined module Colors

scala> val red = Colors.Red
red: Colors.Value = Red

scala> red.enum.values
res58: Enumeration#ValueSet = Colors.ValueSet(Red, Green, Blue)

Hooray? Well no, because the conversion from Value to ValueWithEnum is done only when accessing red.enum, not at the time of instantiation of the Colors enumeration. In other words, when calling enum the compiler needs to know the exact static type of the enumeration (the compiler must statically know that red's type is Colors.Value, and not just Enumeration# Value). And in the use case you mention (a property editor) you can only rely to java reflection (I already assumed that you won't use scala reflection) to get the type of an enumeration value, and java reflection will only give you Enumeration#Val (which extends Enumeration#Value) as the type of Colors.Red. So basically you are stuck here. Your best bet is definitly to use scala reflection in the first place.




回答2:


For first part of your question. You can define holder for generic enum value like this:

case class EnumerationProperty[T <: Enumeration#Value](value:T)

But I don't know how to get all enum values without explicitly pass Enumeration object. Enumeration has values() method to get all values. And Enumeration#Value has link to Enumeration, but with private access




回答3:


class EnumProps[E <: Enumeration](val e: E)(init: e.Value) {...}

Then you can use e and e.Value to implement the class.



来源:https://stackoverflow.com/questions/14451152/scala-class-that-handles-enumerations-generically

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!