Scala - new vs object extends

2020-02-28 15:47发布

问题:

What is the difference between defining an object using the new operator vs defining a standalone object by extending the class?

More specifically, given the type class GenericType { ... }, what is the difference between val a = new GenericType and object a extends GenericType?

回答1:

As a practical matter, object declarations are initialized with the same mechanism as new in the bytecode. However, there are quite a few differences:

  • object as singletons -- each belongs to a class of which only one instance exists;
  • object is lazily initialized -- they'll only be created/initialized when first referred to;
  • an object and a class (or trait) of the same name are companions;
  • methods defined on object generate static forwarders on the companion class;
  • members of the object can access private members of the companion class;
  • when searching for implicits, companion objects of relevant* classes or traits are looked into.

These are just some of the differences that I can think of right of the bat. There are probably others.

* What are the "relevant" classes or traits is a longer story -- look up questions on Stack Overflow that explain it if you are interested. Look at the wiki for the scala tag if you have trouble finding them.



回答2:

object definition (whether it extends something or not) means singleton object creation.

scala> class GenericType
defined class GenericType

scala> val a = new GenericType
a: GenericType = GenericType@2d581156

scala> val a = new GenericType
a: GenericType = GenericType@71e7c512

scala> object genericObject extends GenericType
defined module genericObject

scala> val a = genericObject
a: genericObject.type = genericObject$@5549fe36

scala> val a = genericObject
a: genericObject.type = genericObject$@5549fe36


回答3:

While object declarations have a different semantic than a new expression, a local object declaration is for all intents and purpose the same thing as a lazy val of the same name. Consider:

class Foo( name: String ) {
  println(name+".new")
  def doSomething( arg: Int ) {
    println(name+".doSomething("+arg+")")
  }
}

def bar( x: => Foo ) {
  x.doSomething(1)
  x.doSomething(2)
}

def test1() {
  lazy val a = new Foo("a")
  bar( a )
}

def test2() {
  object b extends Foo("b")
  bar( b )
}

test1 defines a as a lazy val initialized with a new instance of Foo, while test2 defines b as an object extending Foo. In essence, both lazily create a new instance of Foo and give it a name (a/b).

You can try it in the REPL and verify that they both behave the same:

scala> test1()
a.new
a.doSomething(1)
a.doSomething(2)

scala> test2()
b.new
b.doSomething(1)
b.doSomething(2)

So despite the semantic differences between object and a lazy val (in particular the special treatment of object's by the language, as outlined by Daniel C. Sobral), a lazy val can always be substituted with a corresponding object (not that it's a very good practice), and the same goes for a lazy val/object that is a member of a class/trait. The main practical difference I can think of will be that the object has a more specific static type: b is of type b.type (which extends Foo) while a has exactly the type Foo.



标签: scala