105. Final Classes and Methods

A class marked with the final modifier cannot be extended (subclassed) by another class. In the example below we draw a line at SuperHero and the attempt to create a SuperSuperHero will cause a compilation error:

Classes marked final cannot be extended
class Person {
    //...
}

final class SuperHero extends Person {
    //...
}

//This isn't allowed as SuperHero is declared as final
class SuperSuperHero extends SuperHero {
    //...
}

Why do this? Whilst reuse is highly regarded in programming it’s important to keep good encapsulation and code maintainability in mind. By marking a class as final we can lock down the implementation and ensure that no-one is tempted into believing that we’ll support their subclass implementation.

When a method is marked as final it cannot be overridden. This is very useful if you want to block future implementations from altering the implementation. In the example below, the getName method in SuperHero is marked as final and the attempt by the BizarroSuperHero to override it will cause a compilation error:

Methods marked final cannot be overridden
class Person {
    private String name

    final setName(name) {}
}

class SuperHero extends Person {
    final String getName() {
        'Unknown'
    }
}

class BizarroSuperHero extends SuperHero {
    //This will NOT be allowed:
    String getName() {
        name
    }
}

Importantly, marking a method as final does not transfer to its overloaded siblings. In the example below, I try to protect my hero’s identity but am not thorough enough:

Methods marked final cannot be overridden
class Person {
    String name
}

class SuperHero extends Person {

    final String discoverName() {
        'Unknown'
    }

    String discoverName(Boolean mindRead) {
        'Unknown'
    }

    final setName(name) {}
}

class BizarroSuperHero extends SuperHero {

    //This WILL be allowed:
    @Override
    String discoverName(Boolean mindRead) {
        name
    }
}

def batBoy = new BizarroSuperHero(name: 'Bryce Rain')

assert batBoy.discoverName() == 'Unknown'
assert batBoy.discoverName(true) == 'Bryce Rain'

In that last example, the BizarroSuperHero can’t override String discoverName() but is allowed to override String discoverName(Boolean mindRead). If I’d been thorough in my information hiding attempts I’d have marked all discoverName methods as final or been really certain and marked SuperHero as final.