【问题标题】:Two ways of (kind of) instantiating a trait in Scala在 Scala 中实例化特征的两种方法
【发布时间】:2015-12-26 13:12:36
【问题描述】:

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

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

scala> val instance1 = new SomeTrait{} // Method 1
instance1: SomeTrait = $anon$1@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 更常见。所以我想知道我是否一直在做错事。

【问题讨论】:

    标签: scala anonymous-class traits


    【解决方案1】:

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

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

    可以在 REPL 中看到这一点:

    定义特征

    scala> trait Example {}
    defined trait Example
    

    新的匿名类

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

    scala> new Example{}
    res0: Example = $anon$1@768debd
    
    scala> new Example{}
    res1: Example = $anon$1@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 文件

    匿名类

        $ cat example.scala 
        object Example1 {
          trait A
          new A {}
        }
    
        $ scalac example.scala 
    
        $ ls *class
    
          Example1$$anon$1.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 
    

    【讨论】:

    • 我明白了。而且你不能像上面的匿名实例那样拥有匿名对象。但是,如果您创建两个对象,每个对象都扩展了Example,那么当使用== 进行比较时,它们两个将不会返回false。我从不认为它们是相同的,但我的印象是,一旦实例化,它们就可以以相同的方式使用。
    • 如果你创建两个对象来扩展特征,它们实际上是不同的。我添加了一些关于这两种方法如何影响数据转换到 JVM 的方式的信息。
    • 顺便说一句,val a = new A{}; a == a 有效,所以如果您正在寻找平等,这更像是一个偏好问题。除此之外,object 可能定义在 *.scala 模块的根目录中,value 不能,并且 object 默认是惰性的。
    【解决方案2】:

    val instance1 = new SomeTrait{}

    class X extends SomeTrait
    val instance1: SomeTrait = new X
    

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

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

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

    【讨论】:

    • 在顶层使用object 是一个明显的区别。但这也意味着我可以在任何可以使用匿名类实例化的地方使用单例。对于抛出异常的构造函数,这让我更加困惑:根据定义,特征不能拥有构造函数,这不是真的吗?您对这些示例中的任何一个都使用的唯一构造函数是空的默认构造函数。
    • 不,特征不能有构造函数参数。所以他们只能有默认的构造函数,但它不必为空。它由正文中的所有非定义和val/var 初始化组成,与类相同。例如。你可以有trait SomeTrait { throw new Exception }
    猜你喜欢
    • 1970-01-01
    • 2011-03-17
    • 2016-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多