I have:
type AllNumericTypes = Double | Float | Long | Int | Short | Byte
And I want to get the value of AllNumericTypes
and cast it to passed type for example Int
def castToSingleNumericType[NumericType <: AllNumericTypes](num: AllNumericTypes): NumericType = num match
case d: Double => d.asInstanceOf[NumericType]
case f: Float => f.asInstanceOf[NumericType]
case l: Long => l.asInstanceOf[NumericType]
case i: Int => i.asInstanceOf[NumericType]
case s: Short => s.asInstanceOf[NumericType]
case b: Byte => b.asInstanceOf[NumericType]
but when I try
scala> val t: AllNumericTypes = 2f
scala> castToSingleNumericType[Int](t)
I get:
java.lang.ClassCastException: class java.lang.Float cannot be cast to class java.lang.Integer (java.lang.Float and java.lang.Integer are in module java.base of loader 'bootstrap')
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:99)
... 36 elided
but this method work as expected:
def castToSingleNumericType(num: AllNumericTypes): Int = num match
case d: Double => println("double"); d.asInstanceOf[Int]
case f: Float => f.asInstanceOf[Int]
case l: Long => l.asInstanceOf[Int]
case i: Int => i.asInstanceOf[Int]
case s: Short => s.asInstanceOf[Int]
case b: Byte => b.asInstanceOf[Int]
scala> castToSingleNumericType(t)
val res1: Int = 2
didn’t find answer in older questions
It even gets better…
println(castToSingleNumericType[Int](t)) // 2.0
println(castToSingleNumericType[Int](t).getClass) // int
No idea what exactly is going on there…
The generic version triggers boxing, and conversion/“casting” doesn’t work with the boxed representations.
2.0f.asInstanceOf[Int] // works
java.lang.Float.valueOf(2.0f).asInstanceOf[java.lang.Integer] // ClassCastException
In Scala 2, this might be fixed via specialization, but it looks like there is no equivalent mechanism in Scala 3 (yet?). However, there’s inlining which covers some of the use cases, which seems to include this one:
inline def castToSingleNumericType[NumericType <: AllNumericTypes](num: AllNumericTypes): NumericType = ???
(Not sure if and where this may break for specific application scenarios, though.)
That being said, even knowing that it’s specified that “casting” numeric values via #asInstanceOf
is translated to a numeric conversion, it still looks somewhat wrong to me.
You need to define a typeclass that can convert one numeric type to another. asInstanceOf really should only be used for casting, not conversion, and in any case you might accidentally box your primitive types (like a generic function), you’re going to get a casting exception.
Unpacking that a little more (because this is a common FAQ, and easy to misunderstand): asInstanceOf
doesn’t do anything. It is essentially a compiler directive that means “this value of type A
is already a value of type B
, and you should just believe me”. It’s how you work around the compiler not having enough information, but it doesn’t change A
to B
.
So the runtime ClassCastException
is what I would normally expect to happen: you’re lying to the compiler, telling it that a Float is actually an Int – I would expect that to crash at runtime. To me, the surprise is that it’s sometimes not doing so – it must be hitting some sort of automatic conversion by accident, probably entirely separately from the asInstanceOf
.
But just generally, asInstanceOf
isn’t the right tool to use for this sort of problem – you’re trying to convert a value from one runtime class to another, and asInstanceOf
doesn’t do that.
2 Likes
For primitive numeric values, it does.
Welcome to Scala 2.13.5 (OpenJDK 64-Bit Server VM, Java 15.0.1).
Type in expressions for evaluation. Or try :help.
scala> 3.14f.asInstanceOf[Int]
val res0: Int = 3
scala> 3.asInstanceOf[Float]
val res1: Float = 3.0
It seems that scala-lang.org isn’t accessible right now (at least not for me), so I can’t check the spec, but a web search shows me this preview snippet, for example:
asInstanceOf[$T$] is treated specially if $T$ is a numeric value type. In this case the cast will be translated to an application of a conversion method …
https://www.scala-lang.org/files/archive/spec/2.11/14-the-scala-standard-library.html
As noted above, this usage looks fishy to me, as well, but it does work.
yes, but when you write a generic function, you’re no longer dealing with primitive numeric values.
I think it’s confusing because scala uses the same type for two concepts. Int the primitive is a simple 4 byte value on the jvm. Int the object is an actual object. In java, these are seperate types, int
and Integer
. In the failed code, you’ll notice it complains that you cannot cast Float to Integer. That means in that case it is trying to cast one object to another. Conversion is not done in that case, and if Float
cannot be viewed as an Integer
(and it cannot be), then the cast fails. Meanwhile, you can cast from float
to int
because automatic conversion is done for these (a holdover from c/c++).
Java generics are all based on class instances that are children of the Object
type. int
and float
are not descendants of Object
, nor are they descendants of any other type. In order to be able to use primitive types like these with generic functions and classes, java boxes them to class types that are children of Object
, Float
and Integer
.
Casting on the jvm only does conversion in the case of a primitive type, and so it cannot be relied on ever in the context of a generic function. In order to get the results you want, you need to define a type class for the “primitive types” in scala that specifies how to convert from one to the other.
1 Like
Allow me to add to the confusion. f.asInstanceOf[NumericType]
doesn’t actually do anything at runtime other than returning (a boxed version of) f
. Because NumericType
is an unbounded type variable it would be like (f: java.lang.Float).asInstanceOf[Object]
. The compiler actually adds a cast after the call to castToSingleNumericType[Int](t)
. Because generics are erased, castToSingleNumericType
actually returns Object
. But the type signature in Scala promises that it returns java.lang.Integer
(because non-specialized generics are always boxed). So the compiler simply adds bytecode that casts the result to java.lang.Integer
.
IMHO f.asInstanceOf[Int]
should not convert between primitives like Java does. In Scala we have .toInt
and friends for that. asInstanceOf
between primitives should do exactly the same as it does for other types—like @jducoeur explained. Otherwise it only creates confusion, as evidenced by this thread.
2 Likes
Assuming you’re using Scala 3.x, you can use the transparent
feature:
transparent inline def x: NumericType = 5
will be typechecked as Int
.
See Inline | Macros in Scala 3 | Scala Documentation
1 Like