【问题标题】:Please help me understand overriding of val in scala请帮助我理解 val 在 scala 中的覆盖
【发布时间】:2013-11-13 13:43:59
【问题描述】:

我之前问了一个问题:here,虽然我接受了答案,但我离真正理解还很远,所以我挖得更深了,我正在写一个进一步的问题。

在 scala 中覆盖 val 的行为让我感到惊讶。例如,给定以下代码:

class A {
  val name = "AAAAA"
}

class B extends A {
  override val name = "BBBBB"
}

如果我说:

object Atest extends App {
  val b = new B
  println(b.name)
  val a = b.asInstanceOf[A]
  println(a.name)
}

我期待

BBBBB
AAAAA

但我明白了

BBBBB
BBBBB

我只是想查看我认为 A 应该存储在某处的 AAAAA 值。所以我尝试:

class A {
  val name = "AAAAA"

  def showSuper {
    println(name)
  }
}

和:

  val b = new B
  val a = b.asInstanceOf[A]
  b.showSuper
  a.showSuper

但我仍然得到:

BBBBB
BBBBB

所以我试着看看 scala 从我的课程中实际生成了什么:

scalac -Xprint:all A.scala

给我

  class A extends Object {
    private[this] val name: String = _;
    <stable> <accessor> def name(): String = A.this.name;
    def <init>(): p3.A = {
      A.super.<init>();
      A.this.name = "AAAAA";
      ()
    }
  };
  class B extends p3.A {
    private[this] val name: String = _;
    override <stable> <accessor> def name(): String = B.this.name;
    def <init>(): p3.B = {
      B.super.<init>();
      B.this.name = "BBBBB";
      ()
    }
  }

对 B.super 的调用发生在 B.this.name 甚至被设置之前,并且 A 清楚地将其名称设置为 AAAAA。

发生了什么事?为什么,当我覆盖这样的 val 时,我看不到 A 的值(或者它是否被设置为 B 的值?)发生这种情况的机制是什么?我如何才能看到这种机制 - 是否有一段 scala 源代码告诉我为什么会发生这种情况?

非常感谢

编辑: 补充一点,如果我使用 javap 查看字节码,它清楚地表明 A 和 B 都有自己的 name 变量副本:

$ javap -private A
Compiled from "A.scala"
public class p3.A extends java.lang.Object{
    private final java.lang.String name;
    public java.lang.String name();
    public p3.A();
}

$ javap -private B
Compiled from "A.scala"
public class p3.B extends p3.A{
    private final java.lang.String name;
    public java.lang.String name();
    public p3.B();
}

因此,A 和 B 不必共享同一个变量 - 他们每个人都有可能使用自己的副本。

【问题讨论】:

  • This answer 可能会提供信息
  • 谢谢,但我认为这与选角无关。在上面的示例中,即使是 b.showSuper,我也希望显示 AAAAA,因为它显式调用其超类的方法,其中引用了 A 的字段(或者至少在我看来)

标签: scala


【解决方案1】:

这不是 Scala 特有的。这是面向对象的问题。第二个类覆盖该方法并将其隐藏。 Overriding and Hiding Methods

【讨论】:

  • 嗯.. 让我感到困惑的是,这是关于变量而不是方法。在java中,如果你将一个变量强制转换为它的超类,你可以访问超类的字段——例如 ((A)b).showField1();和 println(((A)b).field1) 是不同的。第一个仍然调用子类的方法,但第二个访问 SUPERCLASS 的字段。所以也许我的问题的答案是,scala 将所有字段访问视为方法访问,其覆盖规则适用于 java 中的方法而不是字段?
  • 没有。这是方法。见java代码:public java.lang.String name();变量名是私有的,方法名()是公共的。
  • 啊...如果我理解正确,您是说即使我已转换为超类,因为超类调用了“名称”方法,由于多态性,它仍在继续在子类上调用“名称”方法?这真的很有意义!非常感谢,这让我发疯了。我还发现这篇有用的帖子stackoverflow.com/questions/14912847/… 有一个有用的规则“当你调用一个方法时,它将是实例类型(多态性)当你调用变量时,它将是引用的类型”
  • 统一访问原则指出,无论是通过返回预设数据(字段)还是动态计算(方法)来为您提供答案都无关紧要。这两个 must 的行为完全相同。以不同的方式对待它们根本没有意义。始终返回相同值的方法与字段同构。它们是无法区分的。 Java 以不同方式对待它们的事实是一个主要的设计缺陷。
【解决方案2】:

只是一些额外的注释。事实证明,如果您反编译 scala 类,您会发现 scala 将对 vals 的引用更改为对获取 vals 的方法的引用。例如我上面的A类有:

def showSuper {
  println(name)
}

反编译后的字节码显示

public void showSuper()
{
  Predef..MODULE$.println(name());
}

(感谢jd-gui)

因此这清楚地表明,scala 对 val 的引用在字节码中等效于 java 的多态方法调用,而不是依赖于类型的 java 变量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-07
    • 2018-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-11
    • 1970-01-01
    相关资源
    最近更新 更多