假设User
是一个保存用户信息的case类:
case class User(name: String, age: Int)
给定字段名称(例如"name"
或"age"
),我想返回一个函数提取此字段(无需再次解析字段名称)。简而言之,我需要这个工作:
val u = User(name = "john",age = 44)
val func = extractFunctionFromFieldName("name") // returns func: User => Any
func(u) // return "john"
阅读反射,我得到了这样的工作:
def extractFunctionFromFieldName(s: String): User => Any = {
val f = classOf[User].getDeclaredField(s)
f.setAccessible(true)
u: User => f.get(u)
}
问题是这个函数是不可序列化的,因为java.lang.reflect.Field
是不可序列化的。
有什么建议或替代方案吗?
注释/更多背景:
- 字段列表比
{name,age}
大得多,并且在不断增长。这就是为什么我要避免硬编码 - 使用
productElement
应该没有序列化问题,因为字段被替换为数字。但是我知道产品元素的顺序不能保证。 - BTW,我需要它是可序列化的,因为我使用apache spark。
使用productElement应该没有序列化问题,因为字段被数字替换了。但是我知道产品要素的顺序不能保证。
。对于case类,它们将与参数的顺序相同。
或者你可以创建一个类,它仍然可以是一个函数(这里更通用一点;如果不需要,更改为仅使用User
应该很容易):
case class ExtractField[T](s: String)(implicit t: ClassTag[T]) extends (T => Any) {
@transient lazy val f = {
val f = t.runtimeClass.getDeclaredField(s)
f.setAccessible(true)
f
}
def apply(x: T) = f.get(x)
}
这将只在第一次应用时初始化该字段,因此可以在一次反序列化后多次应用该字段,而无需重复getDeclaredField
调用。
我最初没有注意到这已经是问题所要求的,所以我最初给出的另一个解决方案是不适用的:您可以将f
移动到函数内部(在您的情况下,当您创建函数对象并被它"捕获"时,f
已经已知,这就是为什么它必须被序列化):
def extractFunctionFromFieldName(s: String): User => Any = { u: User =>
val f = classOf[User].getDeclaredField(s)
f.setAccessible(true)
f.get(u)
}