Scala 特征继承奇怪的行为

Scala trait inheritance strange behavior

谁能解释为什么这个程序打印的是 0 而不是 4?

trait Rectangular {
  def width: Int
  def height: Int
  val area = width * height
}
case class Square(val size: Int) extends Rectangular {
  val width = size
  val height = size
}

print(Square(2).area)

但当我将 vals 设为惰性时它会起作用

trait Rectangular {
  def width: Int
  def height: Int
  val area = width * height
}
case class Square(val size: Int) extends Rectangular {
  lazy val width = size
  lazy val height = size
}

print(Square(2).area)

这是 Scala 构造 val 成员方式的不幸问题。

trait Rectangular {
  def width: Int
  def height: Int
  val area = width * height
}
case class Square(val size: Int) extends Rectangular {
  val width = size
  val height = size
}

在这里,Scala 在内部将 Square 中的私有成员称为 widthheight。它将它们初始化为零。然后,在 Square 构造函数中,它设置它们。基本上,它的作用与此 Java 代码很接近。

public abstract class Rectangular {
    private int area;
    public Rectangular() {
        area = width() * height();
    }
    public abstract int width();
    public abstract int height();
    public int area() { return area; }
}
public class Square extends Rectangular {
    private int width, height;
    public Square(int size) {
        Rectangular();
        width = size;
        height = size;
    }
    public int width() { return width; }
    public int height() { return width; }
}

请注意,Rectangular 构造函数在 Square 构造函数之前被调用,因此 area 在它们之前看到默认的零 widthheight 值'重新设置。正如您已经发现的那样,使用 lazy val 可以解决问题。

trait Rectangular {
  def width: Int
  def height: Int
  val area = width * height
}
case class Square(val size: Int) extends Rectangular {
  lazy val width = size
  lazy val height = size
}

或者,您可以使用早期初始化语法来强制以正确的顺序写入值。

trait Rectangular {
  def width: Int
  def height: Int
  val area = width * height
}
case class Square(val size: Int) extends {
  val width = size
  val height = size
} with Rectangular

lazy val 解决方案通常更可取,因为它会减少以后的意外情况。

有关详细信息,请参阅有关此特定主题的 Scala FAQ

问题是你特征中的 val 在你 class 中的 val 之前被初始化。

所以当你调用构造函数时,首先 area 被初始化。它将其值设置为 width * heightwidthheight 都尚未初始化,因此它们的值为零。这意味着 area = 0 * 0 = 0.

在那之后,widthheight 被设置为 size 的值,但是此时对于区域来说已经太晚了。

它适用于 lazy val,因为惰性值会在访问时检查它们是否已被初始化,如果没有则自行初始化。常规 val 没有此类检查。