Reputation: 1122
I have two case classes
abstract class MainClass
case class Acc(x:String, y:String) extends MainClass
case class Bcc(x:String, y:String) extends MainClass
I need to write a generic fucntion that will take either one of them and return a list of the same case class, something similar to :
def f1[T <: MainClass ]():List[T]={
val o1 = new Acc("sss","ddd").asInstanceOf[T]
List(o1)
}
the only problem is that I am hard-coding the type class "Acc" . My question is How do I make the f1 method instantiate the generic class T instead of Acc or Bcc
thanks in advance.
Upvotes: 2
Views: 382
Reputation: 139038
While the ClassTag
solution may work for you, relying on runtime reflection is a code smell, and it can actually get you into real trouble. Suppose you end up with a new subclass like this (defined either by you or by someone else using your code):
case class Ccc(x: String, y: String, i: Int) extends MainClass
Now when someone writes f1[Ccc]
—and there's no indication in the method signature that they shouldn't—their program will crash at runtime with a NoSuchMethodException
.
There's a much safer way to do this kind of thing, and while it requires a bit of boilerplate-writing for you, it's transparent to people using your code down the line (including yourself). First you define a type class that describes how to create an instance of some specific subclass of MainClass
:
trait MainClassFromStrings[T <: MainClass] {
def apply(x: String, y: String): T
}
Then you write some type class instances:
implicit def AccFromStrings: MainClassFromStrings[Acc] =
new MainClassFromStrings[Acc] {
def apply(x: String, y: String) = Acc(x, y)
}
implicit def BccFromStrings: MainClassFromStrings[Bcc] =
new MainClassFromStrings[Bcc] {
def apply(x: String, y: String) = Bcc(x, y)
}
And now you can write your f1
pretty cleanly:
def f1[T <: MainClass](implicit fs: MainClassFromStrings[T]) =
List(fs("sss", "ddd"))
And then:
scala> f1[Acc]
res0: List[Acc] = List(Acc(sss,ddd))
scala> f1[Bcc]
res1: List[Bcc] = List(Bcc(sss,ddd))
But you get a nice compile-time error if you try f1[Ccc]
.
Type classes are pretty widely used in Scala (including in the standard library)—searching Stack Overflow for "type classes" in Scala
will turn up lots of examples and discussion.
Upvotes: 11
Reputation: 2468
You can use ClassTag to keep meta information about Class during runtime.
import scala.reflect._
abstract class MainClass
case class Acc(x:String, y:String) extends MainClass
case class Bcc(x:String, y:String) extends MainClass
object Boot extends App {
def f1[T <: MainClass : ClassTag ]():List[T]={
val o1 = classTag[T].runtimeClass.getConstructor(classOf[String], classOf[String]).newInstance("sss","ddd").asInstanceOf[T]
List(o1)
}
val list = f1[Acc]
}
Upvotes: 1