2

The following code doesn't compile because of an unhandled exception, though it seems to me like there should be no problem:

class Car {
    public void drive() throws Exception {
        System.out.println("Driving...");
    }
}

public class Sedan extends Car {
    public void drive() {
        System.out.println("Driving Sedan...");
    }
    public static void main(String[] args) {
        Car c = new Sedan();
        c.drive(); //unhandled exception!
    }
}

Shouldn't it be obvious to the compiler that when the overriding method c.drive() is called, a checked exception will not be thrown? Why is it that just because the reference is of type Car instead of type Sedan, we have to treat drive as if it still throws a checked exception? The overriding method doesn't!

John Thompson
  • 1,674
  • 1
  • 20
  • 35
  • `drive` can throw `Exception` but `main` doesn't handle it. – tkausl Aug 23 '16 at 13:22
  • 3
    because `c` could aswell be not a `Sedan` which could aswell `throw` an `Exception`. As the `throw` is defined in `Car` you need to handle it. – SomeJavaGuy Aug 23 '16 at 13:23
  • Do `Sedan c = new Sedan();` – Santiago Gil Aug 23 '16 at 13:23
  • The compiler won't go out of its way to see if you're trying to trick it or not. You've declared the superclass to throw `Exception` and although a subclass may not throw one, it still needs to be handled because it *can* throw one. – Kayaman Aug 23 '16 at 13:24
  • The checked exception system is based on the *static type* of the variable. There is no type inference involved that tells the compiler, that *at runtime*, `c` never behaves like `Car`s are allowed to, because it's always a `Sedan`. Only static typing, here. – dhke Aug 23 '16 at 13:27
  • 1
    It would be possible for the compiler to infer that information in a lot of cases. But crucially, not in all cases, and that would lead to a set of rules so convoluted that no-one would understand them fully. And I think we can all agree that the JLS is complicated enough as it is. :) – biziclop Aug 23 '16 at 13:30

2 Answers2

5

Unfortunately, no, it's not obvious to the compiler.

The compiler is essentially looking at Car c and the call to drive. The compiler has no knowledge of the run-time type of the object that c is pointing to. Because of this, it evaluates the method signature of Car.drive(), which includes the throws Exception.

To make it clearer, what if in some other method c was reassigned to some SUV object which still throws this exception? The compiler has no way to know the state of the object by the time the drive method is called.

Zircon
  • 4,677
  • 15
  • 32
  • Actually, if we had `final Car c`, the type inference would be possible. But it doesn't happen, because the language is built around static typing. – dhke Aug 23 '16 at 13:29
  • @dhke Since we also have the concept of "effectively final", such explicit marking would not be necessary. But it would be mightily confusing. – biziclop Aug 23 '16 at 13:38
  • @dhke With type inference you'd have `val c = new Sedan()` and then it would work for the obvious reason. If you explicitly declare the type as `Car`, then that is what you get. – Marko Topolnik Aug 23 '16 at 13:43
0

You can fix it with

1.

Sedan c = new Sedan();
c.drive();

2.

or

Car c = new Sedan();
((Sedan) c).drive();
Santiago Gil
  • 1,292
  • 7
  • 21
  • 52