【问题标题】:Type-safe usage of Java reflection in ScalaScala 中 Java 反射的类型安全使用
【发布时间】:2019-08-14 17:07:35
【问题描述】:

我正在尝试使用 Java 反射在运行时替换对象字段的值,但以类型安全的方式。

假设我们有以下对象。

import cats.Eval

object Foo {
  val bar: Eval[Int] = Eval.later(throw new Exception)
}

我们想在运行时更改Foo.bar 的值。我们可以使用普通的 Java 反射轻松定义以下方法:

import java.lang.reflect.Field

def usingJavaReflection[T](targetObject: AnyRef, fieldName: String)(newValue: T): Unit = {
  val field = targetObject.getClass.getDeclaredField(fieldName)
  field.setAccessible(true)
  field.set(targetObject, newValue)
}

// usage
usingJavaReflection(Foo, "bar")(Eval.now(42))

现在问题变成了:如何以更类型安全的方式做到这一点?

def usingJavaReflectionButTypesafe[T](field: T)(newValue: T): Unit = ???

// intended usage
usingJavaReflectionButTypesafe(Foo.bar)(Eval.now(42))

这意味着能够做一些事情:

  1. Foo.bar 不应被评估
  2. 相反,Foo.bar 应分解为目标对象Foo 和字段"bar"
  3. Foo.bar 的类型应与newValue 的类型相同

额外问题:如果这是可能的,如何使它适用于 Scala 2.11 和 2.12 和 2.13。我只是不知道哪些 Scala 宏技术在哪些版本的 Scala 中可以安全使用,以及如何使用。

【问题讨论】:

  • 运行时反射在 JVM 上本质上是不安全的

标签: scala reflection scala-macros scala-cats


【解决方案1】:

试试

def usingJavaReflectionButTypesafe[T](field: => T)(newValue: T): Unit = macro impl[T]

def impl[T: c.WeakTypeTag](c: blackbox.Context)(field: c.Tree)(newValue: c.Tree): c.Tree = {
  import c.universe._
  field match {
    case q"$expr.$tname" =>
      q"usingJavaReflection($expr, ${tname.toString})($newValue)"
  }
}

【讨论】:

  • 有趣的是,这在 Scala 2.13 中不能很好地用于顶级对象,因为它们现在似乎被编译成静态字段:java.lang.IllegalAccessException: Can not set static final int field ammonite.$sess.cmd4$Foo$Bar$.baz to java.lang.Integer。我们需要一种方法来区分普通对象和具有静态字段的顶级对象。
  • @betehess 尝试将val 更改为var
  • 我想完全避免使用var。另外,如果能同时拒绝deflazy val,那就太好了。
猜你喜欢
  • 1970-01-01
  • 2019-08-04
  • 2012-11-13
  • 2019-02-18
  • 2015-04-18
  • 2022-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多