How Does Java Object Casting Work Behind the Scene

How casting works in Java?

Does it converts Object reference type to Processor type which will be holding to Object class Instance location?

No, because Object does not implement the interface Processor. Also because of this, the cast will fail, throwing a ClassCastException.

Casting a type (Object) to another unrelated type (Processor) will generally fail.

What is happening actually?

Casting is you telling the compiler what type a variable should actually be, because you know better than than the compiler.

Here

Processor prcsr = (Processor) ob.getObject();

You are telling the compiler

Hey compiler, you only know that obj.getObject returns something of type Object, but I know better than you. I know that obj.getObject is going to return a type that implements Processor.

The compiler hears this and goes "okay", and lets you assign it to the variable prcsr, which is of type Processor.

However, at runtime, it turns out that obj.getObject returned an instance of Object, which does not implement Processor, so what you were telling the compiler was wrong, and an exception gets thrown.

Now let's say obj.getObject actually returns an instance of FooProcessor, which does implement Processor (declaration not shown here):

Object getObject(){

return new FooProcessor();
}

Now at runtime, the cast succeeds because what you were telling the compiler was correct.

So basically, casting is mostly a compile time thing. Very little actually happens at runtime. At runtime it just checks whether what you were saying was right or not. If is wrong, throw an exception.

How does the Java cast operator work?

Is the JLS good enough?

Casting conversion is applied to the operand of a cast operator (§15.16): the type of the operand expression must be converted to the type explicitly named by the cast operator. Casting contexts allow the use of:

  • an identity conversion (§5.1.1)
  • a widening primitive conversion (§5.1.2)
  • a narrowing primitive conversion (§5.1.3)
  • a widening reference conversion (§5.1.5) optionally followed by an unchecked conversion (§5.1.9)
  • a narrowing reference conversion (§5.1.6) optionally followed by an unchecked conversion
  • a boxing conversion (§5.1.7)
  • an unboxing conversion (§5.1.8).

Actually, maybe this part is more relevant:

The detailed rules for compile-time legality of a casting conversion of a value of compile-time reference type S to a compile-time reference type T are as follows:


  • If
    S is a class type:
    • If T is
      a class type, then either |S|
      <: |T|, or |T| <:
      |S|; otherwise a compile-time
      error occurs. Furthermore, if there
      exists a supertype X of
      T, and a supertype Y of
      S, such that both X and
      Y are provably distinct
      parameterized types (§4.5),
      and that the erasures of X and
      Y are the same, a compile-time
      error occurs.
    • If T is an interface type:

      • If
        S is not a final
        class (§8.1.1),
        then, if there exists a supertype
        X of T, and a supertype
        Y of S, such that both
        X and Y are provably
        distinct parameterized types, and that
        the erasures of X and Y
        are the same, a compile-time error
        occurs. Otherwise, the cast is always
        legal at compile time (because even if
        S does not implement T,
        a subclass of S might).
      • If S is a
        final class (§8.1.1),
        then S must implement T,
        or a compile-time error occurs.

    • If T
      is a type variable, then this
      algorithm is applied recursively,
      using the upper bound of T in
      place of T.
    • If T is
      an array type, then S must be
      the class Object, or a
      compile-time error occurs.
  • If S is
    an interface type:
    • If T is
      an array type, then T must
      implement S, or a compile-time
      error occurs.
    • If T is a type that is not
      final (§8.1.1),
      then if there exists a supertype
      X of T, and a supertype
      Y of S, such that both
      X and Y are provably
      distinct parameterized types, and that
      the erasures of X and Y
      are the same, a compile-time error
      occurs. Otherwise, the cast is always
      legal at compile time (because even if
      T does not implement S,
      a subclass of T might).
    • If T is
      a type that is final,
      then:
      • If S is not a parameterized
        type or a raw type, then T must
        implement S, and the cast is
        statically known to be correct, or a
        compile-time error occurs.
      • Otherwise,
        S is either a parameterized
        type that is an invocation of some
        generic type declaration G, or
        a raw type corresponding to a generic
        type declaration G. Then there
        must exist a supertype X of
        T, such that X is an
        invocation of G, or a
        compile-time error occurs.
        Furthermore, if S and X
        are provably distinct parameterized
        types then a compile-time error
        occurs.
  • If S is
    a type variable, then this algorithm
    is applied recursively, using the
    upper bound of S in place of
    S.
  • If
    S is an array type SC[],
    that is, an array of components of
    type SC:
    • If T is a
      class type, then if T is not
      Object, then a
      compile-time error occurs (because
      Object is the only class
      type to which arrays can be assigned).
    • If T
      is an interface type, then a
      compile-time error occurs unless
      T is the type
      java.io.Serializable or
      the type Cloneable, the
      only interfaces implemented by arrays.
    • If T
      is a type variable, then:
      • If the upper
        bound of T is
        Object or the type
        java.io.Serializable or
        the type Cloneable, or a
        type variable that S could
        legally be cast to by recursively
        applying these rules, then the cast is
        legal (though unchecked).
      • If the upper
        bound of T is an array type
        TC[], then a compile-time error
        occurs unless the type SC[] can
        be cast to TC[] by a recursive
        application of these compile-time
        rules for casting.
      • Otherwise, a
        compile-time error occurs.
    • If T is
      an array type TC[], that is, an
      array of components of type TC,
      then a compile-time error occurs
      unless one of the following is true:

    • TC and SC are the
      same primitive type.
    • TC and
      SC are reference types and type
      SC can be cast to TC by
      a recursive application of these
      compile-time rules for casting.

Perfectly clear now, isn't it? :D

In other words, this is the best I can do without knowing more details about your problem.

Java type casting. Logic behind (String), .toString() and + int

As was mentioned before, (String) myInt is a typecast. In Java, we can either cast within primitives or upwards in the object-hierarchy. Since int is a primitive and String is an object, we have a problem. Even Autoboxing cannot resolve this dilemma since Integer and String do not stand in an inheritance-relationship. Therefore, it is perfectly plausible for (String) myInt to result in a compilation error.

The semantics of Integer.toString(myInt) and "" + myInt are identical. The specifics, however, are different.

When executing Integer.toString(myInt), a new String is constructed, containing a String-representation of myInt.

When executing "" + myInt, Java first constructs a global String-constant, having the value "" (this is done by the JVM, we do not see this 1). The lexer demands a String on the right side of + since it found a String on the left side of +. For primitives, the JVM "knows" how to convert them into Strings. For objects, toString() is called. Since Object has this method and each class is (at least implicitly) derived from Object, each object is guaranteed to have a toString() method. This is the second String constructed. Since Strings are immutable, the JVM might create a third String, representing the concatenation of the first two Strings2.


Epilogue

And then, at execution time, the JIT-compiler strikes and most of this might be irrelevant since the JIT-optimized versions of both variants may look equal. Or not. Or maybe only sometimes. JIT does funny stuff. So in the end it is more a question of personal style than performance :)


1 This is actually a lie. When writing

String s1 = "";
String s2 = "";
System.out.println(s1 == s2);

one will observe that the result is true, whereas false is expected. This is due to the fact that the JVM creates a pool for all String constants to save some memory.

2 It is well possible that the JVM "recognizes" that something + "" == "" + something == something and therefore does not create this third String. I did neither test nor research this.

Java casting implementation

Could you please explain me how the casting works ( in memory )?

It works at byte code level not really in memory

How the variable type is changed on upcasting and downcasting?

If it is a primitive with an special bytecode instruction, for instance from long to integer as in:

long l = ...
int i = ( int ) l;

The bytecode is: l2i if is a reference with the instruction checkcast

How the JVM knows that from this time it's safe to send this method to this object?

It doesn't, it tries to do it at runtime and if it fails throws an exception.

It is legal to write:

String s = ( String ) new Date();

Any type casting done by javac?

Actually, there are three possibilities in this case:

  1. The javac compiler could perform the optimization.
  2. The JIT compiler could perform the optimization.
  3. The native code by the JIT compiler could include code to do a runtime type check.

I expect that it is option 1. or 2. but this could be platform specific.


In fact, on my system the bytecode is not optimized. If any optimization is to occur it will be up the the JIT compiler to do it. (This fits with what I've heard ... that most Java bytecode compilers do little in the way of optimization before generating bytecodes.)

Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return

public Child getChild();
Code:
0: new #16; //class Child
3: dup
4: invokespecial #18; //Method Child."<init>":()V
7: astore_1
8: aload_1
9: checkcast #16; //class Child
12: areturn

}

Does using (Class) object reinstate the object in java?

It would be helpful if you provided more of a context as to what you are trying to achieve. I think what you have written now would throw a "Variable 'object' is already defined in the scope" error.

If you are trying to cast object to type Class, you would need to use a different variable name such as:

Class object2 = (Class) object;

This does not create or re-instantiate anything, the variable object2 still points to the same object.

For a more extensive answer on how casting is handled:

How does Java Object casting work behind the scene?

Performance of Object Typecasting

It is cheap enough that it falls into the category of premature optimization. Don't waste time even thinking or asking questions about it unless you have profiled your application and determined that it's a problem, and most importantly: don't compromise your design to avoid it.



Related Topics



Leave a reply



Submit