4

Having trouble understanding generic programming in Java.

I read some tutorial about it but still quite confused, especially when things get complicated.

Can anyone explain what's happening in this example?

import java.util.Date;

public class Test1 {

    public static void main(String[] args) {
        P<Cls> p = new P<>();   //<1>   //I expect a ClassCastException here, but no. Why? //How does the type inference for class P<E> work? 
        System.out.println(p.name); //it prints
//      System.out.println(p.name.getClass());//but this line throws ClassCastException //why here? why not line <1>?

        test1(p);//it runs
//      test2(p);//throws ClassCastException//What is going on in method test1&test2? 
                //How does the type inference for generic methods work in this case?
        }


    public static<T> void test1(P<? extends T> k){
        System.out.println(k.name.getClass());
    }

    public static<T extends Cls> void test2(P<? extends T> k){
        System.out.println(k.name.getClass());
    }
}

class P<E>{
    E name = (E)new Date();//<2>
}

class Cls{}
Roman C
  • 49,761
  • 33
  • 66
  • 176
du369
  • 821
  • 8
  • 22

1 Answers1

2
P<Cls> p = new P<>();

Remember that Java implements generics by erasure, meaning that the constructor for P doesn't really have any idea what E is at runtime. Generics in Java are purely there to help developers at compile-time.

This means that when you create a new P<>(), a new Date() is created, but it isn't actually cast to any particular type, because the runtime doesn't know anything about E. E does not exist at runtime, as far as the P<E> class is concerned. name is just an Object reference that has a Date inside. However, any time you write code that consumes name in a way where the runtime environment needs to know that it's of a particular type (Cls in this case), the compiler inserts a cast to that type without telling you.

  • p.name.getClass() gets compiled as ((Cls)p.name).getClass(), which will create a class cast exception.
  • test2() specifies a type constraint that is not generic (extends Cls). So its call to p.name.getClass() likewise is translated to ((Cls)p.name).getClass().

On the other hand:

  • System.out.println(p.name) is actually the same as System.out.println((Object)p.name) because println is a non-generic method that takes an object.
  • test1(p.name) is similar. Because the runtime doesn't actually know what type T is, it basically casts p.name as Object before calling getClass() on it.

In other words, here's your code as it actually gets compiled:

class P{
    Object name = new Date();
}

public static void main(String[] args) {
    P p = new P();   
    System.out.println(p.name);
    System.out.println(((Cls)p.name).getClass());

    test1(p);
    test2(p);
}


public static void test1(P k){
    System.out.println(k.name.getClass());
}

public static void test2(P k){
    System.out.println(((Cls)k.name).getClass());
}
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • This explains *that* it occurs, but it doesn't explain *why*. Why does `p.name.getClass()` involve a cast, but not `p.name`? i.e. why doesn't the first line compile as `System.out.println((Cls)p.name);`? – Oliver Charlesworth Oct 11 '14 at 12:47
  • @OliverCharlesworth: Because Java wants the runtime to know what the compiler knows about the object in order to call a method on it. The compiler paints with broader strokes than necessary in this case because `getClass()` happens to be available on all `Objects`, but if it were a method declared on `Cls` specifically then the class cast would be necessary. On the other hand, passing an object into a method that takes `Object` requires no cast. – StriplingWarrior Oct 11 '14 at 12:56
  • Ok, so really what we're saying is that the compiler elides casting in certain cases, depending on context. The question is, what are the rules that govern this? Presumably it's not random. (This is a genuine question; I can't figure out where this might be specified in the JLS, etc.) – Oliver Charlesworth Oct 11 '14 at 13:02
  • @OliverCharlesworth: I have no idea. I find the Java Spec exceedingly difficult to understand. Perhaps you should try that as its own question. :-) – StriplingWarrior Oct 11 '14 at 20:33