Omitting throws declarations in derived classes

1.1k views Asked by At

Consider the following interface:

public interface Generator {
    String generate() throws IOException;
}

and the following implementation:

public class EmptyStringGenerator implements Generator {
    @Override
    public String generate() {
        return "";
    }
}

Note that I omitted the throws IOException part of the signature specified in the Generator interface. Yet there is no compiler error, no compiler warning, not even the @Override annotation complains.

I am aware that this is working as intended. I would, however, like to know the intent behind this. If my method does not actually throw an IOException, it would be fine to just not throw it, I do not have to remove it from the signature. But if I do remove it from my method signature in EmptyStringGenerator, I am forcing all current and future subclasses of this class to forego the possibility of throwing an exception that is actually specified in the interface.

This, to me, sounds like a feature that does not really bring you any benefit (apart from saving a couple of keystrokes, which is not really a benefit at all), but has the potential to be a terrible mistake, when actually used.

So my question, effectively, is this: What is the point of omitting throws exceptions in derived classes? What is the problem that this possibility solves? Why is this allowed?

UPDATE

For people asking "but where is the harm in that?", here is my example from one of my comments. It is not far-fetched, by the way, because that is exactly what I am dealing with right now:

Programmer A specifies interface I. Programmer B writes implementation class X, but forgets to add the throws. He also never notices, because there is not even a warning being thrown here. Programmer C writes implementation class Y, inheriting from class X, he even specifically also wants to put the throws there, because he is gonna throw. But even though the interface stipulates it, he is now not allowed to do so anymore, because of B's oversight. He is effectively not allowed to use that exception here anymore. That's a pretty big harm. Especially if class X is not under your control.

3

There are 3 answers

0
mszalbach On

In Java its OK and normal that you can make your classes less restrictive when implementing or extending that is a design decision of the Java developer. I can not say why Sun decided it this way and of course I can understand your problem with it, but the current way also has some benefits.

I see an Interface as a possibility to have multiple implementations for one job. For example the List classes which have different implementations for different needs. But they can all be used via the List interface. Lets say the remove method throws a CheckedException if the element is not part of the list. Now if am not able to be less restrictive in my implementations all List classes must throw this CheckedException, even they do not need or use it.

So if I use the remove method internally in my Class I am forced to handle the CheckedException. If I use my List class directly without the Interface I am forced to catch the exception. But for both cases I am pretty sure I do not need it and it will never happen. So with the current approach I can save a lot of "try catch ignore" blocks.

An other benefit of the current solution is that a class can easily match similar Interfaces.

So for example in one lib someone added a:

public interface Generator {
    String generate() throws IOException;
}

And in an other lib:

public interface GeneratorInterface {
    String generate();
}

If I write a Class with a generate method without any CheckedException I can easily use this to satisfy both Interfaces and maybe work with both libs:

public class EmptyStringGenerator implements Generator,GeneratorInterface {

    @Override
    public String generate() {
        return "";
    }
}

Also as far as I know Java is the only language with such handling of Checked and Unchecked exceptions and so the design decisions which pull through Java are strange compared with other languages not having Checked exceptions.

3
judaschrist On

Based on your class decleration, campare these two conditions:

    EmptyStringGenerator generator = new EmptyStringGenerator();
    generator.generate();

if you create an instance of EmptyStringGenerator like above, Since you omit the throws in EmptyStringGenerator, the above code indicates you are using the class itself, which of course has nothing to do with the interface, so the code works just fine.

but if you intend to use the interface instead of the class, like this:

    Generator generator = new EmptyStringGenerator();
    generator.generate();

than the compiler would actually remind you that there is an unhandled exception, you must handle it with a try-catch or throw it, otherwise the code will not compile.

if you use any subclass of EmptyStringGenerator in the latter manner, the same compile error will occur. So omitting the throw does not actually rlease you from handling the exception. It's only natural not to throw exception in a class and its subclasses when there is none to throw, but When you are calling the method through the interface, the exception still has to be handle.

4
Jimmy T. On

If you omit the throws exception in derived classes you can call the method of the derived class without having to catch the exception.

You make sure that subclasses of EmptyStringGenerator also don't throw an exception. Otherwise it would not be sure for the compiler if the method call can cause a checked exception which the code must handle. In that case the throws wouldn't make sense at all.