Home > Blockchain >  Why does this code not issue a `ClassCastException` warning at compile-time?
Why does this code not issue a `ClassCastException` warning at compile-time?

Time:09-22

I have seen this question, but it does not seem to answer both cases below and does not reference any further documentation, which I'd love to read.

I have the following fragment of code:

    public static void main(String[] args) {
        // compile-time OK
        // run-time ClassCastException
        Child noWarnings = (Child) getParent();

        // vs...

        // compile-time failure
        Child compileTimeFailure = new Parent();
    }
    
    private static Parent getParent() {
        return new Parent();
    }

    private static class Parent { }

    private static class Child extends Parent { }

The code on line 4 of the snippet produces no warnings, but results in a ClassCastException. Why is the compiler unable to typecheck this?

This gets even weirder if we add a layer of indirection:

    public static void main(String[] args) {
        // compile-time OK
        // run-time ClassCastException
        Boxed noWarnings = new Boxed((Child) getParent());

        // vs...
        
        // compile-time OK, but warning is emitted correctly
        // run-time ClassCastException
        Boxed classCastWarning = new Boxed((Child) new Parent());
    }

    private static Parent getParent() {
        return new Parent();
    }

    private static class Boxed {
        public Boxed(Child data) {

        }
    }

    private static class Parent { }

    private static class Child extends Parent { }

It seems un-intuitive that the compiler does not allow the first example, but allows the second.

Any light shed on either of these case would be most welcome.

CodePudding user response:

Why is the compiler unable to typecheck this?

Some Parent instances are instance of Child, because all Childs are Parents. The compiler doesn't know if the instance that will be returned is a Child or not; but it could be. So it allows it.

In fact, as Stephen C points out, JLS does not allow the compiler to call this a compilation error.

Specifically, the language spec says:

If the compile-time type of the operand cannot be converted by casting conversion (§5.5) to the target type specified by the cast operator, then a compile-time error occurs.

(It also describes another situation in which compile-time errors occur in casting expressions; but these don't apply here because there isn't one or more AdditionalBound terms).

Parent can be converted to Child by a narrowing reference conversion, so a compile-time error doesn't occur.

Boxed classCastWarning = new Boxed((Child) new Parent());

This is obviously going to fail. new Parent() isn't an instance of Child, it's an instance of Parent.

But the compiler doesn't actually consider anything about new Parent() in the context of the cast beyond the fact it's an expression of type Parent, which it has to allow you to cast to a Child.

But that's not to say that tooling such as an IDE isn't allowed to give you a warning (or even an error, if you have it so configured): it's clear via static analysis that new Parent() is going to be an instance of Parent exactly (because that's all new Parent() can be), so casting that to a subclass is going to fail. Your tooling can tell you things that aren't specifically covered in the spec.

CodePudding user response:

Apart from existing answer, I would like to stress the reason why it's not easy to compute this exception during compile time.

The below example is also from the excerpt from https://docs.oracle.com/javase/specs/jls/se11/html/jls-5.html#jls-5.5 with minor additions from me.

class Point { int x, y; }

interface Colorable { void setColor(int color); }

class ColoredPoint extends Point implements Colorable {
    int color;
    public void setColor(int color) { this.color = color; }
}
final class EndPoint extends Point {}

class Test {
    public static void main(String[] args) {
        Point p = new Point();
        ColoredPoint cp = new ColoredPoint();
        Colorable c;
        // The following may cause errors at run time because
        // we cannot be sure they will succeed; this possibility
        // is suggested by the casts:

        cp = (ColoredPoint)p; // p might not reference an
                              // object which is a ColoredPoint
                              // or a subclass of ColoredPoint

        // this wont throw casts exception as references to object is valid
        Point refPoint = new ColoredPoint();
        cp = (ColoredPoint)refPoint; // perfectly valid

        c = (Colorable)p;      // p might not be Colorable

        // The following are incorrect at compile time because
        // they can never succeed as explained in the text:
        Long l = (Long)p;            // compile-time error #1
        EndPoint e = new EndPoint();
        c = (Colorable)e;            // compile-time error #2
    }
}

This example is self-explanatory on its own most cases, but you can give further read to the documentation above.

CodePudding user response:

ClassCastExceptions never occur at compile time. All exceptions occur at runtime. You can check the settings of your IDE to get hints while coding.

  • Related