7

Observation

There are many Exception subtypes that don't have any added state or behavior. For example, ClosedByInterruptException code.

Question

Why is subtyping without adding state or behavior "acceptable" in the case of Exception? From what I know, an interface should be used when defining new behavior, and a class should be used when implementing behavior or when state is added (which may cause behavior to be overriden). Other than Exception types, would applying be acceptable?

Please don't use markers as an example.

Elaboration

Below is a design that is frowned upon:

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
}

class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
}

abstract class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

   //...assume methods that use String name are not overridable
}

For those who don't understand why this is frowned upon, see bottom of post.

For those who do understand, this would fall under the same category as ClosedByInterruptException: the subtypes do not define new state or behavior. So why would exceptions like this exist if the design is frowned upon?

Although the exception is more descriptive, an extended description can be included via a String message. Seeing how a SocketException is thrown in the case of a socket being closed early, rather than SocketClosedException or SocketNotConnectedException, why does a newer API use the design in question?


For those wondering why the design is frowned upon

Looking at the Cat and Dog example, both would perform the exact same way, since no new behaviors were added, nor were behaviors overwritten. The only difference would come from type checking via instanceof. If you use instanceof, you are attempting to perform some kind of behavior based on the type, for example:

Animal animal = ...;

if(animal instanceof Dog) {
    //perform some behavior
} else if(animal instanceof Cat) {
    //perform some behavior
}

In a case like this, the behavior should be contained within the type by overriding a method, and the behavior should be triggered via polymorphism.

5
  • Who said that it was acceptable? Where did you read that?. What if the purpose of all the 74 IOExceptions is merely informative (markers)? Is by any chance the JDK a paradigm of best practices?
    – Laiv
    Commented Dec 24, 2016 at 2:51
  • @Laiv It was accepted by those developing the JDK, and I highly doubt their decisions (especially during a modern era, the example exception is from nio) were without reason. I was wondering what would lead them to implement a design that's commonly frowned upon.
    – Dioxin
    Commented Dec 24, 2016 at 4:09
  • Your question title has nothing in common with your question. Different exception subtypes can be used for controlling the program, they not "just for documentation purposes". Sounds you have a misconception here?
    – Doc Brown
    Commented Dec 24, 2016 at 7:20
  • @VinceEmigh, when I ask if jdk is a paradigm of good practices, I'm don't descredit the devs. You are comparing the JDK with cat/dog example and both are very different things. I would consider that both may not be governed by the same principles and "rules". You will read often that using exceptions for flow control is considered a doubtful practice. However, in embedded systems the practice is fairly common and sometimes necessary. Creating custom exceptions is another doubtful practice and as I said, IO has 74 subtypes.
    – Laiv
    Commented Dec 24, 2016 at 10:29
  • Since you did not react to my comment, I took the freedom to change your question title by myself. If I did not get it right, feel free to revert my edit, but if you do so, consider to edit the question to solve the contradiction I informed you about.
    – Doc Brown
    Commented Dec 27, 2016 at 8:34

4 Answers 4

16

Java's exception handing mechanism uses the class hierarchy to model a taxonomy of exception cases.

  • Catch phrases effectively do instanceof tests. Thus they can distinguish ClosedByInterruptException from more general cases AsynchronousCloseException, ClosedChannelException, etc.
  • Throwable#toString() includes the class name. Thus the stack traceback shows the specific type of exception that occurred.
  • A throws clause also distinguishes the given exception type from its more general cases (represented as superclasses).

The design guideline you referred to applies in the common case of treating an instance in the ordinary object-oriented way -- as something with state, behavior, and identity. Just send it messages.

But sometimes people use the class hierarchy as a modeling tool. Another example would be instances read from and written to a database. The database I/O framework can pair the classes with database tables.

1

If exceptions would be implemented the way you suggest, it would mean any exception would have to be caught and eveluated at every level exception handlers exist. This would defeat a large part of their current purpose and appeal and would ultimately not be that much better than returning error codes and evaluating them on every level of the call stack, like we used to do in the pre-OO era. So maybe, from an OO perspective, it is a kludge but the benefits are substantial.

As for the animal-cat example, these kind of violations are often done for consistency and practical reasons. You may not have a full implementation for all concrete animals yet but your program structure requires the one generic class and the extensions, like when you use the visitor pattern for example.

Your observation is probably the explanation for the fact that a lot of people cannot be bothered to create a custom exception class for any problem domain level type of issue and that this bad practice is so common.

0

Why is subtyping without adding state or behavior "acceptable" in the case of Exception? From what I know, an interface should be used when defining new behavior, and a class should be used when implementing behavior or when state is added.

Default methods were only added to Java in release 8, the Exception class has been in the source since release 1, so until release 8, there was no way pass functionality down the tree unless a class was used.

Using an interface for an exception would contribute to copy-pasta code. You'd have to re-implement the logic for each exception (which aren't that different). A class allows you to use a constructor or two, and you're done.

By extending from throwable, you're adding state - the stack trace shows the specific exception that occurred, and a few methods also return the name of the exception, such as toString().


In the Wikipedia article Marker interface pattern:

A major problem with marker interfaces is that an interface defines a contract for implementing classes, and that contract is inherited by all subclasses. This means that you cannot "unimplement" a marker. In the example given, if you create a subclass that you do not want to serialize (perhaps because it depends on transient state), you must resort to explicitly throwing NotSerializableException (per ObjectOutputStream docs)

This is the class definition for java.lang.Throwable:

public class Throwable implements Serializable

Looking at the Cat and Dog example, both would perform the exact same way, since no new behaviors were added, nor were behaviors overwritten. The only difference would come from type checking via instanceof.

The try/catch mechanism of java relies heavily on instanceof. It allows you to catch broad exceptions IOException, or specific exceptions ArrayIndexOutOfBounds. It allows specific exceptions to exist, and new functionality to be added to an exception, even if rarely done. This isn't as easy by 'overriding a method and having the behavior triggered by polymorphism'.

Design patterns come and go. Just because a pattern is used somewhere, or something is implemented a specific way, even in the JDK official source, doesn't mean it is the best choice. It might've been at the time, or maybe no one thought of implementing said way.

0

I don't think it to be a bad practice. That's called design:

  • You can model a type hierarchy or a set of interfaces so as to give junior programmers a starting point.
  • Software is not writen in one day, so perhaps you can have subtypes with no added behavior or state to be completed tomorrow, the same way you can write a bunch of interfaces to make a squeleton of a design and flesh them out along subsequent days.
  • In the case of exceptions in Java I think it is indeed very useful since you can catch specific exceptions, otherwise you would be using case structures to determine what exception was thrown.

Just for the record: I prefer composition and interfaces over inheritance and abstract classes but they do have their place and uses and the Java Exception handling is an example of inheritance put to good use, even if no further behavior or state is added by subclasses.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.