我试图表达以下想法:
函数caseClassFields
应该通过处理案例类来返回(String, T)
对的数组。
我为T
设置了上限,期望它应该是AnyRef
或AnyRef
本身的子类型。
这是一个功能:
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)]
如何解决这个问题?
答案 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
}
现在,为了示例,我们要定义Int
和String
上的验证:
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))
}
}
有了这么多代码,我们现在可以验证其中包含String
和Int
字段的任何案例类,并且为更多原语添加其他验证器是微不足道的:
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.get
和T
之间存在某种联系,它可以起作用,但需要向编译器证明这种关系。由于T
可以是编译器无法证明的任何内容。
我强烈推荐这本书“宇航员的无形指南”#39;作为主题的介绍。