Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala's @throws annotation is ignored in javac if I declare the variable as its abstract superclass

In Java you can't specify that an overridden abstract method throws some exception if the original abstract method does not (overridden method does not throw Exception.) However in Scala you can do this since it doesn't have checked exceptions. Fine, but if you use the @throws annotation that should tip the Java compiler off to what's going on, right?

Given this Scala code:

package myscala

abstract class SFoo {
    def bar(): Unit
}

class SFoobar extends SFoo {
    @throws[Exception]
    override def bar(): Unit = {
        throw new Exception("hi there")
    }
}

I have two different Java programs, one of which will compile and run into an Exception at runtime, and the other which will not compile.

Compiles:

import myscala.SFoo;
import myscala.SFoobar;

public class Foobar {
    public static void main(String[] args) {
        SFoo mySFoo = new SFoobar();
        mySFoo.bar();
    }
}

Doesn't compile (unreported exception Exception; must be caught or declared to be thrown):

import myscala.SFoo;
import myscala.SFoobar;

public class Foobar {
    public static void main(String[] args) {
        SFoobar mySFoo = new SFoobar();       // only difference is the declared type
        mySFoo.bar();
    }
}

I don't really get it. Why doesn't the Java compiler pick up on the fact that I'm declaring that SFoobar.bar throws an exception even though Foo.bar has no such declaration, and as a result, raise a similar compilation error?

like image 309
2rs2ts Avatar asked Aug 05 '14 01:08

2rs2ts


1 Answers

If you convert the Scala code for SFoo and SFooBar to equivalent Java code:

abstract class SFoo {
    void bar() {}
}

class SFoobar extends SFoo {
    void bar() throws Exception {
        throw new Exception("hi there");
    }
}

It doesn't compile, because:

error: bar() in SFoobar cannot override bar() in SFoo
        void bar() throws Exception {
             ^
  overridden method does not throw Exception

Java enforces the rule that overriding methods cannot throw exceptions that weren't thrown by the method they are overriding. This makes checked exceptions typesafe when upcasting, e.g. when converting a SFooBar to an SFoo.

Your first definition of the FooBar class, where an SFooBar is stored in a SFoo variable, would be perfectly safe if SFoo and SFooBar had been defined in Java. The Java compiler knows that an SFoo.bar doesn't throw Exception, assumes that the rule for overridden methods was observed, and so doesn't do any extra checks here.

However, the Scala compiler doesn't care about this rule for overridden methods, and will allow you to break this type safety. The Java compiler is oblivious to this fact, and so produces code that breaks at runtime. It's a mismatch between Java and Scala that neither compiler can work around.

like image 67
wingedsubmariner Avatar answered Oct 03 '22 13:10

wingedsubmariner