Every time I needed to cast objects in Java I always thought dynamic casting, using Class.cast() was more expensive than simply using the (cast) operator. But apparently in Java, static vs dynamic casting don’t mean much difference.
Let’s consider this code.
package net.superglobals; public interface Action { void perform(); }
And a class that implements my interface.
package net.superglobals; public class SendEmail implements Action { @Override public void perform() { System.out.println("Canadian Pharmacy Order the Cheapest Medication Now!!"); } }
Now I’ll create a situation that requires casting. I’m simply using the cast operator.
package net.superglobals; public class Main { public static void main(String[] args ) { Object o = new SendEmail(); SendEmail sameThing = (SendEmail) o; sameThing.perform(); } }
If we were to disassemble the generated class and look at the bytecode we find this. It basically:
- new creates an object and does some boilerplate storing it and so on
- Read more about dup, astore_n, aload_n. 1 is basically our o variable, and 2 is the sameThing variable.
- invokespecial invokes the <init> method of the SendEmail class
- checkcast checks if the top variable on stack (aka 1, which is o) can be cast to net.superglobals.SendEmail.
- stores it in variable 2 then finally invokes the class method perform().
.method public static main : ([Ljava/lang/String;)V .code stack 2 locals 3 L0: new net/superglobals/SendEmail L3: dup L4: invokespecial Method net/superglobals/SendEmail <init> ()V L7: astore_1 L8: aload_1 L9: checkcast net/superglobals/SendEmail L12: astore_2 L13: aload_2 L14: invokevirtual Method net/superglobals/SendEmail perform ()V L17: return
All good and pretty self explanatory until now. Let’s continue in our Java static vs dynamic casting dilemma.
Let’s create a function that takes an arbitrary class and tries to cast the first argument to that type. I’ll call this ‘dynamic’ casting since it’s not known at compile time. (Well it is written in the source code but in that Class<T> param any class can be passed. We can program it to change on user input and so on… 🙂 ).
package net.superglobals; public class Main { public static void main(String[] args ) { Object o = new SendEmail(); SendEmail sameThing = dynamicCast(o, SendEmail.class); sameThing.perform(); } public static <T> T dynamicCast(Object o, Class<T> clasz) { return clasz.cast(o); } }
This above translates to the following bytecode. It’s pretty much as the one above, except it calls the static method dynamicCast before doing checkcast.
.method public static main : ([Ljava/lang/String;)V .code stack 2 locals 3 L0: new net/superglobals/SendEmail L3: dup L4: invokespecial Method net/superglobals/SendEmail <init> ()V L7: astore_1 L8: aload_1 L9: ldc Class net/superglobals/SendEmail L11: invokestatic Method net/superglobals/Main dynamicCast (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; L14: checkcast net/superglobals/SendEmail L17: astore_2 L18: aload_2 L19: invokevirtual Method net/superglobals/SendEmail perform ()V L22: return
We now move to the bytecode of our dynamicCast class. Basically local variables 1 and 0 are loaded onto the operand stack. Then invokevirtual is called with Method java/lang/Class cast which expects two operands on the stack. It pops them and…?
.method public static dynamicCast : (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; .signature '<T:Ljava/lang/Object;>(Ljava/lang/Object;Ljava/lang/Class<TT;>;)TT;' .code stack 2 locals 2 L0: aload_1 L1: aload_0 L2: invokevirtual Method java/lang/Class cast (Ljava/lang/Object;)Ljava/lang/Object; L5: areturn L6: .linenumbertable L0 11 .end linenumbertable .localvariabletable 0 is o Ljava/lang/Object; from L0 to L6 1 is clasz Ljava/lang/Class; from L0 to L6 .end localvariabletable .localvariabletypetable 1 is clasz Ljava/lang/Class<TT;>; from L0 to L6 .end localvariabletypetable .end code .end method
Well that’s a problem because that’s in the source of Java and it’s not shown in our decompiled file. But if we were to check the source of java.lang.Class we can search for the cast method and see:
@SuppressWarnings("unchecked") public T cast(Object obj) { if (obj != null && !isInstance(obj)) throw new ClassCastException(cannotCastMsg(obj)); return (T) obj; }
This method simply defines a generic type T and tries to cast it using the regular (cast) operator.
Basically, in Java static vs dynamic casting doesn’t mean much difference. Other than the fact Class.cast() has some overhead because it calls more methods, imho you can use both things without any worry.