简体   繁体   中英

Reffer to Java Enum as a Type parameter in Scala

I want to implement a thin wrapper for enum generated by protobuf compiler, because I need to annotate some methods. Protobuf generates final class so I implement a new generic class that stores a enum instance instead of extending the enum subclass.

Protobuf-generated class is like bellow

  public enum CardType
    implements com.google.protobuf.ProtocolMessageEnum {
  /**
   * <code>NONE = 0;</code>
   */
  NONE(0, 0)
  }

and I implement a wrapper class

object ProtoEnumWrapper {
  def fromString[T <: Enum[T] with ProtocolMessageEnum](s: String): ProtoEnumWrapper[T] =
    Enum.valueOf[T](classOf[T], s) match {
      case null => throw new InvalidStringForEnum(s"${s} is not value of ${classOf[T]}")
    }

  def fromProto[T <: Enum[T] with ProtocolMessageEnum](proto: T): ProtoEnumWrapper[T] =
    new ProtoEnumWrapper[T](proto)

  class InvalidStringForEnum(message: String) extends RuntimeException(message)
}

class ProtoEnumWrapper[T <: Enum[T] with ProtocolMessageEnum](proto: T) {
  override def equals(o: Any) = o match {
    case x: ProtoEnumWrapper[T] => proto == x.proto
    case e: T => proto == e
    case s: String => toString == s
    case _ => false
  }

  override def toString = proto.toString
}

as I want to use this like

val wrapper = ProtoEnumWrapper.fromString[CardType]("NONE")
val proto = CardType.valueOf("NONE")
val wrapper2 = ProtoEnumWrapper.fromProto[CardType](proto)

wrapper == proto // true
wrapper == wrapper2 // true
wrapper == "NONE" // true

but, this causes compile errors

Error:(7, 29) class type required but T found
    Enum.valueOf[T](classOf[T], s) match {
                            ^
Error:(8, 84) class type required but T found
      case null => throw new InvalidStringForEnum(s"${s} is not value of ${classOf[T]}")
                                                                                   ^
Error:(19, 47) value proto is not a member of jp.pocket_change.voucher.domain.ProtoEnumWrapper[T]
case x: ProtoEnumWrapper[T] => proto == x.proto
                                              ^

There's a thing called erasure - because of it, when your code is compiled to JVM bytecode, there is no trace of T in it. Most of the time. In Scala though, there is a workaround for it: you may specify an implicit scala.reflect.ClassTag which is generated by the compiler and passed to respective method invocations.

I updated the signatures of ProtoEnumWrapper.fromString , ProtoEnumWrapper.fromProto and ProtoEnumWrapper in your code:

object ProtoEnumWrapper {
  def fromString[T <: Enum[T] with ProtocolMessageEnum](s: String)(implicit ct: ClassTag[T]: ProtoEnumWrapper[T] = try {
    new ProtoEnumWrapper(Enum.valueOf[T](ct.runtimeClass, s))
  } catch {
    case e: IllegalArgumentException => throw new InvalidStringForEnum(s"${s} is not a value of ${ct.runtimeClass}")
  }

  def fromProto[T <: Enum[T] with ProtocolMessageEnum : ClassTag](proto: T): ProtoEnumWrapper[T] =
    new ProtoEnumWrapper[T](proto)

  class InvalidStringForEnum(message: String) extends RuntimeException(message)
}

class ProtoEnumWrapper[T <: Enum[T] with ProtocolMessageEnum : ClassTag](proto: T) {
  override def equals(o: Any) = o match {
    case x: ProtoEnumWrapper[T] => proto == x.proto
    case e: T => proto == e
    case s: String => toString == s
    case _ => false
  }

  override def toString = proto.toString
}

Note the 2 alternative definitions: def a[T : ClassTag] is just a shorter way to write def a[T](implicit ct: ClassTag[T]) in case you don't need an actual instance of ct in your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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