Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics - raw type and parameterized type conversions

I've been reading Java Generics Tutorials and few threads in Stackoverflow that deal with Generics, and still could not understand a specific case. Here it is:

public class Box<T>
{
    private T t;

    public T getT ()
    {
        return t;
    }

    public void setT (T t)
    {
        this.t = t;
    }

    public static void main (String[] args)
    {
        Box<Integer> intBox = new Box<Integer>();
        Box rawBox = intBox;
        rawBox.setT("NBA");
        System.out.println(rawBox.getT());
        System.out.println(intBox.getT());
        /*1*/ //System.out.println(intBox.getT().toString());
    }
}

Here's the deal,the first print I understand, that is,

 System.out.println(rawBox.getT());

prints NBA, because rawBox is of raw type of Box T and it "gives" us Objects.

What I don't get is the second print:

System.out.println(intBox.getT());

which prints NBA. intBox is of a generic type (in this case Box of Integers), which means its getter method should return a value of type T (which is Integer in this case), so what I understand is that the String object held there should be converted to Integer (because this is the argument type given to Box T) and a ClassCastException should be raised in runtime, but it doesn't happen, why is that?

By the way, comment number /1/ adds to the confusion, because if I were to uncomment it, it'll cause ClassCastException to be raised in ruuntime (String cannot be cast to Integer), I don't understand that

Thanks all :)

like image 873
Michael1 Avatar asked Oct 17 '22 21:10

Michael1


1 Answers

It's sometimes hard to guess where Java will insert checked casts. In general, it'll only insert them where necessary. The best way to understand the behavior you see is by checking the bytecode!

If we run javap -c Box.class (after compiling with /*1*/ uncommented), we see:

  public static void main(java.lang.String[]);
    Code:
      ...
      20: invokevirtual #8                  // Method getT:()Ljava/lang/Object;
      23: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      26: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      29: aload_1
      30: invokevirtual #8                  // Method getT:()Ljava/lang/Object;
      33: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      36: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      39: aload_1
      40: invokevirtual #8                  // Method getT:()Ljava/lang/Object;
      43: checkcast     #10                 // class java/lang/Integer
      46: invokevirtual #11                 // Method java/lang/Integer.toString:()Ljava/lang/String;
      49: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      52: return

You can see here that the only time the JVM actually requires a cast to integer is at #43 (checkcast). That's so that it can invokevirtual Integer.toString().

The call to println (#33) does not require a cast because println takes an Object, not an Integer (perhaps you thought you were calling println(int), but you're not). So the JVM never needs to check that it's an integer, because it doesn't need to be.

If instead of calling println(Object), you called a method that accepted an Integer, you should see a ClassCastException instead.

For example, this:

    ...
    print(intBox.getT());
}

private static void print(Integer integer) {
    System.out.println(integer);
}

Will perform a cast:

  26: aload_1
  27: invokevirtual #8                  // Method getT:()Ljava/lang/Object;
  30: checkcast     #10                 // class java/lang/Integer
  33: invokestatic  #11                 // Method print:(Ljava/lang/Integer;)V
like image 186
Mark Peters Avatar answered Oct 21 '22 07:10

Mark Peters