Difference between case object and object

55,709

Solution 1

Case classes differ from regular classes in that they get:

  1. pattern matching support
  2. default implementations of equals and hashCode
  3. default implementations of serialization
  4. a prettier default implementation of toString, and
  5. 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.

Share:
55,709

Related videos on Youtube

Wojciech Durczyński
Author by

Wojciech Durczyński

Updated on September 23, 2020

Comments

  • Wojciech Durczyński
    Wojciech Durczyński over 3 years

    Is there any difference between case object and object in scala?

    • axel22
      axel22 about 13 years
      He 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
      Age Mooij about 13 years
      I 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
      Raphael about 13 years
      It is not necessary to use case to have pattern matching, it is just sugar. Implementing unapply yourself does the job.
  • Raphael
    Raphael about 13 years
    Having 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
    Dave Griffith about 13 years
    Sure, but that works just as well for case objects as non-case objects.
  • Wojciech Durczyński
    Wojciech Durczyński about 13 years
    Point 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 years
    I think that case objects can be serialized is the most difference from regular objects especially in the network communication between actors
  • nilskp
    nilskp about 11 years
    This 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 limits object 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
    nilskp about 11 years
    Adding extends Serializable should do the same trick though.
  • User
    User over 8 years
    The question is about case object not class, why is this the correct answer?
  • M.K.
    M.K. over 8 years
    This does not answer the question. This answer deals with the difference between case class and a class. The question is about the difference between case object and object.
  • C4stor
    C4stor over 7 years
    Because the list of differences between case classes and classes IS the list of differences between case object and object.
  • itsbruce
    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
    itsbruce about 7 years
    In 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
    Admin about 6 years
    It's not an answer to the question!
  • jeevan kishore
    jeevan kishore over 5 years
    This is not the right answer it's about case class vs class difference

Related