3

I am stuck with a code that I do not understand why it works. Assume that I create a generic interface Foo<T> as following:

interface Foo<T>{
   void set(T item);
}

Then I create a class called Bar which implements Foo<String> as following:

class Bar implements Foo<String>{ 
   @override
   public void set(String item){
      //useless body
   }
}

Based on this we can writes following code:

Bar bar = new Bar();
bar.set("Some string");
Foo rawFoo = (Foo) bar;
rawFoo.set(new Object()); // ClassCastException: Object cannot be cast to string

That last line is the one that I don't really get. As it's known that when using raw types the generic parameter types are converted to Object. In this case the code compiles and we can pass Object to set() method. But how Java determines that it has to cast the Object to String at runtime?

halfer
  • 19,824
  • 17
  • 99
  • 186
grustamli
  • 396
  • 4
  • 16
  • [Here](https://docs.oracle.com/javase/tutorial/java/generics/erasure.html) the second bullet point: *Insert type casts if necessary to preserve type safety.* – Elliott Frisch Apr 25 '18 at 21:46
  • Let's say we call `item.charAt(0)` in the body of `Bar.set()`. What would you expect to happen when you call `set(new Object())`? – shmosel Apr 25 '18 at 21:51
  • Of course we would expect some guarantee that only String can be passed to the set method. All the answers here actually point to the same concept, bridge methods. I will need to carefully read and understand bridge methods – grustamli Apr 25 '18 at 21:59
  • @grustamli do you? Can't you just avoid raw types instead and continue to live in blissful ignorance? :) – Andy Turner Apr 25 '18 at 22:00
  • 2
    @AndyTurner, no Sir I honestly cannot sleep unless I fully understand what I read. I could live with that ignorance once I didn't know anything about generics, but now it's too late :) – grustamli Apr 25 '18 at 22:10
  • @grustamil my point was that the more important thing to do is to avoid raw types if at all possible: bridge methods are merely an implementation detail. – Andy Turner Apr 26 '18 at 06:25
  • @AndyTurner, I agree with you. I was reading a section on specializing classes by delegation, then I saw a similar code to what I posted here. I couldn't comprehend how that worked. – grustamli Apr 26 '18 at 07:08

4 Answers4

5

If you decompile Bar using javap:

class Bar implements Foo<java.lang.String> {
  Bar();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void set(java.lang.String);
    Code:
       0: return

  public void set(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #2                  // class java/lang/String
       5: invokevirtual #3                  // Method set:(Ljava/lang/String;)V
       8: return
}

void set(java.lang.Object) is a synthetic bridge method. Note the checkcast instruction. The equivalent "real" code would look like:

public void set(Object object) {
  set((String) object);
}

The compiler created this method in order for the class to work under type erasure; the actual method doing the work is set(java.lang.String), to which the bridge method delegates.

It is this bridge method which actually overrides the method in the base class. The method you declare with the @Override annotation is merely an overload.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
1

First of all, what you are doing is just providing a reference to existing object, you are not creating a new object.

When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, called a bridge method, as part of the type erasure process.

After type erasure your Foo becomes:

interface Foo{
   void set(Object item);
}

and your class becomes:

class Bar implements Foo{ 
   @override
   public void set(String item){
      //useless body
   }
}

After type erasure, the method signatures do not match.

Therefore, the Bar set method does not implement the Foo set method.

To solve this problem and preserve the polymorphism of generic types after type erasure, a Java compiler generates a bridge method to ensure that subtyping works as expected. For the Bar class, the compiler generates the following bridge method for set:

public void set(Object item){
    set((String) data);
}

https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html

MutantOctopus
  • 3,431
  • 4
  • 22
  • 31
ACV
  • 9,964
  • 5
  • 76
  • 81
  • 1
    When writing code snippets, highlight the snippet and hit ctrl+k to turn it into a code block, with four spaces at the start of each line. – MutantOctopus Apr 25 '18 at 22:32
1

Generics in Java are a compile-time type safety feature. Because they were introduced in version 1.5, they had to be backwards-compatible with prior versions. This allows you to use raw types -- a variable without type parameters referring to a class with type parameters.

At runtime, most of the type parameter information is erased, but not all of it. In this tutorial page about type erasure, we learn that the compiler will insert casts when necessary.

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.

Here, the compiler creates a bridge method to preserve polymorphism at runtime, which takes an Object, casts its parameter to a String, then calls the real set(String) method.

When you call rawFoo.set(new Object());, because of polymorphism, this bridge method is called in Bar. This is the implicit cast that you see here showing up as a ClassCastException.

rgettman
  • 176,041
  • 30
  • 275
  • 357
0

It turns out that the answer to my question is bridge methods. I am currently reading the book, 'Java Generics and Collections' by Maurice Naftalin and Philip Wadler. I actually read a section on bridge methods, however it seems I didn't read it carefully. I am glad that all the answers had common point. Now, I will go and read that section again

grustamli
  • 396
  • 4
  • 16