参考 Java Enum 作为 Scala 中的类型参数

Reffer to Java Enum as a Type parameter in Scala

我想为 protobuf 编译器生成的枚举实现一个瘦包装器,因为我需要注释一些方法。 Protobuf 生成 final class 所以我实现了一个新的通用 class 来存储枚举实例而不是扩展枚举 subclass.

Protobuf 生成的 class 如下所示

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

我实现了一个包装器 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
}

因为我想像这样使用它

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

但是,这会导致编译错误

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
                                              ^

有一种叫做擦除的东西——因为它,当你的代码被编译成 JVM 字节码时,里面没有 T 的踪迹。大多数时候。不过在 Scala 中,有一个解决方法:您可以指定一个隐式 scala.reflect.ClassTag,它由编译器生成并传递给相应的方法调用。

我在您的代码中更新了 ProtoEnumWrapper.fromStringProtoEnumWrapper.fromProtoProtoEnumWrapper 的签名:

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
}

注意 2 个替代定义:def a[T : ClassTag] 只是一种较短的写法 def a[T](implicit ct: ClassTag[T]),以防您的代码中不需要 ct 的实际实例。