Unable to clone an object from within an abstract class method in Scala

Unable to clone an object from within an abstract class method in Scala

Problem Description:

I am creating an object of a class in Scala and I would like to clone the object but change one member’s value. For that I’m trying to do something like this:

abstract class A {
  var dbName: String

  def withConfig(db: String): A = {
    var a = this
    a.dbName = db
    a
  }
}

class A1(db: String) extends A {
  override var dbName: String = db
}

class A2(db: String) extends A {
  override var dbName: String = db
}

object Test {
  def main(args: Array[String]): Unit = {
    var obj = new A1("TEST")
    println(obj.dbName)
    var newObj = obj.withConfig("TEST2")
    println(newObj.dbName)
    println(obj.dbName)
  }
}

When I run this program, I get the below output:

TEST
TEST2
TEST2

While I was able to create a new object from the original in this, unfortunately it ended up modifying the value of the member of the original object as well. I believe this is because I’m using the same instance this and then changing the member.

Thus I thought I can clone the object instead, for which I made a change to the withConfig method:

def withConfig(db: String): A = {
    var a = this.clone().asInstanceOf[A]
    a.dbName = db
    a
}

Unfortunately, it is throwing an error saying Clone Not supported:

TEST
Exception in thread "main" java.lang.CloneNotSupportedException: c.i.d.c.A1
    at java.lang.Object.clone(Native Method)
    at c.i.d.c.A.withConfig(Test.scala:7)
    at c.i.d.c.Test$.main(Test.scala:21)
    at c.i.d.c.Test.main(Test.scala)

Is there a way I could achieve the functionality similar to clone(), but without making significant changes to the abstract class?

Solution – 1

You should override clone in classes

abstract class A extends Cloneable {
  var dbName: String

  def withConfig(db: String): A = {
    var a = this.clone().asInstanceOf[A]
    a.dbName = db
    a
  }
}

class A1(db: String) extends A {
  override var dbName: String = db
  override def clone(): AnyRef = new A1(db)
}

class A2(db: String) extends A {
  override var dbName: String = db
  override def clone(): AnyRef = new A2(db)
}

By the way, using vars is not idiomatic.


With vals instead of vars (and not using Java Cloneable at all)

abstract class A {
  def db: String
  def withConfig(db: String): A
}

class A1(val db: String) extends A {
  override def withConfig(db: String): A = new A1(db)
}

class A2(val db: String) extends A {
  override def withConfig(db: String): A = new A2(db)
}

val obj = new A1("TEST")
println(obj.db) // TEST
val newObj = obj.withConfig("TEST2")
println(newObj.db) // TEST2
println(obj.db) // TEST

or (with withConfig returning more precise type than A)

abstract class A {
  def db: String
  type This <: A
  def withConfig(db: String): This
}

class A1(val db: String) extends A {
  override type This = A1
  override def withConfig(db: String): This = new A1(db)
}

class A2(val db: String) extends A {
  override type This = A2
  override def withConfig(db: String): This = new A2(db)
}

I would even implement This and withConfig with a macro annotation

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro annotations")
class implement extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro ImplementMacro.impl
}

object ImplementMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._
    annottees match {
      case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
        val tparams1 = tparams.map {
          case q"$mods type $tpname[..$tparams] = $tpt" => tq"$tpname"
        }
        q"""
          $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
            ..$stats
            override type This = $tpname[..$tparams1]
            override def withConfig(db: String): This = new $tpname(db)
          }

          ..$tail
        """
    }
  }
}
abstract class A {
  def db: String
  type This <: A
  def withConfig(db: String): This
}

@implement
class A1(val db: String) extends A

@implement
class A2(val db: String) extends A

//scalac: {
//  class A1 extends A {
//    <paramaccessor> val db: String = _;
//    def <init>(db: String) = {
//      super.<init>();
//      ()
//    };
//    override type This = A1;
//    override def withConfig(db: String): This = new A1(db)
//  };
//  ()
//}
//scalac: {
//  class A2 extends A {
//    <paramaccessor> val db: String = _;
//    def <init>(db: String) = {
//      super.<init>();
//      ()
//    };
//    override type This = A2;
//    override def withConfig(db: String): This = new A2(db)
//  };
//  ()
//}

Settings: Auto-Generate Companion Object for Case Class in Scala

Rate this post
We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.
Accept
Reject