For the following code:
object Test {
class MapOps(map: Map[String, Any]) {
def getValue[T](name: String): Option[T] = {
map.get(name).map{_.asInstanceOf[T]}
}
}
implicit def toMapOps(map: Map[String, Any]): MapOps = new MapOps(map)
def main(args: Array[String]): Unit = {
val m: Map[String, Any] = Map("1" -> 1, "2" -> "two")
val a = m.getValue[Int]("2").get.toString
println(s"1: $a")
val b = m.getValue[Int]("2").get
println(s"2: $b")
}
}
val a is computed without exception and the console prints 1: two,
but when computing val b, the java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer is thrown.
Besides, if I execute
val c = m.getValue[Int]("2").get.getClass.toString
println(s"1: $c")
The console prints "int".
Can someone explain why this code behaves like this?
This is certainly odd.
If you look at the following statement in the Scala REPL:
scala> val x = m.getValue[Int]("2")
x: Option[Int] = Some(two)
What I think is happening is this: the asInstanceOf[T] statement is simply flagging to the compiler that the result should be an Int, but no cast is required, because the object is still just referenced via a pointer. (And Int values are boxed inside of an Option/Some) .toString works because every object has a .toString method, which just operates on the value "two" to yield "two". However, when you attempt to assign the result to an Int variable, the compiler attempts to unbox the stored integer, and the result is a cast exception, because the value is a String and not a boxed Int.
Let's verify this step-by-step in the REPL:
$ scala
Welcome to Scala 2.12.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_151).
Type in expressions for evaluation. Or try :help.
scala> class MapOps(map: Map[String, Any]) {
| def getValue[T](name: String): Option[T] = {
| map.get(name).map{_.asInstanceOf[T]}
| }
| }
defined class MapOps
scala> import scala.language.implicitConversions
import scala.language.implicitConversions
scala> implicit def toMapOps(map: Map[String, Any]): MapOps = new MapOps(map)
toMapOps: (map: Map[String,Any])MapOps
scala> val a = m.getValue[Int]("2").get.toString
a: String = two
scala> println(s"1: $a")
1: two
So far so good. Note that no exceptions have been thrown so far, even though we have already used .asInstanceOf[T] and used get on the resulting value. What's significant is that we haven't attempted to do anything with the result of the get call (nominally a boxed Int that is actually the String value "two") except to invoke it's toString method. That works, because String values have toString methods.
Now let's perform the assignment to an Int variable:
scala> val b = m.getValue[Int]("2").get
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
... 29 elided
Now we get the exception! Note also the function in the stack trace that caused it: unboxToInt - it's clearly trying to convert the value stored in the Some to an Int and it fails because it's not a boxed Int but a String.
A big part of the problem is type erasure. Don't forget that a Some(Banana) and a Some(Bicycle) are - at runtime - both just Some instances with a pointer to some object. .asInstanceOf[T] cannot verify the type, because that information has been erased. However, the compiler is able to track what the type should be based upon what you've told it, but it can only detect the error when its assumptions are proven wrong.
Finally, with regard to the getClass call on the result. This is a bit of compiler sleight-of-hand. It's not actually calling a getClass function on the object, but - because it thinks it's dealing with an Int, which is a primitive - it simply substitutes an int class instance.
scala> m.getValue[Int]("2").get.getClass
res0: Class[Int] = int
To verify that the object actually is a String, you can cast it to an Any as follows:
scala> m.getValue[Int]("2").get.asInstanceOf[Any].getClass
res1: Class[_] = class java.lang.String
Further verification about the return value of get follows; note the lack of an exception when we assign the result of this method to a variable of type Any (so no casting is necessary), the fact that the valid Int with key "1" is actually stored under Any as a boxed Int (java.lang.Integer), and that this latter value can be successfully unboxed to a regular Int primitive:
scala> val x: Any = m.getValue[Int]("2").get
x: Any = two
scala> x.getClass
res2: Class[_] = class java.lang.String
scala> val y: Any = m.getValue[Int]("1").get
y: Any = 1
scala> y.getClass
res3: Class[_] = class java.lang.Integer
scala> val z = m.getValue[Int]("1").get
z: Int = 1
scala> z.getClass
res4: Class[Int] = int
Expanding on the last part of eje211's answer.
You told the compiler that a String was an Int, and now you're looking for a sensible explanation of the resulting behavior. That's understandable, but it's not really useful. Once you tell the compiler a lie, all bets are off. You can spend time investigating exactly when and where the compiler inserts checks that happen to discover your deceit, but your time would probably be better spent writing code that doesn't cause you to lie.
As the earlier answer pointed out, you can do that (avoid accidental lying) by using pattern matching. You'll need a ClassTag to make pattern matching work in cases like the above, but the end result will be code that is type-safe and correct.
It's an Int because you request an Int on this line :
val b = m.getValue[Int]("2").get
This calls this method:
def getValue[T](name: String): Option[T] = {
map.get(name).map{_.asInstanceOf[T]}
}
and applies it this way:
def getValue[Int](name: String): Option[Int] = {
map.get(name).map{_.asInstanceOf[Int]}
}
So if you ask for an Int, you get an Int.
In the case of "two", this is what happens in that case:
"two".asInstanceOf[Int]
That's what throws your exception.
A String is not an Int. You can't cast it that way. You can cast it this way:
"2".toInt
But that's different.
In general, using asInstanceOf[] is dangerous. Try pattern matching instead. If you must use, it's up you to make sure that the casts you attempt are valid. You're basically telling the compiler to bypass its own type-checks, particularly when you cast from Any.
It works when you add .toString because then, you change the type back to String, which is what it really was in the first place. The lie of what type the data was is corrected.
Related
I am trying to overload a constructor with mutable list of Int and Long , it mentions that the method is already defined.
I need updateList to be either mutable.MutableList[Int] or mutable.MutableList[Long]
object PercentileDistribution {
def apply(updateList: mutable.MutableList[Int], percentileDistribution: PercentileDistribution): PercentileDistribution = {
updateList.foreach { x =>
percentileDistribution.update(x)
}
percentileDistribution
}
def apply(updateList: mutable.MutableList[Long], percentileDistribution: PercentileDistribution): PercentileDistribution = {
updateList.foreach { x =>
percentileDistribution.update(x)
}
percentileDistribution
}
}
Being new to scala I am facing some issues, any help is appreciated.
The error clearly refers to the upcasting that is happening in your code.
An Int can be represented as a Long hence you have essentially written the same method with one methods parameter being the upcasted version of the parameter of the other apply method.
You can simply use the apply method that has the MutableList[Long] type and remove the one with Int.
Follow this documentation from the official scala docs and you will get a good idea as to how types behave in Scala
The error refers to type erasure. The other answer incorrectly states that it is related to casting (although type erasure can lead to casting-related problems).
A quick example done in a Scala REPL session:
scala> class Foo {
| def bar(list: List[Int]) = "ints"
| def bar(list: List[String]) = "strings"
| }
<console>:12: error: double definition:
def bar(list: List[Int]): String at line 11 and
def bar(list: List[String]): String at line 12
have same type after erasure: (list: List)String
def bar(list: List[String]) = "strings"
^
The message here is saying that both bar methods will have a type signature like def bar(list: List): String in the compiled output; type erasure is taking away the [Int] and [String] parameters, making the two methods indistinguishable. It's an annoyance you just have to put up with if you're running code on the JVM.
My recommended workaround is to distinguish the methods by their names, e.g. instead of apply you might call it forInts and forLongs.
Also note that type erasure causes another problem:
scala> List(1,2,3).isInstanceOf[List[String]]
<console>:11: warning: fruitless type test: a value of type List[Int] cannot also be a List[String] (the underlying of List[String]) (but still might match its erasure)
List(1,2,3).isInstanceOf[List[String]]
^
res5: Boolean = true
and
scala> List(1, 2, 3) match {
| case l: List[String] => l // you'd think this shouldn't match, but it does
| }
<console>:12: warning: fruitless type test: a value of type List[Int] cannot also be a List[String] (the underlying of List[String]) (but still might match its erasure)
case l: List[String] => l // you'd think this shouldn't match, but it does
^
res2: List[Int] with List[String] = List(1, 2, 3)
scala> res2.head
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
... 33 elided
Since in the compiled bytecode, List[Int] and List[String] are both only represented as List, the runtime can't actually distinguish between the two, so an isInstanceOf check may provide the wrong answer, possibly leading to ClassCastExceptions as it tries to treat an Int as a String.
In your case you might get away with it, since it is safe to cast an Int to a Long, but it'd be best to avoid unchecked casting altogether.
I would like to add a charAt method to String that takes a default value.
Can I even do that? How?
Also I love that trick where it looks like you're calling a method but you're really selecting an object. How does that work?
Besides conversion to an expected type, the other two kinds of "implicit views" in Scala are when you select a member that doesn't exist and also when you apply a member that doesn't apply.
So for x.f, if x has no f, implicit search will look for a conversion that supply an f.
For x.f(arg), if that doesn't type check, it will look for a conversion that supplies an f method that does type check.
➜ ~ scala -Yimports:java.lang,scala,scala.Predef,scala.util,scala.util.chaining,scala.util.control.Breaks
Welcome to Scala 2.13.1 (OpenJDK 64-Bit Server VM, Java 11.0.3).
Type in expressions for evaluation. Or try :help.
scala> implicit class R(val s: String) { def charAt(n: Int, c: Char): Char = Try(s.charAt(n)).getOrElse(c) }
defined class R
scala> "abc".charAt(3, 'z')
res0: Char = z
Because it only considers alternative methods in the case of a misapplication, it doesn't work to use an object of the same name, though using a different name works fine, because that is a selection like x.f:
scala> implicit class R(val s: String) { object charAt { def apply(n: Int, c: Char): Char = Try(s.charAt(n)).getOrElse(c) } }
defined class R
scala> "abc".charAt(3, 'z')
^
error: too many arguments (2) for method charAt: (x$1: Int)Char
scala> implicit class R(val s: String) { object charAt2 { def apply(n: Int, c: Char): Char = Try(s.charAt(n)).getOrElse(c) } }
defined class R
scala> "abc".charAt2(3, 'z')
res2: Char = z
Thanks to the original author asking about this; the question was too quickly downvoted as a duplicate, which it wasn't; it inspired a bug report.
The intended output of the following code is the digit, 0.
object Bad extends App {
implicit val massTable: Map[String, Int] =
Map("H" -> 1, "He" -> 4, "O" -> 16)
Implementation.doWork()
}
object Implementation {
def doWork()(implicit massTable: Map[String, Int]) = println("0".toInt)
}
The actual output is an exception:
java.util.NoSuchElementException: key not found: 0
at scala.collection.immutable.Map$Map3.apply(Map.scala:156)
at Implementation$.doWork(Main.scala:20)
at Bad$.delayedEndpoint$Bad$1(Main.scala:16)
...
Investigation shows that Map implements apply(string): Int causing the implicit parameter supplied to doWork, massTable, to also and unintentionally function as an implicit conversion from String => Int.
The result is that, the above toInt call is the synthetic method toInt for the type Int, rather than the toInt method provided by StringOps.
What is the best way to avoid this sort of issue and achieve the desired output?
Assume there is a class X. At some point in my code, I get an o of type Object and I want to explicitly cast it to X. Normally, I would do val x: X = o.asInstanceOf[X]. However, X is not in scope, all I have is X's instance of Class, like so:
I pass classOf[X] to my function as an argument called classOfX.
Within my function, I would like to do val x: X = o.asInstanceOf[classOfX].
Is there a way to do this in Scala?
Only kind of. java.lang.Class has a cast method that lets you cast a Class[A] to an A. You could write a method like this:
def cast[A](o: Any, clazz: Class[A]): A = clazz.cast(o)
scala> cast("abc", classOf[String])
res10: String = abc
This will "work" for most classes, but not for Scala boxed primitives:
scala> cast(1, classOf[Int])
java.lang.ClassCastException: Cannot cast java.lang.Integer to int
And of course, casting only works modulo type erasure, so this will not immediately throw an exception:
scala> val a = List("a").getClass
a: Class[_ <: List[String]] = class scala.collection.immutable.$colon$colon
scala> cast(List(1), a)
res16: List[String] = List(1)
On the upside, it will also work with classes obtained at run time using getClass, like above.
The downside is that you should be avoiding casting at all costs, because of how error prone it will make the code. It throws type safety out the window. Rarely should anyone find themselves in a situation where they have an object where they don't know what it is, but can somehow tell the compiler what it is using classOf. It is likely that using some form of generics could eliminate a casting problem, but it is impossible to tell without more code.
Trying to cast a String to a Double obviously should fail:
scala> Try("abc".asInstanceOf[Double])
res11: scala.util.Try[Double] = Failure(java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double)
However, if I define the above as a function:
scala> def convertAsTypeOf[T](anyValue: Any): Try[T] = Try(anyValue.asInstanceOf[T])
convertAsTypeOf: [T](anyValue: Any)scala.util.Try[T]
Strangely it returns Success when I try to cast a String to a Double:
scala> convertAsTypeOf[Double]("abc")
res10: scala.util.Try[Double] = Success(abc)
If I try to get the value out of Success, I get the following exception:
scala> convertAsTypeOf[Double]("abc").get
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double
at scala.runtime.BoxesRunTime.unboxToDouble(BoxesRunTime.java:119)
Why does this happen? Is there any way to fix this and make asInstanceOf[T] when it's in a generic function?
Because of type erasure, the type T is unknown at runtime, so asInstanceOf can't actually check the cast that it does. In fact, it compiles as a no-op. However, when the .get is finally done a cast is made to Double, and because the type is known here we can get a ClassCastException.
If you change convertAsTypeOf to use ClassTag:
def convertAsTypeOf[T](anyValue: Any)(implicit tag: ClassTag[T]): Try[T] =
Try(tag.runtimeClass.cast(anyValue).asInstanceOf[T])
You will then get errors when you expect:
scala> convertAsTypeOf[Double]("abc")
res1: scala.util.Try[Double] = Failure(java.lang.ClassCastException: Cannot cast java.lang.String to double)
The ClassTag represents the type T at runtime, allowing the value to be checked against it.
The reason this happens is type erasure, which makes asInstanceOf[T] for generic T useless. I don't know why the compiler doesn't issue any warning.
You can force the check to happen by using a ClassTag. Something like this:
import reflect.ClassTag
def convertAsTypeOf[T : ClassTag](anyValue: Any): Try[T] = Try {
anyValue match { case t: T => t }
}
This works because the compiler will use the implicit ClassTag in the case match if it is available (and generate a warning if it is not).