Scala:上限[T&lt ;: AnyRef]不允许返回[AnyRef]

时间:2017-05-22 10:16:54

标签: scala

我试图表达以下想法: 函数caseClassFields应该通过处理案例类来返回(String, T)对的数组。 我为T设置了上限,期望它应该是AnyRefAnyRef本身的子类型。

这是一个功能:

def caseClassFields[T <: AnyRef](obj: AnyRef): Array[(String, T)] = {
  val metaClass = obj.getClass
  metaClass.getDeclaredFields.map {
    field => {
      field.setAccessible(true)
      (field.getName, field.get(obj))
    }
  }
}

但遗憾的是我收到了以下错误:

Array[(String, AnyRef)]类型的表达式不符合预期类型Array[(String, T)]

如何解决这个问题?

3 个答案:

答案 0 :(得分:4)

通过反射和保持类型安全来做你想要的是正交要求。但是shapeless,一个用于泛型推导的库,可以做你想要的,并且仍然保证你输入的安全。

这是一个简单的例子,使用无形来帮助你入门。

我们首先定义代数:

sealed trait ValidatableField

case class ValidatableString(value: Boolean)
    extends ValidatableField

case class ValidatableInt(value: Boolean) extends ValidatableField

case class ValidatableRecord(fields: List[(String, ValidatableField)])
    extends ValidatableField

现在我们定义验证器特征:

trait Validator[T] {
  def validate(value: T): ValidatableField
}

trait RecordValidator[T] extends Validator[T] {
  def validate(value: T): ValidatableRecord
}

现在,为了示例,我们要定义IntString上的验证:

implicit val intValidator = new Validator[Int] {
  override def validate(t: Int): ValidatableField = ValidatableInt(t > 42)
}

implicit val stringValidator = new Validator[String] {
  override def validate(t: String): ValidatableField = ValidatableString(t.length < 42)
}

现在我们为HList定义一个通用实现,它将覆盖我们的ValidatableRecord,它是我们案例类的通用表示:

implicit val hnilEncoder: RecordValidator[HNil] = new RecordValidator[HNil] {
  override def validate(value: HNil): ValidatableRecord = ValidatableRecord(Nil)
} 

implicit def hlistValidator[K <: Symbol, H, T <: HList](
  implicit witness: Witness.Aux[K],
  hEncoder: Lazy[Validator[H]],
  tEncoder: RecordValidator[T]
): RecordValidator[FieldType[K, H] :: T] = {
  val fieldName = witness.value.name
  new RecordValidator[::[FieldType[K, H], T]] {
    override def validate(value: ::[FieldType[K, H], T]): ValidatableRecord = {
      val head = hEncoder.value.validate(value.head)
      val tail = tEncoder.validate(value.tail)
      ValidatableRecord((fieldName, head) :: tail.fields)
    }
  }
}

implicit def genericEncoder[A, H <: HList](
  implicit generic: LabelledGeneric.Aux[A, H],
  hEncoder: Lazy[RecordValidator[H]]): Validator[A] = {
    new RecordValidator[A] {
      override def validate(value: A): ValidatableRecord =
        hEncoder.value.validate(generic.to(value))
  }
}

有了这么多代码,我们现在可以验证其中包含StringInt字段的任何案例类,并且为更多原语添加其他验证器是微不足道的:

object Test {
  def main(args: Array[String]): Unit = {
    case class Foo(s: String, i: Int)
    val foo = Foo("hello!", 42)
    println(Validator[Foo].validate(foo))
  }
}

收率:

ValidatableRecord(List((s,ValidatableString(true)), (i,ValidatableInt(false))))

我知道这可能会让人不知所措,但David Gurnells "Guide To Shapeless"是一个很好的开始。

答案 1 :(得分:2)

原因是field.get(obj)返回AnyRef,而您的返回类型为T。因此,您需要将其转换为T。但是,我在代码中看不到Generic type T的使用情况,因此您只需将返回类型更改为Array[(String, AnyRef)]

def caseClassFields[T <: AnyRef](obj: AnyRef): Array[(String, AnyRef)]

但是,如果您坚持使用Generic,则需要将field.get(obj)转换为T类型。请注意,在转换为T类型时,如果类型无效,您可能会遇到异常。

  def caseClassFields[T <: AnyRef](obj: AnyRef): Array[(String, T)] = {
    val metaClass = obj.getClass
    metaClass.getDeclaredFields.map {
      field => {
        field.setAccessible(true)
        (field.getName, field.get(obj).asInstanceOf[T])
      }
    }
  }
  case class Foo(name:String)

  val result:Array[(String, String)] = caseClassFields[String](Foo("bar"))

答案 2 :(得分:1)

正如评论中所讨论的,你可能想要使用无形,但要详细说明。

方法field.get()与type参数无关。通常你会使用像这样的类型参数

def caseClassFields[T <: AnyRef](obj: T): Array[(String, T)] = ???

或者这个..

def caseClassFields[T <: AnyRef](obj: Container[T]): Array[(String, T)] = ???

如果field.getT之间存在某种联系,它可以起作用,但需要向编译器证明这种关系。由于T可以是编译器无法证明的任何内容。

我强烈推荐这本书“宇航员的无形指南”#39;作为主题的介绍。

http://underscore.io/books/shapeless-guide/

https://github.com/milessabin/shapeless