3

During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded.

But when we refer methods withing compiled class, compiler ensures type check at compile time.

For. e.g. if I use generics on class A compile it and then refer it through a class B, during compilation it will ensure type checking.

If java erases type on compilation, then how does the compiled class file ensures type checking?

Raedwald
  • 46,613
  • 43
  • 151
  • 237
Ankit Zalani
  • 3,068
  • 5
  • 27
  • 47
  • The compiler has to ensure that the references all exist before it can successfully complete. So I am not sure I follow your question. – Woot4Moo Jul 10 '13 at 19:04

2 Answers2

13

There are no type checks at runtime except for the erased upper bounds. Java Generics is all about compiler checking.

On the other hand, maybe your question is only about how can the compiler do its checks if the type parameter information is gone from the bytecode. The answer to that is that it is not gone from the classfile as a whole: it is attached as meta-data, available to the compiler (as well as the Reflection API), but irrelevant to the executing code.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • Type erasure means that individual object instances at runtime don't have their full generic type information, but the implementation of methods still has all the information it had at compile time. – Louis Wasserman Jul 10 '13 at 19:10
  • 1
    @LouisWasserman But it should be stressed that there's no *type checking* done at runtime except for erased upper bounds. The bytecode is identical to what it was on pre-Generics Java. – Marko Topolnik Jul 10 '13 at 19:40
  • Beg to disagree, since a cast actually does a form of runtime type checking. – Ingo Jul 13 '13 at 10:42
  • @Ingo That kind of type checking is implied by "except for erased upper bounds". If you "cast" a `List` already containing some `String`s into a `List`, you will get no error since the "cast" doesn't even exist at the bytecode level. – Marko Topolnik Jul 13 '13 at 11:10
  • @MarkoTopolnik No, it's not implied. Look at the bytecode in selig example, offset 27: Here you see that the result of list.get(0) is casted back from Object to String. And this involves checking if the Object is indeed instanceof String. – Ingo Jul 13 '13 at 11:21
  • @Ingo You are self contradictory now: you say "no, it's not implied" and proceed to describe precisely the type checking done on erased upper bounds. Could it be that you simply do not comprehend that concept? – Marko Topolnik Jul 13 '13 at 13:01
  • Well, @MarkoTopolnik, then your claim "no typechecking done at runtime except for erased upper bounds" really means "typechecking is done at runtime all the time". – Ingo Jul 13 '13 at 13:12
  • @Ingo Indeed, there is no comprehension. Let me demonstrate with code, then: `List> a = new ArrayList(); List b = (List) a; b.add(3);` Do you think this a) compiles, b) runs without error? – Marko Topolnik Jul 13 '13 at 13:35
2

I would disagree that there are no checks at Runtime - but it is true that all type-checking is done at compile time.

It's always interesting to look at the actual bytecode produced. Let us take this code

import java.util.*;

class Types{

  public static void main(String [] args){

    List<String> list = new ArrayList<String>();
    list.add("hello");
    System.out.println(list.get(0));

  }

}

Compile it and then disassemble it using javap -c, we get

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup           
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1      
       8: aload_1       
       9: ldc           #4                  // String hello
      11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      20: aload_1       
      21: iconst_0      
      22: invokeinterface #7,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      27: checkcast     #8                  // class java/lang/String
      30: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      33: return        
}

We notice a two things

  • As expected, the list is a list of Objects
  • On line 27 we call checkcast to ensure that the object retrieved from the list is in fact a String. This is a runtime check inserted by the compiler. Something that would have been manually introduced prior to Generics.
selig
  • 4,834
  • 1
  • 20
  • 37