Home > Blockchain >  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

Time:12-05

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?

CodePudding user response:

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

  • Related