Difference between case object and object
Solution 1
Case classes differ from regular classes in that they get:
- pattern matching support
- default implementations of
equals
andhashCode
- default implementations of serialization
- a prettier default implementation of
toString
, and - the small amount of functionality that they get from automatically inheriting from
scala.Product
.
Pattern matching, equals and hashCode don't matter much for singletons (unless you do something really degenerate), so you're pretty much just getting serialization, a nice toString
, and some methods you probably won't ever use.
Solution 2
Here's one difference - case objects extend the Serializable
trait, so they can be serialized. Regular objects cannot by default:
scala> object A
defined module A
scala> case object B
defined module B
scala> import java.io._
import java.io._
scala> val bos = new ByteArrayOutputStream
bos: java.io.ByteArrayOutputStream =
scala> val oos = new ObjectOutputStream(bos)
oos: java.io.ObjectOutputStream = java.io.ObjectOutputStream@e7da60
scala> oos.writeObject(B)
scala> oos.writeObject(A)
java.io.NotSerializableException: A$
Solution 3
scala> object foo
defined object foo
scala> case object foocase
defined object foocase
Serialization difference:
scala> foo.asInstanceOf[Serializable]
java.lang.ClassCastException: foo$ cannot be cast to scala.Serializable
... 43 elided
scala> foocase.asInstanceOf[Serializable]
res1: Serializable = foocase
toString difference:
scala> foo
res2: foo.type = foo$@7bf0bac8
scala> foocase
res3: foocase.type = foocase
Solution 4
A huge necro, but it is the highest result for this question in Google outside official tutorial which, as always, is pretty vague about the details. Here are some bare bones objects:
object StandardObject
object SerializableObject extends Serializable
case object CaseObject
Now, lets use the very useful feature of IntelliJ 'decompile Scala to Java' on compiled .class files:
//decompiled from StandardObject$.class
public final class StandardObject$ {
public static final StandardObject$ MODULE$ = new StandardObject$();
private StandardObject$() {
}
}
//decompiled from StandardObject.class
import scala.reflect.ScalaSignature;
@ScalaSignature(<byte array string elided>)
public final class StandardObject {
}
As you can see, a pretty straightforward singleton pattern, except for reasons outside the scope of this question, two classes are generated: the static StandardObject
(which would contain static forwarder methods should the object define any) and the actual singleton instance StandardObject$
, where all methods defined in the code end up as instance methods. Things get more intresting when you implement Serializable
:
//decompiled from SerializableObject.class
import scala.reflect.ScalaSignature;
@ScalaSignature(<byte array string elided>)
public final class SerializableObject {
}
//decompiled from SerializableObject$.class
import java.io.Serializable;
import scala.runtime.ModuleSerializationProxy;
public final class SerializableObject$ implements Serializable {
public static final SerializableObject$ MODULE$ = new SerializableObject$();
private Object writeReplace() {
return new ModuleSerializationProxy(SerializableObject$.class);
}
private SerializableObject$() {
}
}
The compiler doesn't limit itself to simply making the 'instance' (non-static) class Serializable
, it adds a writeReplace
method. writeReplace
is an alternative to writeObject
/readObject
; what it does, it serializes a different object whenether the Serializable
class having this method is being serialized. On deserializention then, that proxy object's readResolve
method is invoked once it is deserialized. Here, a ModuleSerializableProxy
instance is serialized with a field carrying the Class[SerializableObject]
, so it knows what object needs to be resolved. The readResolve
method of that class simply returns SerializableObject
- as it is a singleton with a parameterless constructor, scala object
is always structurally equal to itself between diffrent VM instances and different runs and, in this way, the property that only a single instance of that class is created per one VM instance is preserved. A thing of note is that there is a security hole here: no readObject
method is added to SerializableObject$
, meaning an attacker can maliciously prepare a binary file which matches standard Java serialization format for SerializableObject$
and a separate instance of the 'singleton' will be created.
Now, lets move to the case object
:
//decompiled from CaseObject.class
import scala.collection.Iterator;
import scala.reflect.ScalaSignature;
@ScalaSignature(<byte array string elided>)
public final class CaseObject {
public static String toString() {
return CaseObject$.MODULE$.toString();
}
public static int hashCode() {
return CaseObject$.MODULE$.hashCode();
}
public static boolean canEqual(final Object x$1) {
return CaseObject$.MODULE$.canEqual(var0);
}
public static Iterator productIterator() {
return CaseObject$.MODULE$.productIterator();
}
public static Object productElement(final int x$1) {
return CaseObject$.MODULE$.productElement(var0);
}
public static int productArity() {
return CaseObject$.MODULE$.productArity();
}
public static String productPrefix() {
return CaseObject$.MODULE$.productPrefix();
}
public static Iterator productElementNames() {
return CaseObject$.MODULE$.productElementNames();
}
public static String productElementName(final int n) {
return CaseObject$.MODULE$.productElementName(var0);
}
}
//decompiled from CaseObject$.class
import java.io.Serializable;
import scala.Product;
import scala.collection.Iterator;
import scala.runtime.ModuleSerializationProxy;
import scala.runtime.Statics;
import scala.runtime.ScalaRunTime.;
public final class CaseObject$ implements Product, Serializable {
public static final CaseObject$ MODULE$ = new CaseObject$();
static {
Product.$init$(MODULE$);
}
public String productElementName(final int n) {
return Product.productElementName$(this, n);
}
public Iterator productElementNames() {
return Product.productElementNames$(this);
}
public String productPrefix() {
return "CaseObject";
}
public int productArity() {
return 0;
}
public Object productElement(final int x$1) {
Object var2 = Statics.ioobe(x$1);
return var2;
}
public Iterator productIterator() {
return .MODULE$.typedProductIterator(this);
}
public boolean canEqual(final Object x$1) {
return x$1 instanceof CaseObject$;
}
public int hashCode() {
return 847823535;
}
public String toString() {
return "CaseObject";
}
private Object writeReplace() {
return new ModuleSerializationProxy(CaseObject$.class);
}
private CaseObject$() {
}
}
A lot more is going on, as CaseObject$
now implements also Product0
, with its iterator and accessor methods. I am unaware of a use case for this feature, it is probably done for consistency with case class
which is always a product of its fields. The main practical difference here is that we get canEqual
, hashCode
and toString
methods for free. canEqual
is relevant only if you decide to compare it with a Product0
instance which is not a singleton object, toString
saves us from implementing a single simple method, which is useful when case objects are used as enumeration constants without any behaviour implemented. Finally, as one might suspect, hashCode
returns a constant, so it is the same for all VM instances. This matters if one serializes some flawed hash map implementation, but both standard java and scala hash maps wisely rehash all contents on deserialization, so it shouldn't matter. Note that equals
is not overriden, so it is still reference equality, and that the security hole is still there. A huge caveat here: if a case object inherit equals
/toString
from some supertype other than Object
, the corresponding methods are not generated, and the inherited definitions are used instead.
TL;DR: the only difference that matters in practice is the toString
returning the unqualified name of the object.
I must make a disclamer here, though: I cannot guarantee that the compiler doesn't treat case objects specially in addition to what is actually in the bytecode. It certainly does so when patterm matching case classes, aside from them implementing unapply
.
Solution 5
case objects implicitly come with implementations of methods toString, equals, and hashCode, but simple objects don't. case objects can be serialized while simple objects cannot, which makes case objects very useful as messages with Akka-Remote. Adding the case keyword before object keyword makes the object serializable.
Related videos on Youtube
Wojciech Durczyński
Updated on September 23, 2020Comments
-
Wojciech Durczyński over 3 years
Is there any difference between case object and object in scala?
-
axel22 about 13 yearsHe has a point - it's not necessary to have a case object in order to be able to pattern match on it. I think this wasn't addressed in the previous question...
-
Age Mooij about 13 yearsI thought there would be a difference in pattern matching behaviour but both a case object and a normal object behave the same way in a pattern match AFAIK. It's pretty hard to find any information at all about case objects so I'm looking forward to someone enlightening us.
-
Raphael about 13 yearsIt is not necessary to use
case
to have pattern matching, it is just sugar. Implementingunapply
yourself does the job.
-
-
Raphael about 13 yearsHaving pattern matching makes a lot of sense if
case
is used in the "proper" use case: providing several implementations of one abstract class or trait. Some might be classes, some might be singleton objects. -
Dave Griffith about 13 yearsSure, but that works just as well for case objects as non-case objects.
-
Wojciech Durczyński about 13 yearsPoint 3 and 4 of this answer is correct difference between case objects and objects. Point 1 and 2 doesn't matter for singleton objects. And singleton objects are always Products with arity 0 so point 5 doesn't matter also.
-
爱国者 over 12 yearsI think that case objects can be serialized is the most difference from regular objects especially in the network communication between actors
-
nilskp about 11 yearsThis post perpetuates the myth that
object
is the same as a singleton. It is not. Rather it is exactly what it says it is, an object, i.e. a declaration and instantiation in one. This limitsobject
to a single instance if defined in package scope, which effectively makes it a singleton, but only if defined IN THAT SCOPE. If defined inside a class, you can have as many instances as the class itself (it's lazily instantiated, so it's not necessarily 1-1). And those inner objects may very well be used as hash keys, making default equals/hashCode very much sensible. -
nilskp about 11 yearsAdding
extends Serializable
should do the same trick though. -
User over 8 yearsThe question is about
case object
not class, why is this the correct answer? -
M.K. over 8 yearsThis does not answer the question. This answer deals with the difference between
case class
and aclass
. The question is about the difference betweencase object
andobject
. -
C4stor over 7 yearsBecause the list of differences between case classes and classes IS the list of differences between case object and object.
-
itsbruce about 7 years@C4stor The answer doesn't say that, though. An object is not a class. Given that case classes do a fair amount of magic behind the scenes, given Scala's various edge cases and complications, there's no reason simply to assume that the only difference between a standard Scala object and a case object is explained by what we know about the difference between standard classes and case classes. This answer doesn't even address the wording of the question.
-
itsbruce about 7 yearsIn fact, some things behave differently for case objects than regular instances of case classes. Pattern matching behaves differently, for one thing. So the answer isn't even accurate.
-
Admin about 6 yearsIt's not an answer to the question!
-
jeevan kishore over 5 yearsThis is not the right answer it's about case class vs class difference