1

Having some idea of type erasure, I would think this cast would not compile or work at runtime:

public class GenericDatumReader<D> implements DatumReader<D> {
    ...
    @SuppressWarnings("unchecked")
    public D read(D reuse, Decoder in) throws IOException {
      return (D) read(reuse, actual, expected != null ? expected : actual, in);
    }

However, it works!

How does the program know at runtime what D is?

EDIT

I wrote a simple test:

  1 public class Main {                                                                                                                                                           
  2                                                                                                                                                                               
  3     public static class Something<D> {                                                                                                                                        
  4                                                                                                                                                                               
  5         private Object getObj() { return new Object(); }                                                                                                                      
  6         private String getStr() { return new String("hello"); }                                                                                                               
  7                                                                                                                                                                               
  8         public <D> D get(boolean str) {                                                                                                                                       
  9            return (D) (str ? getStr() : getObj());                                                                                                                            
 10         }                                                                                                                                                                     
 11     }                                                                                                                                                                         
 12                                                                                                                                                                               
 13     public static void main(String[] args) {                                                                                                                                  
 14         Something<String> something = new Something<>();                                                                                                                      
 15         String str = something.get(true);                                                                                                                                     
 16         String notStr = something.get(false);                                                                                                                                 
 17     }                                                                                                                                                                         
 18 } 

Without the (D) cast, this doesn't compile:

Main.java:9: error: incompatible types: bad type in conditional expression
           return (str ? getStr() : getObj());
                               ^
    String cannot be converted to D
  where D is a type-variable:
    D extends Object declared in method <D>get(boolean)
Main.java:9: error: incompatible types: bad type in conditional expression
           return (str ? getStr() : getObj());
                                          ^
    Object cannot be converted to D
  where D is a type-variable:
    D extends Object declared in method <D>get(boolean)
2 errors

With the (D) cast I get just an unchecked warning but it compiles. However, at runtime:

$ java Main
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
        at Main.main(Main.java:16)
Dmitry Minkovsky
  • 36,185
  • 26
  • 116
  • 160
  • 1
    The answer is simple: it doesn't. – biziclop Jul 23 '15 at 16:51
  • It doesn't need to; after type erasure, `read` returns an `Object`, so there's no need for a cast inside of the function itself. – Colonel Thirty Two Jul 23 '15 at 16:51
  • @biziclop the code compiles, and the cast works... in so far as I get the object I expected. Yes, the warning is suppressed, but the code works. – Dmitry Minkovsky Jul 23 '15 at 16:53
  • @ColonelThirtyTwo can you explain further? You're saying after type erasure all `D`s are made into `Object`s? – Dmitry Minkovsky Jul 23 '15 at 16:54
  • 1
    @dimadima The code works but the cast doesn't do anything. Read [this](https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.5.1), in particular the line: `if T is a type variable, then this algorithm is applied recursively, using the upper bound of T in place of T.` Since `D` can always be cast to the upper bound of `D`, this cast will always work. – biziclop Jul 23 '15 at 16:55
  • 1
    @dimadima Yes, see http://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java – Colonel Thirty Two Jul 23 '15 at 16:56
  • @biziclop thank you for the link/post. So you're saying the cast does nothing, but rather the `D` specified as return value does the trick? – Dmitry Minkovsky Jul 23 '15 at 16:59
  • But yes, will read these links. Thank you. – Dmitry Minkovsky Jul 23 '15 at 17:01
  • 1
    Those saying that the cast "does nothing" aren't being entirely accurate. The cast tells the compiler that it can treat the operand as being of the given type, for purposes of type checking and member resolution. However, your question: "How does the program know at runtime what D is?" - doesn't make a lot of sense. At runtime, 'D' is not any type. Java generics are implemented through _type erasure_; type parameters effectively disappear during compilation. You need to explain why you think the cast shouldn't work. – davmac Jul 23 '15 at 17:44
  • @davmac: To be clear, though, since `D` has no type constraints on it, telling the compiler it can treat it as a `D` is no different than telling it that it can be treated as an `Object`, which the compiler would have assumed anyway. So in that sense, it "does nothing" in this case. – StriplingWarrior Jul 23 '15 at 17:50
  • Rather, it "does nothing" except to cause the compiler to allow this value to be returned from a method that claims to return a `D`. – StriplingWarrior Jul 23 '15 at 17:58
  • @davmac so the static cast is used strictly at compile time, and permits this to compile. I think this is making sense. Not very complicated? – Dmitry Minkovsky Jul 23 '15 at 17:59
  • At compile time, the compiler _does_ know what I'm using for `D`, so it can perform that static cast using that "reified" type. At run time it doesn't know what `D` is, but since the compiler checked the program during compilation, the program should work. – Dmitry Minkovsky Jul 23 '15 at 18:01
  • @StriplingWarrior right; it "does nothing" _except_ what it actually does do :) – davmac Jul 23 '15 at 18:21
  • @dimadima wrong use of "reified", but: at compile time, the compiler can verify that the type returned is a `D` because you've cast the expression you return to a `D`, so it passes the compile-time type check. At run-time `D` is mostly equivalent to `Object`. (Note however that a cast to a type parameter isn't necessarily translated to "do nothing" at run time, if the type parameter has a bound that isn't `Object` - in that case, it emits a run-time check to the bound type). – davmac Jul 23 '15 at 18:27
  • 1
    @davmac Just to clarify, by "it does nothing" I meant "it does nothing at runtime". It is, as you say, purely for the sake of the compiler. – biziclop Jul 23 '15 at 18:32
  • @biziclop right, I understand. I feel it's important to make the distinction, though. – davmac Jul 23 '15 at 19:11

1 Answers1

3

There are two aspects here:

  1. Compile-time: This works because the only thing you know about D at compile-time is that it's an Object, so it's perfectly possible that the returned value is actually going to be a D--the compiler can't determine otherwise.
  2. Run-time: Because Java implements generics via erasure, generic types are almost entirely a compile-time concept. This method actually has no idea what D is at run-time, so the cast doesn't do anything at all. If a non-generic method assigned the result to some type that was known at runtime (i.e. non-generic), then it could create a cast exception if the object was not of the right type.
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • 1
    So basically I get the type I expect because I assign the result of this `read()` to a variable defined with the type I expect? – Dmitry Minkovsky Jul 23 '15 at 17:05
  • 1
    @dimadima: Yup. Or, more broadly, you don't try to use the result of the method call in an expression that requires the result to be of a different type than what the returned value turns out to be at run-time. A variable assignment is only one possible example of this. – StriplingWarrior Jul 23 '15 at 17:56
  • 1
    So with #1 you're saying the compiler permits the cast because it's a possible cast. Some casts don't make sense at all, so the compiler won't even compile with them. But this one is in the realm of possibilities (`Object` -> `D`). – Dmitry Minkovsky Jul 23 '15 at 18:07
  • 1
    Ok okay. Just for closure here: So the `(D)` lets this compile. The cast, however, happens at runtime, and is determined by the context surrounding the method's result. I updated my example to demonstrate this. Thank you. – Dmitry Minkovsky Jul 23 '15 at 18:17
  • Good example. The key is to notice that the exception is thrown in your non-generic method after the value is returned, and not when `get()` appears to do a cast to `D`. :-) – StriplingWarrior Jul 23 '15 at 18:52