简体   繁体   中英

How can it be that Java is NOT type safe in a particular situation?

Today I found a very very weird behavior of the JVM, a (normally type-safe) List<Date> did actually hold a List<MyObject> at runtime!

I wonder how that ever could happen, but couldn't find anything in the web.

That's the situation: I'm working with Spring Data JPA 1.2.0 on a JBoss EAP 6.0 Server with JRE 1.6.0_18-b07 .

By mistake, in a Spring Data JPA Repository class there was written a wrong result type in the @Query expression. It should have been:

  @Query("select distinct trunc(record.orderDateTime) from MyType record [...]" )
  public List<Date> getOrderDates(...);

But was:

  @Query("select record from MyType record [...]" )
  public List<Date> getOrderDates(...);

So, the aim was to load a list of dates ( java.util.Date ), which works fine if the query is properly defined as in the first code snippet.

But that coding mistake led to the following result: At runtime, there actually WAS a List<MyType> returned, even though the method's signature defines a List<Date> . Also in my model an attribute of List<Date> was/contained a List<MyType> . I debugged it and couldn't believe my eyes! I could even write the content of this list to a JSP (I only recognized this weird behavior because a JSP couldn't be displayed any more due to a Spring Expression Language error occuring with the attempt to type match from MyType to Date , which of course had to crash).

Hell, shall I now loose my believe in the type-safety of Java?

Has anybody ever had such a problem?

Does an explanation for this exist?

And can I do anything to fix that or is it a general issue? Maybe another version of the JRE, JBOSS, ...?

I wonder how that ever could happen, but couldn't find anything in the web.

It happens because generics are mostly a compile-time-only feature. Metadata is maintained in terms of a class's type parameters, fields etc... but at execution time, type arguments are (mostly) lost. For example:

Object x = new ArrayList<String>();
Object y = new ArrayList<Integer>();
System.out.println(x.getClass() == y.getClass()); // True

The JVM can't tell the difference - which is why you'll get a warning when you try to cast:

// At execution time, this cast will *really* only check for List
List<String> dodgyCast = (List<String>) y;

For the most part, the compiler keeps "regular" code using generics safe. But when you have things like ORMs providing values via reflection or dynamic byte code, all of that safety goes out of the window.

Sadly, that kind of thing can happen because of type erasure . The type you set at your collection is only checked at compilation time, not runtime.

The thing is that your query is not compiled, so there's no way Java can know what kind of objects it will return. To avoid this kind of behaviour, I always create tests of my queries.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM