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 var
s is not idiomatic.
With val
s instead of var
s (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