case class private constructor - need for readResolve implementation
Asked Answered
P

1

1

I was just googling to find out how to create a case class with private constructor. Below is the correct way for doing this as described in

How to override apply in a case class companion

object A {
  def apply(s: String, i: Int): A =
    new A(s.toUpperCase, i) {} //abstract class implementation intentionally empty
}
abstract case class A private[A] (s: String, i: Int) {
  private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
    A.apply(s, i)
  def copy(s: String = s, i: Int = i): A =
    A.apply(s, i)
}

Below is my understanding so far :-

If we declare a case class abstract, then implementation for copy and apply method will not be generated by the compiler.

Below is the question, that I am struggling with :-

Why it is required to provide implementation of readResolve ?

Photoengraving answered 26/8, 2015 at 20:59 Comment(2)
I'm the author of the answer on the other thread upon which you originally asked this question. I am following the JVM/Java best practice based on Joshua Bloch's advice in his book "Effective Java, 2nd Edition". Original thread: https://mcmap.net/q/76157/-how-to-override-apply-in-a-case-class-companionDipody
BTW, for a more thorough context around the specific specialness of the JVM/Java readResolve method and case classes, here's an doc I wrote (along with a CodeReview update) which delves down way deeper into WHY I wanted to always prevent invalid instances of a case class from being instantiated: docs.google.com/document/d/…Dipody
I
4

The readResolve implementation is there to prevent the creation of invalid instances of the case class by editing serialised copies of the class.

Depending on how much you trust the environment in which the code will be used, you may feel you can safely ignore this risk.

It comes about because case classes extend Serializable, and so may end up getting serialised and written out to file (or DB, or wherever). At this point the serialised copy in the file/DB/wherever could be edited to create an invalid value (eg. making s lower case). On deserialising back, the 'live' instance will then be invalid, unless the readResolve method that is used in the deserialisation process is overriden to prevent this.

Intermarry answered 26/8, 2015 at 21:39 Comment(3)
Can i consider implementing readResolve as a best practice irrespective of my case class type (abstract or non-abstract) ? What if my case class is not abstract, then does scala compiler takes care for the implementation of readResolve ?Photoengraving
@rits The standard Java deserialisation process instantiates the instance from the stream being read (basically just reading and setting the individual fields of the instance), then checks to see if a readResolve method is defined. If it is, it is called, and its result used as a replacement for the instance being built. This mechanism provides a chance for the class designer to modify or entirely replace the read-in instance as appropriate (here, ensuring s is always uppercase). Thus, by default, there is usually no need to implement readResolve.Intermarry
Scala defers to the default JVM/Java standard library for Serialization. So, it would be more about whatever the best practices are for the JVM/Java. As Serialization is used in MANY different ways, including but not limited to file read/writing, network socket stream reading/writing, inter-JVM process reading/writing (although that has gone out of style as OSes now provide such fast IO via the network sockets when they are located in the same "machine"). etc.. So, apply the same principles to Scala as you would Java in this area.Dipody

© 2022 - 2024 — McMap. All rights reserved.