2005-04-05

Java Surprise 2: Cast Priority

I promised a Java Surprise series. Thanks to my full-time job this promise is not hard to remember: every few days there is a fresh surprise for me ;) . The second surprise in this series is actually one a discovered last summer, so I'm cheating a bit. If you know me in real life, then I've probably already bothered you with this one.

First of all: please take a seat. Are you sitting comfortably? Excellent. Did you know that the syntactical priority of a cast to a primitive type is different from the cast to a reference type? Well, it is. Most likely, you will never encounter this, but it is not hard to find an example that will surprise you.

You are probably familiar with autoboxing in Java 1.5. In short, autoboxing can convert primitive types (such as int) to reference types (such as Integer) for you if necessary. Hence, you can assign an int to an Integer and you can also cast an int to an Integer. Some (correct) statements:

Integer x = 3;
Integer y = (Integer) 3;
int z = (Integer) 3;

I'm going to abuse your familiarity with autoboxing to show how weird it can be that the priority of primitive casts is different from reference casts. The following program is a correct program that includes a (redundant) cast to an int.

public class JavaSurprise2 {
  public static void main(String[] ps) {
    int y = (int) - 2;
    System.out.println(String.valueOf(y));
  }
}

Compile and run:

martin@logistico:~/tmp> javac JavaSurprise2.java
martin@logistico:~/tmp> java JavaSurprise2
-2

Well, that looks great. Now, let's replace the int with an Integer.

public class JavaSurprise2 {
  public static void main(String[] ps) {
    int y = (Integer) - 2;
    System.out.println(String.valueOf(y));
  }
}

Compile ...

martin@logistico:~/tmp> javac JavaSurprise2.java
JavaSurprise2.java:4: cannot find symbol
symbol  : variable Integer
location: class JavaSurprise2
    int y = (Integer) - 2;
             ^
JavaSurprise2.java:4: illegal start of type
    int y = (Integer) - 2;
            ^
2 errors

What the heck? Cannot find symbol? Let's give it a symbol ...

public class JavaSurprise2 {
  public static void main(String[] ps) {
    int Integer = 3;
    int y = (Integer) - 2;
    System.out.println(String.valueOf(y));
  }
}

Compile and run ...

martin@logistico:~/tmp> javac JavaSurprise2.java
martin@logistico:~/tmp> java JavaSurprise2
1

So, what happens? Of course the compiler is right. As I said in the beginning, the priority of a cast to a primitive type is different from a reference type. Because of the priorities defined in the Java Language Specification, the int example is parsed as a cast. However, the Integer version is parsed to an expression name: an expression that can be referred to using a name (aka variable). The Java compiler will never come back to this decision to make it a cast anyway: syntactical choices are always committed.

I can illustrate these different parses using Java-front, a package that provides a Java parser that is generated from a declarative syntax definition for Java in SDF (yes, I'm the developer: marketing intended ;) )

martin@logistico:~/tmp> echo "(Integer) - 2" | parse-java -s Expr
Minus(ExprName(Id("Integer")),Lit(Deci("2")))

martin@logistico:~/tmp> echo "(int) - 2" | parse-java -s Expr
CastPrim(Int,Minus(Lit(Deci("2"))))

Or in terms of XML:

martin@logistico:~/tmp> echo "(Integer) - 2"
    | parse-java -s Expr | aterm2xml --implicit
<Minus>
  <ExprName><Id>Integer</Id></ExprName>
  <Lit><Deci>2</Deci></Lit>
</Minus>

martin@logistico:~/tmp> echo "(int) - 2"
    | parse-java -s Expr | aterm2xml --implicit
<CastPrim>
  <Int/>
  <Minus>
    <Lit><Deci>2</Deci></Lit>
  </Minus>
</CastPrim>

Surprised? You'd better be!

2 comments:

Anonymous said...

This on is hilarious :D

Can you say something about the possible reasoning behind this design?

Unknown said...

I presented this as being unbelievably weird (and in a sense it is), but there is a good reason for it.

I want to explain that in some more detail, so'll come back to that in my next post.