Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does javac allow some impossible casts and not others?

If I try to cast a String to a java.util.Date, the Java compiler catches the error. So why doesn't the compiler flag the following as an error?

List<String> strList = new ArrayList<>();                                                                       Date d = (Date) strList; 

Of course, the JVM throws a ClassCastException at runtime, but the compiler doesn't flag it.

The behavior is the same with javac 1.8.0_212 and 11.0.2.

like image 272
Mike Woinoski Avatar asked Mar 21 '20 23:03

Mike Woinoski


People also ask

Can you cast anything in java?

In Java, we can cast both reference and primitive data types. By using casting, data can not be changed but only the data type is changed. Note: type casting is not possible for a Boolean data type. There are 13 types of conversion in Java.

Do you have to Javac everytime?

No, java programs are not automatically compiled every time they run. Java programs must be explicitly compiled before they can be executed.

Is Javac open source?

The javac compiler source code is available in the OpenJDK repositories, at http://git.openjdk.java.net/. The mainline sources are in http://git.openjdk.java.net/jdk.


2 Answers

The cast is technically possible. It cannot easily be proven by javac that it is not so in your case and the JLS actually defines this as a valid Java program, so flagging an error would be incorrect.

This is because List is an interface. So you could have a subclass of a Date that actually implements List disguised as List here - and then casting it to Date would be perfectly ok. For example:

public class SneakyListDate extends Date implements List<Foo> {     ... } 

And then:

List<Foo> list = new SneakyListDate(); Date date = (Date) list; // This one is valid, compiles and runs just fine 

Detecting such a scenario might not always be possible, as it would require runtime information if the instance is coming from, for example, a method instead. And even if, it would require much more effort for the compiler. The compiler only prevents casts that are absolutely impossible due to there being no way for the class-tree to match at all. Which is not the case here, as seen.

Note that the JLS requires your code to be a valid Java program. In 5.1.6.1. Allowed Narrowing Reference Conversion it says:

A narrowing reference conversion exists from reference type S to reference type T if all of the following are true:

  • [...]
  • One of the following cases applies:
    • [...]
    • S is an interface type, T is a class type, and T does not name a final class.

So even if the compiler could figure out that your case is actually provably impossible, it is not allowed to flag an error because the JLS defines it as valid Java program.

It would only be allowed to show a warning.

like image 191
Zabuzard Avatar answered Oct 14 '22 18:10

Zabuzard


Let us consider a generalization of your example:

List<String> strList = someMethod();        Date d = (Date) strList; 

These are the main reasons why Date d = (Date) strList; is not a compilation error.

  • The intuitive reason is that the compiler does not (in general) know the precise type of the object returned by that method call. It is possible that in addition to being a class that implements List, it is also a subclass of Date.

  • The technical reason is that the Java Language Specification "allows" the narrowing reference conversion that corresponds to this type cast. According to JLS 5.1.6.1:

    "A narrowing reference conversion exists from reference type S to reference type T if all of the following are true:"

    ...

    5) "S is an interface type, T is a class type, and T does not name a final class."

    ...

    In a different place, JLS also says that an exception may be thrown at runtime ...

    Note that the JLS 5.1.6.1 determination is based solely on the declared types of the variables involved rather than the actual runtime types. In the general case, the compiler does not and cannot know the actual runtime types.


So, why can't the Java compiler work out that the cast won't work?

  • In my example, the someMethod call could return objects with a variety of types. Even if the compiler was able to analyze the method body and determine the precise set of types that could be returned, there is nothing to stop someone changing it to return different types ... after compiling the code that calls it. This is the basic reason why JLS 5.1.6.1 says what it says.

  • In your example, a smart compiler could figure out that the cast can never succeed. And it is permitted to emit a compile-time warning to point out the problem.

So why isn't a smart compiler permitted to say this is an error anyway?

  • Because the JLS says that this is a valid program. Period. Any compiler that called this an error would not be Java compliant.

  • Also, any compiler that rejects Java programs that the JLS and other compilers say is valid, is an impediment to the portability of Java source code.

like image 39
Stephen C Avatar answered Oct 14 '22 16:10

Stephen C