【问题标题】:In Scala Reflection, why do constructor params hide getters?在 Scala 反射中,为什么构造函数参数隐藏 getter?
【发布时间】:2014-01-08 16:06:28
【问题描述】:

Scala 类中的vars 自动获取 getter 和 setter,您可以通过 members 的 scala 反射看到

import scala.reflect.runtime.{universe => ru}
class A(var x: Int)

scala> ru.typeOf[A].members.filter{_.name.toString.contains("x")}
res22: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(variable x, method x_=, method x)

但是,如果您创建一个子类,它在构造函数中重用 var 名称,getter 就消失了:

class B(x:Int, var y: Int) extends A(x)
scala> ru.typeOf[B].members.filter{_.name.toString.contains("x")}
res23: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(value x, method x_=)
scala> res23.head.asTerm.isVal
res25: Boolean = true

这似乎有点误导......毕竟,B 仍然有 x 的吸气剂(而不是 val)

scala> val b = new B(5,6)
b: B = B@270288ed

scala> b.x
res26: Int = 5

scala> b.x = 7
b.x: Int = 7

scala> b.x
res27: Int = 7

如果我试图假装从members 得到的value x 是一个getter,我会得到一个错误:

scala> val xGetter = res23.head.asTerm
xGetter: reflect.runtime.universe.TermSymbol = value x

scala> val objMirror = ru.runtimeMirror(getClass.getClassLoader).reflect(b)
objMirror: reflect.runtime.universe.InstanceMirror = instance mirror for B@270288ed

scala> val getterMirror = objMirror.reflectField(xGetter)
scala.ScalaReflectionException: Scala field x isn't represented as a Java field, neither it has a Java accessor method
note that private parameters of class constructors don't get mapped onto fields and/or accessors,
unless they are used outside of their declaring constructors.

什么是正确的解决方法?将子类名称及其构造函数参数与父参数中的名称相同是完全错误的吗?或者我不需要调用 members,而是需要对所有超类进行处理以获取所有 getter 和 setter?

请注意,只要子类不创建具有相同名称的构造函数,members 就会为我提供继承的 getter:

class Y(var x: Int)
class Z(q:Int, z: Int) extends Y(q)
scala> ru.typeOf[Z].members.filter{_.name.toString.contains("x")}
res28: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(method x_=, method x)

编辑 如果不清楚,我真的在问: 1)这是scala反射中的错误吗? 2)如果没有,我应该: (a) 从来没有类使用构造函数字段的名称与基类中的字段名称相同? (如果是这样,我可能把我所有的类都定义错了......) 要么 (b) 要获取所有 getter 和 setter,我是否应该浏览所有父类的列表并使用 declarations,而不是依靠 members 做正确的事情,因为在这种情况下它不起作用?

编辑 2 响应@som-snytt 的回答,x 的可见方法实际上在A 中的x 上,而不是B 的构造函数中的参数。例如:

class A(var x: Int){def showMeX {println(x)}}
class B(x:Int, var y: Int) extends A(x)
scala> val b = new B(5,10)
scala> b.showMeX
5
scala> b.x = 17
b.x: Int = 17
scala> b.showMeX
17

所以从普通用户代码的角度来看,我真的不认为x 的getter 或setter 已被隐藏。它仅被反射代码遮蔽......对我来说,有两种不同版本的遮蔽是没有意义的。

【问题讨论】:

  • 这似乎是一个错误,请报告它here,可以肯定地告诉它。
  • 感谢@sschaef,在此报告issues.scala-lang.org/browse/SI-8096
  • @som-snytt ha,是的,我已经潜伏了很长时间,只是变得活跃......

标签: scala reflection


【解决方案1】:

2) 如果不是,我应该:(a) 永远不要让类使用构造函数字段的名称与基类中的字段名称相同?

因为他们不让我解决这个问题,所以我就是这么做的。我尝试为所有构造函数参数赋予与所有继承名称不同的新名称。这是编译器中的一个典型示例。

class PackageClassSymbol protected[Symbols] (owner0: Symbol, pos0: Position, name0: TypeName)

是的,这很荒谬。

哦,男孩,不要一直拉扯那根线......

不难看出,解决任何问题的可能性为零。这是我为什么退出的一个完美例子。

顺便说一句,如果您使用 -Xlint,它会警告您这一点。这在 SI-4762 中有所提及。

% cat a.scala
class A(var x: Int)
class B(x:Int, var y: Int) extends A(x) {
  def z = x
}

% scalac -Xlint a.scala
a.scala:3: warning: private[this] value x in class B shadows mutable x inherited from class A.
Changes to x will not be visible within class B - you may want to give them distinct names.
  def z = x
          ^
one warning found

【讨论】:

  • 接受这一点很痛苦,但证据确凿。是时候重写我所有的代码了...
  • @ImranRashid 在哪个语言中? :)
【解决方案2】:

我想只要我的问题的答案不是 (2a),那么如果其他人遇到这个问题,这里有一个解决方法。根据(1)的答案,将来可能不需要它。

(这里有一些额外的东西,但也可能有用)

import scala.reflect.runtime.{universe => ru}
object ReflectionUtils {
  def extractGetterSetterPairs(typ: ru.Type): Seq[GetterSetterPair] = {
    typ.baseClasses.foldLeft(Seq[GetterSetterPair]()){case (acc, clsSymb) =>
      extractGetterSetterPairs(clsSymb.asClass.toType, acc)
    }
  }

  private def extractGetterSetterPairs(typ: ru.Type, acc: Seq[GetterSetterPair]): Seq[GetterSetterPair] = {
    val terms = typ.declarations.collect{case x if x.isTerm => x.asTerm}
    acc ++ terms.filter{x => x.isGetter}.map{x => x -> x.setter}.
      filter{case(g,s) => s.isTerm}.map{case(g,s) =>
        GetterSetterPair(g,s.asTerm)
      }
  }

  def termName(t: ru.TermSymbol): String = {
    t.name.toString.trim
  }

}

case class GetterSetterPair(getter: ru.TermSymbol, setter: ru.TermSymbol) {
  val name = ReflectionUtils.termName(getter)

  val fieldType = {
    //this is way more complicated than it should be. But
    // 1) getters for some reason are not instances of ru.MethodType
    //        java.lang.ClassCastException: scala.reflect.internal.Types$NullaryMethodType cannot be cast to scala.reflect.api.Types$MethodTypeApi
    // 2) its a headache to get the types out of setters
    val m = setter.typeSignature.
      asInstanceOf[ru.MethodType]
    m.params.head.typeSignature
  }
}

【讨论】:

    【解决方案3】:

    好吧,在:

    class B(x:Int, var y: Int) extends A(x)
    

    B 中的 x 是私有的(不是案例类,没有 val 或 var 说明符),A 中的 x 是公共的(您指定了 var)。我对这个反射 API 不太熟悉,但是它显示私有成员吗?

    如果你改为:

    scala> class B(val x:Int, var y: Int) extends A(10)
    <console>:9: error: overriding variable x in class A of type Int;
     value x needs `override' modifier
           class B(val x:Int, var y: Int) extends A(10)
                       ^
    

    唯一的公共 x 是 A 中的那个,可以在这里显示:

    scala> class B(x:Int, var y: Int) extends A(10)
    defined class B
    
    scala> new B(2,3).x
    res4: Int = 10
    

    如果要覆盖父成员,请使用override,并将父成员更改为可以覆盖的内容。

    【讨论】:

    • 对,我想你误解了这个问题。我知道正常的非反射 scala 行为。这很棒。问题是,通过 scala 反射,我无法访问 A 中定义的 public x。(至少,我无法通过“members”方法访问,我可以通过所有的父母得到一切,但似乎不必要的复杂。)
    猜你喜欢
    • 1970-01-01
    • 2019-11-23
    • 1970-01-01
    • 1970-01-01
    • 2010-10-06
    • 1970-01-01
    • 1970-01-01
    • 2015-06-06
    • 2023-03-06
    相关资源
    最近更新 更多