在 Scala 中(某种)实例化特征的两种方法

Two ways of (kind of) instantiating a trait in Scala

我知道在 Scala 中创建匿名 class 来实例化特征的两种方法:

scala> trait SomeTrait {
     |   def aUsefulMethod = ()
     | }
defined trait SomeTrait

scala> val instance1 = new SomeTrait{} // Method 1
instance1: SomeTrait = $anon@7307556f

scala> instance1.aUsefulMethod // Returns a Unit.

scala> object instance2 extends SomeTrait // Method 2
defined module instance2

scala> instance2.aUsefulMethod // Returns a Unit.

我想不出它们不相等的原因。我错了吗?

我问的部分原因是我以前只知道方法 2,但现在我发现方法 1 更常见。所以我想知道我是否一直做错了什么。

第一种方法 new Trait {} 创建一个新的 class 实例。

第二种方法创建一个object,它是一个单例。

可以在 REPL 中看到:

定义特征

scala> trait Example {}
defined trait Example

新匿名class

每次调用 new 都会 return 一个新实例。可以看出每个对象都有一个新地址。

scala> new Example{}
res0: Example = $anon@768debd

scala> new Example{}
res1: Example = $anon@546a03af

对象扩展特征

这里创建了一次单例对象。

scala> object X extends Example
defined object X

scala> X
res2: X.type = X$@1810399e

scala> X
res3: X.type = X$@1810399e

影响与比较

即使这两种方法表面上看起来相似,但它们会导致不同的结果。

scala> new Example{} == new Example{}
<console>:12: warning: comparing values of types Example and Example using `==' will always yield false
   new Example{} == new Example{}
                 ^
 res4: Boolean = false

 scala> X == X
 res5: Boolean = true

更深入

在底层结构上,当 JVM

上的 运行 时,两种方法都会导致生成不同的 *class 文件

匿名class

    $ cat example.scala 
    object Example1 {
      trait A
      new A {}
    }

    $ scalac example.scala 

    $ ls *class

      Example1$$anon.class Example1$A.class
      Example1$.class        Example1.class         

    $ cat example2.scala 
    object Example2 {
      trait A

      object X extends A
    }

    $ scalac example2.scala 

    $ ls *class
    Example2$.class   Example2$X$.class
    Example2$A.class  Example2.class 

val instance1 = new SomeTrait{} 等同于

class X extends SomeTrait
val instance1: SomeTrait = new X

除了编译器创建 class X 并给它起一个像 $anon 这样的名字。如果您随后执行 val instance2 = new SomeTrait{},编译器会注意到它可以重用相同的匿名 class。而object instance2也基本上是

class instance2$ extends SomeTrait {
  override def toString = "instance2"
}
lazy val instance2 = new instance2$

除非您无法创建 instance2$ 的新实例。因此,一个区别是惰性实例化:instance2 仅在访问时才实际创建(例如,当您调用 instance2.aUsefulMethod 时),如果 SomeTrait 构造函数抛出异常或有其他方面,这会有所不同效果。另一个是您可以在顶层使用 object(在 classtraitobject 之外)。