tl;dr
class C
defines a class, just as in Java or C++.
object O
creates a singleton object O
as instance of some anonymous class; it can be used to hold static members that are not associated with instances of some class.
object O extends T
makes the object O
an instance of trait T
; you can then pass O
anywhere, a T
is expected.
- if there is a
class C
, then object C
is the companion object of class C
; note that the companion object is not automatically an instance of C
.
Also see Scala documentation for object and class.
object
as host of static members
Most often, you need an object
to hold methods and values/variables that shall be available without having to first instantiate an instance of some class.
This use is closely related to static
members in Java.
object A {
def twice(i: Int): Int = 2*i
}
You can then call above method using A.twice(2)
.
If twice
were a member of some class A
, then you would need to make an instance first:
class A() {
def twice(i: Int): Int = 2 * i
}
val a = new A()
a.twice(2)
You can see how redundant this is, as twice
does not require any instance-specific data.
object
as a special named instance
You can also use the object
itself as some special instance of a class or trait.
When you do this, your object needs to extend some trait
in order to become an instance of a subclass of it.
Consider the following code:
object A extends B with C {
...
}
This declaration first declares an anonymous (inaccessible) class that extends both B
and C
, and instantiates a single instance of this class named A
.
This means A
can be passed to functions expecting objects of type B
or C
, or B with C
.
Additional Features of object
There also exist some special features of objects in Scala.
I recommend to read the official documentation.
def apply(...)
enables the usual method name-less syntax of A(...)
def unapply(...)
allows to create custom pattern matching extractors
- if accompanying a class of the same name, the object assumes a special role when resolving implicit parameters
Case classes can be seen as plain and immutable data-holding objects that should exclusively depend on their constructor arguments.
This functional concept allows us to
- use a compact initialization syntax (
Node(1, Leaf(2), None))
)
- decompose them using pattern matching
- have equality comparisons implicitly defined
In combination with inheritance, case classes are used to mimic algebraic datatypes.
If an object performs stateful computations on the inside or exhibits other kinds of complex behaviour, it should be an ordinary class.
Best Answer
As so many others have said, the object assigned to a
val
cannot be replaced, and the object assigned to avar
can. However, said object can have its internal state modified. For example:So, even though we can't change the object assigned to
x
, we could change the state of that object. At the root of it, however, there was avar
.Now, immutability is a good thing for many reasons. First, if an object doesn't change internal state, you don't have to worry if some other part of your code is changing it. For example:
This becomes particularly important with multithreaded systems. In a multithreaded system, the following can happen:
If you use
val
exclusively, and only use immutable data structures (that is, avoid arrays, everything inscala.collection.mutable
, etc.), you can rest assured this won't happen. That is, unless there's some code, perhaps even a framework, doing reflection tricks -- reflection can change "immutable" values, unfortunately.That's one reason, but there is another reason for it. When you use
var
, you can be tempted into reusing the samevar
for multiple purposes. This has some problems:Simply put, using
val
is safer and leads to more readable code.We can, then, go the other direction. If
val
is that better, why havevar
at all? Well, some languages did take that route, but there are situations in which mutability improves performance, a lot.For example, take an immutable
Queue
. When you eitherenqueue
ordequeue
things in it, you get a newQueue
object. How then, would you go about processing all items in it?I'll go through that with an example. Let's say you have a queue of digits, and you want to compose a number out of them. For example, if I have a queue with 2, 1, 3, in that order, I want to get back the number 213. Let's first solve it with a
mutable.Queue
:This code is fast and easy to understand. Its main drawback is that the queue that is passed is modified by
toNum
, so you have to make a copy of it beforehand. That's the kind of object management that immutability makes you free from.Now, let's covert it to an
immutable.Queue
:Because I can't reuse some variable to keep track of my
num
, like in the previous example, I need to resort to recursion. In this case, it is a tail-recursion, which has pretty good performance. But that is not always the case: sometimes there is just no good (readable, simple) tail recursion solution.Note, however, that I can rewrite that code to use an
immutable.Queue
and avar
at the same time! For example:This code is still efficient, does not require recursion, and you don't need to worry whether you have to make a copy of your queue or not before calling
toNum
. Naturally, I avoided reusing variables for other purposes, and no code outside this function sees them, so I don't need to worry about their values changing from one line to the next -- except when I explicitly do so.Scala opted to let the programmer do that, if the programmer deemed it to be the best solution. Other languages have chosen to make such code difficult. The price Scala (and any language with widespread mutability) pays is that the compiler doesn't have as much leeway in optimizing the code as it could otherwise. Java's answer to that is optimizing the code based on the run-time profile. We could go on and on about pros and cons to each side.
Personally, I think Scala strikes the right balance, for now. It is not perfect, by far. I think both Clojure and Haskell have very interesting notions not adopted by Scala, but Scala has its own strengths as well. We'll see what comes up on the future.