abstract class Handler {
type Message
def handleAny(msg: Any) {
if (msg.isInstanceOf[Message]) handle(msg.asInstanceOf[Message]) // fix me!
}
def handle(msg: Message)
}
class StringHandler extends Handler {
override type Message = String
def handle(msg: Message) { println(s"handling: $msg") }
}
val h = new StringHandler
h.handleAny("ahoj")
h.handleAny(true)
warning: abstract type Handler.this.Message is unchecked since it is
eliminated by erasure
if(msg.isInstanceOf[Message]) handle(msg.asInstanceOf[Message])
^
one warning found
handling: ahoj
java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.String
at Main$$anon$1$StringHandler.handle(typeProblem.scala:11)
at Main$$anon$1$Handler.handleAny(typeProblem.scala:5)
How to change the snippet (without changing interface or replacing type field with type parameter) to work as expected, which is to handle only messages specified by the type field?
I'm looking for a solution which can be applied to the parent abstract class rather than polluting all children.
I also tried to use a match
and it behaves exactly same. Or is this kind of thing impossible with type fields (I thought they are more powerful than type parameters)?
One usually uses a match and in your case, as you do not want to add any type parameters you will have to specify a ClassTag
(I think).
Something along the lines of:
import scala.reflect.ClassTag
abstract class Handler {
type Message <: Any
implicit val tag: ClassTag[Message]
def handleAny(msg: Any): Unit = {
msg match {
case tag(message) =>
handle(message)
case _ =>
throw new IllegalArgumentException(s"Argmument MUST BE a 'Message' but $msg is not!")
}
}
def handle(msg: Message): Unit
}
object StringHandler extends Handler {
override type Message = String
// lazy is important here!
implicit lazy val tag: ClassTag[Message] = ClassTag(classOf[String])
def handle(msg: Message): Unit = { println(s"handling: $msg") }
}
StringHandler.handleAny("ahoj")
StringHandler.handleAny(true)