A while ago it happened to me again. I was working on a piece of code for which I had to extend a class and override a method; while doing so I ran straight into a brick wall in the form of that miserable throwback of the Java language, the final method. Let me tell you about it….

About a week and a half ago (from the time of writing), I had to adapt some code in the codebase of one of my projects. The code in question was business rule validation code that was being applied to a data set. It had worked fine in the past, but now the business wanted an exception made to the general rule. In other words, in certain circumstances the business rule validation should not take place. Being a well-mannered developer I work test-first, which meant creating a test to prove that certain code wasn’t being called. So how do you do that? Well, in my case it meant subclassing the class with business rules in my unit test and overriding the method in question to throw an exception if it was called. That, and overriding a setter that I needed to work slightly differently.

So that was the plan and that was what I did. I created my subclass with the overridden methods, created an instance of it and used a setter to inject my new object into the class that the unit test was testing. At least, that was the plan. Really, people, who makes a setter method final?

Polymorphism and the open-closed principle

So what, at the heart of it, really is the problem with final as a concept (in Java or any other object oriented programming language)? To answer that question we have to examine two central principles of object oriented software development (one of which is based on the other): polymorphism and the open-closed principle.

The Open-Closed Principle

The Open-Closed Principle is a principle of software development that was introduced by Bertrand Meyer and was expanded upon by Robert Martin. The principle states that any software module should be

open for extension, but closed for modification

In other words, once a software module hasbeen written and published, changes should be made by extension rather than modification. The reason is obvious: if you modify existing code, you risk breaking code that depends on the existing behavior of that code. If specialized behavior is needed, it should be added through specialization and not through change. By consequence, the code should allow for extension and not modification.

Polymorphism

The Open-Closed Principle is an application of the concept of polymorphism, the idea that code can be subclassed and specialized subclasses can override behavior defined by superclasses. More specifically, polymorphism is the concept that any one method can occur in (literally) “different shapes” – given a method signature, subclassing and overriding allows you to provide multiple implementations for that method declaration.

To give a very simple example: say that you define a class hierarchy of apples. You create an abstract class Apple with an abstract method public abstract Color getColor();. You then introduce subclasses for different kinds of apples (Granny Delicious, Elstar, etc.). Each of these subclasses provides its own implementation of the getColor() method, since they all have different colors. This is polymorphism in action: one method declaration can be associated with multiple method definitions.

The final problem

The problem with final as a concept is obvious in the light of these central principles of object orientation: final kills of all inheritance possibilities and with it polymorphism and the Open-Closed Principle. In other words, final takes your object oriented program and turns it into an old-fashioned imperative program instead.

Valid uses of final

That isn’t to say there aren’t valid (or at least seemingly valid) uses of final in Java. Framework builders often have a real need of final to prevent their framework code from being overwritten. The main application of final in this context is to allow you to keep on developing and extending your own framework code across releases without breaking code written by users of your framework. The archetypal example of this is the java.lang.String class that is part of the Java core library. The JCP would not be able to keep on adding to and expanding upon this class if it wasn’t totally frozen; if String was open to extension, anybody might extend it and any changes in the next Java release might break those extensions. Similar considerations exist in most frameworks.

The alternative

So here we are, faced with this ostensible contradiction between a mechanism that is anathema to object oriented programming and yet a real need for this mechanism to prevent third-party developers from boxing you in. How can we reconcile these issues and reach a satisfactory middle ground?

The answer starts with stepping back and examining what the final concept tries to achieve in a more formal sense. Informally, the whole point of final is to freeze a piece of code with respect to redefinition (i.e. overriding). That is, final is supposed to prevent everybody from changing the meaning of a piece of code. In a more formal sense, final is meant to safeguard the trinary relation

{P}S{Q}

that describes the semantics of a method (in this relationship P is the method precondition, S is the method and Q is the method postcondition). Overriding may, obviously, change this relationship.

However, even though it is very effective, final is a very drastic measure to safeguard this relation. As mentioned before, final serves to safeguard the relation by freezing the code – and therefore prevents redefinitions of the code that may in fact be quite valid within the contraint of this relation. Specifically, predicate calculus teaches us that there are two types of redefinitions that we can allow within this relation without breaking client code: we can weaken the precondition (thereby expecting less of the client code) and/or strengthen the postcondition (thereby providing the client code with more than it expects). Either one will allow redefined code to serve as a drop-in replacement for the original code. And moreover will allow redefition of the code within constraints that are guaranteed not to break anything (rather than forbidding all redefinition).

In other words: what is needed in Java is a form of compiler-based pre- and postcondition checking. Which should be based on a language extension that allows developers to express these pre- and postconditions in a formal way. Something similar to the Design By Contract mechanism introduced by Bertrand Meyer in his Eiffel language (although preferably a little more capable). Something in any case that allows us to constrict overriding in a way that is less blunt than final.

The final problem
Tagged on:     

One thought on “The final problem

  • June 24, 2008 at 1:27 am
    Permalink

    Nicely written!

    While adding Design-By-Contract (DBC) constructs to the language is a great idea, it is also quite dangerous as it can easily be misused. Moreover, one should really watch out how far DBC should be applied. As you probably know (assuming you wrote some Eiffel code), applying DBC as Bertrand actually meant it is great in theory but falls short in practice (it is not that easy and intuitive task to apply DBC correctly). So I would say that adding these constructs, although powerful, may eventually bring more hard than good (just look how easily people misuse the final keyword… yeah… a final setter.. thats a first for me). Further more, I do think that the final keyword has a place and can be used in scenarios where pre/post conditions (along with in/variants) cannot really help out. For example, providing some functional computation which cannot change by definition in order to implement a “correct” implementation. Yes, pre/post condition can help you assure in/variants states but sometimes you just don’t want the client of your code to even “play around” with some code. When used properly, within a well designed API, the final keyword should also not stand in the way of the “Opened/Closed Principle”. This is also the case where “interface oriented design” helps quite a bit.

    Anyway, it’s a great and well written post which definitely makes you think… thanks.

    Uri

Comments are closed.