80. Final Variables

The final modifier can be prefixed to a class- or instance-variable declaration so as to declare it to be immutable (something that doesn’t change). Once set, any attempt to change the value will result in a groovy.lang.­ReadOnlyPropertyException but you have to be mindful of a few gotchas, especially with collections and objects - we’ll cover these shortly.

First up, let’s look at the final modifier in action:

Class and instance usage
class Record {
    static final String OWNER
    final Date creationDate
}

In the code above I’ve declared one class variable (owner) and one instance variable (creationDate) as final. You’ll notice that I’ve not actually set the value for these so that’s the next step. I have three options available to me when setting the value for a final variable:

Option 1: At the point of declaration (class and instance variables):

class Record {
    static final String OWNER = 'Secret Corp'
    final Date creationDate = new Date()
}

Record myRecord = new Record()

Option 2: In an initializer block (class and instance variables):

class Record {
    static final String OWNER

    static {
        owner = 'Secret Corp'
    }

    final GregorianCalendar creationDate

    ;{
        creationDate = new GregorianCalendar()
    }
}

Record myRecord = new Record()

Option 3: In the constructor (instance variables only):

class Record {
    static final String OWNER = 'Secret Corp'

    final Calendar creationDate

    Record() {
        creationDate = new GregorianCalendar()
    }

    Record(GregorianCalendar created) {
        creationDate = created
    }
}

Record record1 = new Record()

GregorianCalendar created = new GregorianCalendar(2015, 5, 4)
Record record2 = new Record(created)

Option 1 is usually best for simple assignments (such as a value or a minor expression) and Option 2 is handy if the you need a more complicated expression or set of expressions. The third option is mainly used when the value is passed by the client code into the constructor and then assigned to the instance variable either directly or following some evaluations.

Final fields and the map-like constructor

Just remember that the map-like constructor that comes as a Groovy beans bonus won’t help you with final variables. The code below won’t work as Groovy is not setting creationDate in the constructor but through the setter after instantiating the instance:

The attempt to use handy built-in approach will fail
class Record {
    final Date creationDate
}

Record myRecord = new Record(creationDate: new Date())

Final objects

When a variable is marked as final it is the value held by the variable that is immutable. This is fine for primitive values (such as int) and some of the elementary classes (such as Integer and String) as their underlying value isn’t changeable once instantiated. However, if that value points to an object that is mutable (can be changed) then your class might find its variables being changed by code outside the class. This isn’t a good thing as the class should be managing its own state. Let’s take a look at how this can happen and how we can stop it.

First up, let’s consider a class FinalReport that is meant to hold a set of Records for archiving purposes. That means that once a FinalReport has been prepared, we don’t want people tampering with it:

First stab at a protected report
import groovy.transform.ToString

@ToString(includeNames = true)
class FinalReport {
    final List records

    FinalReport(List records) {
        this.records = records
    }
}

@ToString(includeNames = true)
class Record {
    final Date creationDate = new Date()
    String title
    String text

    Record(String title, String text) {
        this.title = title
        this.text = text
    }
}

def recordSet = [
        [ 'Record A', 'This is a record' ] as Record,
        [ 'Record B', 'This is another record' ] as Record,
        [ 'Record C', 'This is yet another record' ] as Record
]

FinalReport report = new FinalReport(recordSet)

report.records[1].text = 'REDACTED'
println report.records[1]

report.records << new Record('Record Z', 'You just got hacked')
println report

First of all you’ll notice the @ToString(includeNames = true) annotation. This is used to have a toString() method generated for the class. This is really handy and I provide a description in the Useful Annotations chapter.

When setting up the FinalReport class I dutifully set final List records so that the list of records is final but two sections of code just blew a hole in my archive-ready report. The first one altered the text of a record in the report:

report.records[1].text = 'REDACTED'
println report.records[1]

The second section of code added a new record to the report:

report.records << new Record('Record Z', 'You just go hacked')
println report

My FinalReport isn’t really very final and is quite open to tampering. This is one reason you write test suites for your code - to make sure that you haven’t made an incorrect assumption. Let’s take a look at a more locked-down version of the previous code:

Make it all final
import groovy.transform.ToString

@ToString(includeNames = true)
class FinalReport {
    final List records

    FinalReport(List records) {
        this.records = records.asImmutable()
    }
}

@ToString(includeNames = true)
class Record {
    final Date creationDate = new Date()
    final String title
    final String text

    Record(String title, String text) {
        this.title = title
        this.text = text
    }
}

def recordSet = [
        [ 'Record A', 'This is a record' ] as Record,
        [ 'Record B', 'This is another record' ] as Record,
        [ 'Record C', 'This is yet another record' ] as Record
]

FinalReport report = new FinalReport(recordSet)

//This will fail with groovy.lang.ReadOnlyPropertyException
try {
    report.records[1].text = 'REDACTED'
} catch (ReadOnlyPropertyException e) {
    println 'Sorry, you can\'t change a record in a final report'
}

//This will fail with java.lang.UnsupportedOperationException
try {
    report.records << new Record('Record Z', 'You just got hacked')
} catch (UnsupportedOperationException e) {
    println 'Sorry, you can\'t add a record to a final report'
}

println report

You’ll notice I’ve made a number of changes to really lock things down:

  1. All of the properties in Record are now marked as final
    • This means that Record instances can’t be tampered with post-creation
  2. The records property in FinalReport is still marked as final (final List records)
    • This means that the records list can’t just be swapped over for another
  3. In the FinalReport constructor I call asImmutable() against the records parameter as this creates a copy of the list and marks it as immutable.
    • This means that the list can’t have new items added or removed.

My first stab at the code assumed that final List records meant that the list of records couldn’t be changed. This is true to an extent - once records was assigned an instance of a list it couldn’t be assigned another. However, it didn’t mean that the items in the list couldn’t be changed or the list have items added/removed. I needed to make sure that each list item (each being a Record instance) was itself locked down by making all of its properties final. I also needed to lock down the list being passed to my constructor by using the asImmutable() method to copy the incoming list and stop it from being changed.

The @Immutable annotation

As always, Groovy gives me a very handy approach to locking down my classes so that they’re immutable. The @Immutable annotation does quite a number of things for me, including:

  • Makes properties final
  • Sets up a map-based constructor and a tuple constructor (as per @TupleConstructor)
  • Ensures that certain types of parameter (such as Date and collections) are defensively copied
  • Prepares a toString method (as per @ToString)

So here’s how our FinalReport code now looks with the help of @Immutable:

Use the @Immutable annotation
import groovy.transform.Immutable

@Immutable
class FinalReport {
    final List records
}

@Immutable
class Record {
    Date creationDate = new Date()
    String title
    String text
}

def recordSet = [
        new Record(title: 'Record A', text: 'This is a record'),
        new Record(title: 'Record B', text: 'This is another record'),
        new Record(title: 'Record C', text: 'This is yet another record')
]

FinalReport report = new FinalReport(recordSet)

//This will fail with groovy.lang.ReadOnlyPropertyException
try {
    report.records[1].text = 'REDACTED'
} catch (ReadOnlyPropertyException e) {
    println 'Sorry, you can\'t change a record in a final report'
}

//This will fail with java.lang.UnsupportedOperationException
try {
    report.records << new Record(title: 'Record Z', text: 'You just got hacked')
} catch (UnsupportedOperationException e) {
    println 'Sorry, you can\'t add a record to a final report'
}

println report

You can easily see that the code for my FinalReport and Record classes has been cut right back. This is really helpful in many situations but @Immutable can’t do everything so make sure you read the documentation.

Copying and cloning

Just wandering a little off the final path, let’s take a quick look at how we could defensively handle mutable objects. Defensively copying basic objects such as String and Integers is easy as it happens at assignment time:

Integer i = new Integer(10)
Integer j = i
i = 20
assert !i.is(j)

This works because i = 20 causes i to be assigned a new instance of Integer. Similarly, I can copy a list of numbers quite easily:

def yourList = [2, 4, 6]
def myList = [*yourList]

assert myList == yourList
assert !myList.is(yourList)

This is all reasonably straight-forward as I’m only dealing with basic objects. However, how do I defensively copy an object that consists of several properties/fields? Earlier, I re-programmed the Record class to make all of the properties final and this meant that I didn’t really need to defensively copy instances. Sometimes I don’t get that option, especially for existing or third-party developed classes. In such cases I have a few options:

  1. Don’t copy the whole instance, just extract the fields I actually need and copy them
  2. Create a new instance using the object’s current state as input
  3. Call the clone() method if one exists.

The second option is possible if I can use the object’s properties to create another instance via the constructor and/or setters:

Use the object’s state to instantiate a copy
import groovy.transform.Canonical

@Canonical
class Assignment {
    final String studentName
    String answers
}

@Canonical
class SubmissionSystem {
    Map submissions = [:]

    def submitAssignment(Assignment sub) {
        if (!submissions.get(sub.studentName)) {
            submissions.put sub.studentName, new Assignment(sub.studentName, sub\
.answers)
        }
    }
}

Assignment myAssignment = new Assignment('Fred Nurk', 'I have no idea')

SubmissionSystem system = new SubmissionSystem()
system.submitAssignment(myAssignment)
println system

myAssignment.answers = 'A really good set of answers'
println system

In the code above the submitAssignment method calls the Assignment constructor to create a new instance. This helps make sure that the student can’t mysteriously change their answers after submitting. You can see that it’s a pretty simple example and a more complex classes will make this very difficult, especially if they have internal state that is hard to reach.

The third option is to have a class implement the Cloneable interface. If a third-party class provides this then you’re in luck and can make a copy (clone):

Using Cloneable
import groovy.transform.Canonical

@Canonical
class Assignment implements Cloneable {
    final String studentName
    String answers

    @Override
    protected Assignment clone() throws CloneNotSupportedException {
        return new Assignment(studentName, answers)
    }
}

@Canonical
class SubmissionSystem {
    Map submissions = [:]

    def submitAssignment(Assignment sub) {
        if (!submissions.get(sub.studentName)) {
            submissions.put sub.studentName, sub.clone()
        }
    }
}

Assignment myAssignment = new Assignment('Fred Nurk', 'I have no idea')

SubmissionSystem system = new SubmissionSystem()

system.submitAssignment(myAssignment)
println system

myAssignment.answers = 'A really good set of answers'
println system

As Assignment provides a clone method we just need to call it and we’re returned a copy for our own use. Naturally, this doesn’t help us if the author of Assignment doesn’t provide us with a clone method. Check out the Useful Annotations chapter for the @Canonical annotation.

Final classes and methods

The final modifier can also be used against class and method declarations. We’ll look into this in the chapter on Final Classes