Cannot be cast to class scala.runtime.BoxedUnit

I think a lot of confusion stems from the fact that type erasure is not exactly intuitive. Decompiling Scala code to Java code won’t help IMO. First, consider a Scala code:

object Scala {
  class Something
  class Something1 extends Something
  class Something2 extends Something

  def cast[A <: Something](param: Any): A =
    param.asInstanceOf[A]

  def main(args: Array[String]): Unit = {
    val x = cast[Something2](arg)
    println(x)
  }

//  val arg = new Object
//  val arg = new Something1
}

You need to uncomment one of the lines that define arg. If you uncomment val arg = new Object then ClassCastException will be thrown inside def cast. If you uncomment val arg = new Something1 then ClassCastException will be thrown outside of def cast, in the line val x = cast[Something2](arg).

Why the cast fails in different places if there is only one cast in source code? Because type erasure causes some but sometimes not all checks to be moved from definition site to use site. What can be checked inside a erased method is checked there, what can’t is moved to use site. In this case param.asInstanceOf[A] inside def cast is erased to param.asInstanceOf[Something] as Something is the only known upper bound of A during compilation of def cast. A more precise cast to Something2 is thus moved to line val x = cast[Something2](arg) as that’s the line where type A of value returned from def cast is statically known.

java -p -c confirms my explanation:

Compiled from "Scala.scala"
public final class temp.Scala$ {
  public static temp.Scala$ MODULE$;

  private final java.lang.Object arg;

  public static {};
    Code:
       0: new           #2                  // class temp/Scala$
       3: invokespecial #22                 // Method "<init>":()V
       6: return

  public <A extends temp.Scala$Something> A cast(java.lang.Object);
    Code:
       0: aload_1
      // first cast to `Something`
       1: checkcast     #7                  // class temp/Scala$Something
       4: areturn

  public void main(java.lang.String[]);
    Code:
       0: aload_0
       1: aload_0
       2: invokevirtual #33                 // Method arg:()Ljava/lang/Object;
       5: invokevirtual #35                 // Method cast:(Ljava/lang/Object;)Ltemp/Scala$Something;
      // second, more precise cast to `Something2`
       8: checkcast     #12                 // class temp/Scala$Something2
      11: astore_2
      12: getstatic     #40                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
      15: aload_2
      16: invokevirtual #44                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
      19: return

  public java.lang.Object arg();
    Code:
       0: aload_0
       1: getfield      #49                 // Field arg:Ljava/lang/Object;
       4: areturn

  private temp.Scala$();
    Code:
       0: aload_0
       1: invokespecial #50                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: putstatic     #52                 // Field MODULE$:Ltemp/Scala$;
       8: aload_0
       9: new           #4                  // class java/lang/Object
      12: dup
      13: invokespecial #50                 // Method java/lang/Object."<init>":()V
      16: putfield      #49                 // Field arg:Ljava/lang/Object;
      19: return
}

In def f[A]: A = new Object().asInstanceOf[A] there’s no upper bound of A thus no casts are done inside of def f and instead they are moved to all use sites. If there are many use sites of method f then in each one compiler can decide to handle the cast differently.

Changing value discarding semantics would suprisingly change semantics of some valid (?) programs. Following code prints null:

object Scala {
  def execute[R](param: Any): R =
    param.asInstanceOf[R]

  def main(args: Array[String]): Unit = {
    val x = execute[Unit](null)
    println(x)
  }
}

What is more important then:

  • anything.asInstanceOf[Unit] == () for any type of anything
  • or
  • null.asInstanceOf[A] == null for any type A

?
These requirements are contradictory as it seems.

2 Likes