【问题标题】:Scala subclass field hiding superclass var only within member functionScala子类字段仅在成员函数内隐藏超类var
【发布时间】:2013-03-06 18:50:06
【问题描述】:

我很难找到这个错误,似乎类 Icon 的字段 pos 隐藏了类 Element pos 字段,仅在 draw 函数内。

case class Vector2(val x: Float, val y: Float) { ... }

abstract class Element(var pos: Vector2) {
    def draw(): Unit
}

class Icon(pos: Vector2, var texture: String) extends Element(pos) {
  override def draw() {
    ...
    GL11.glTranslatef(pos.x, pos.y, 0f)
    ...
  }
}

稍后:

// Create an icon with an initial position
val icon = new Icon(pos = Vector2(40,20), "crosshair")

// Draw all elements
elements.foreach{_.draw()} // => draws icon at (40,20)

// Setting a new position for icon
icon.pos = Vector2(100,200)

// See if it worked
Log.info(icon.pos.toString()) // => this prints Vector2(100,200)

// Draw all elements
elements.foreach{_.draw()} // => still draws icon at (40,20)

我见过this post 并尝试过:

  • 在基类中创建 var 抽象:这会阻止我为 Element 设置新的 pos
  • 重命名构造函数参数(例如 _pos):我不会这样做,因为这会搞砸 API
  • 覆盖派生类中的 var:只是编译器告诉我不能覆盖可变变量

有什么出路?

【问题讨论】:

    标签: class scala abstract-class


    【解决方案1】:

    只需明确取消引用this

    class Icon(pos: Vector2, var texture: String) extends Element(pos) {
      override def draw() {
        ...
        GL11.glTranslatef(this.pos.x, this.pos.y, 0f)
        ...
      }
    }
    

    鉴于阴影只发生在 Icon 内部,其他任何地方(包括在派生类中)您可以继续使用 pos(不需要 this.pos)。

    更新:不用等,这行不通!我称之为编译器错误。似乎this.pos 被视为只是pos,即使它们不应该(恕我直言)是同一件事。 不过有一个简单的解决方法:

    class Icon(pos: Vector2) extends Element(pos) {
      private def self = this
      override def draw() {
        println(self.pos.x, self.pos.y, 0f)
      }
    }
    

    更新 2:这是对评论的回复,不适合其他评论。

    兰德尔·舒尔茨说:

    我不相信这是一个错误。

    好吧,它看起来确实是一个错误,或者至少是我想说明理由的不一致之处。

    首先要注意的是,在我上面的工作中,self eq this。它们确实指向相同的引用,此外还具有相同的静态类型。那么self.posthis.pos 怎么会返回两个不同的东西(不管返回的“正确”东西是什么)?换句话说,selfthis 的别名,并且别名的行为必须相同。

    现在,我认为this.pos 应该表示Element 中的变量而不是Icon 构造函数的参数的原因很简单。此参数前面没有val,因此实际上只是一个参数(不是val)。因此,它可以在 Icon 类中访问,只是因为词法作用域。它不是Icon 的成员,甚至不是私有成员(在后台生成私有字段这一事实不会改变语言的语义)。如果pos 参数不是成员,则this.pos 没有理由返回它。 显然,这个论点归结为参数是否也是类的成员。对我来说显然不是,但如果实际上它应该自动成为成员(我仍在查看规范中是否提到这一点),那么self.pos 返回参数的值确实是合乎逻辑的基类中 var 的当前值(这仍然无法解释 self.posthis.pos 的含义不同)。

    这就是存在自我类型((通常)不受约束的使用其他名称的种类)存在的原因之一。

    嗯,不。自我类型无关紧要。不受约束的自类型只是引入了一个别名,因此别名指向与this 相同的引用(如果不受约束,则具有相同的静态类型)。所以使用别名应该不会改变返回的内容。 实际上,它没有:

    class Icon(pos: Vector2) extends Element(pos) { self =>
      override def draw() {
        println(self.pos.x, self.pos.y, 0f)
      }
    }
    val icon = new Icon(Vector2(1, 2))
    icon.draw() // prints "(1.0,2.0,0.0)" as expected
    icon.pos = Vector2(3, 4)
    icon.draw() // oops, still prints "(1.0,2.0,0.0)"!
    

    如您所见,self 类型没有帮助:self.pos 仍然指向参数而不是变量。 要解决此问题,您可能会尝试将self 显式键入为Element

    class Icon(pos: Vector2) extends Element(pos) { self: Element =>
    

    但它不会改变任何东西。

    【讨论】:

    • 是的,现在可以了。有人应该对此填写错误吗?谢谢!
    • 是的,错误报告似乎是必要的。想做吗?至少,如果它是预期的行为,我们会知道它并可能得到一个理由。
    • 我不相信这是一个错误。这就是存在自我类型(使用this 以外的名称的(通常)不受约束的种类)存在的原因之一。
    • 自我类型无关紧要。由于我无法在评论中做出适当的回复,因此我在答案中进行了更新。
    • 啊,我明白你为什么认为这是一个错误......你认为它只是一个参数,而不是一个字段!好吧,不是真的——如果你在除了超类的参数或正文中的val 初始化之外的任何地方引用它,它将被“提升”为私有字段。这包括将其称为this.pos
    【解决方案2】:

    构造函数参数在类体中是可见的,这就是事情的工作方式。有一个私人 val 影子一个公共 var 是很奇怪的,我同意你,但就是这样。在您展示的特定 sn-p 中,您可以这样做:

    abstract class Element {
      def pos: Vector2
      def pos_=(x: Vector2): Unit
      def draw(): Unit
    }
    

    然后

    class Icon(var pos: Vector2, var texture: String) extends Element {
      override def draw() {
        ...
        GL11.glTranslatef(pos.x, pos.y, 0f)
        ...
      }
    }
    

    但是,如果您只想用一个值初始化 Element,而不是在扩展 Element 的任何内容上声明 var,这将无济于事。我自己的建议是避免在构造函数上使用var,使用initialPos 之类的东西,并在正文中初始化var

    【讨论】:

    • 请注意,在构造函数主体之外(意味着在方法主体内部)引用(非属性,即非val,非var)构造函数参数将强制编译器将该构造函数参数复制到(私有)字段。因此可以巧妙地增加实例大小。
    • @Daniel C. Sobral:我认为 initialPos 解决方案的主要问题是您正在更改 API:现在当使用命名参数实例化 Icon 时,我们必须使用丑陋的名称initialPos 只是 pos。另外,您谈到了私人 val。这里没有私有 val,只是一个可以通过简单的词法作用域访问的参数。至少这就是我一直看到的方式,如果规范另有说明,我会很感激指针。
    • @RégisJean-Gilles 类中的方法引用的任何构造函数参数都被视为私有 val。你可以用 javap 来确认,但我会把引用搜索留给其他人。
    • 我知道它在 (jvm) 类中创建了一个私有字段。但这是一个实现细节,因此与预期的语义无关。除了这个实现细节之外,唯一让它看起来像一个 val 的是你可以在类的整个范围内访问它。但在类的外部范围内定义的任何值也是如此,因此对我来说,您可以访问类中的参数这一事实只是词法范围的影响。
    • @RégisJean-Gilles 好吧,你的看法是错误的,它与你的期望不符就证明了这一点。构造函数参数是一个字段。如果没有声明val,它将是私有的。实现细节是在某些情况下它不能成为类的一部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-22
    • 1970-01-01
    • 2011-10-13
    • 2020-03-19
    • 2013-12-29
    相关资源
    最近更新 更多