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
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
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')
... 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]
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
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
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.
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.
val res0: Int = 3
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 …
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,
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
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
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
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.
Allow me to add to the confusion.
f.asInstanceOf[NumericType] doesn’t actually do anything at runtime other than returning (a boxed version of)
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
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.
Assuming you’re using Scala 3.x, you can use the
transparent inline def x: NumericType = 5
will be typechecked as
See Inline | Macros in Scala 3 | Scala Documentation