77. Methods

Methods were covered in their own section and we just looked at getters/setters so you’d think that we’ve covered it all. Well, there’s a bit more to say about methods in Groovy.

In The basics of OO I touched on instance and class elements:

  • Instance elements are properties/fields and methods that relate to a specific instance of a class
    • These are also referred to as member variables and member methods/functions
  • Class elements are properties/fields and methods that relate to the class itself - not a specific instance

In the code below I have two instance properties (name and email) and an instance method (getContact())

getContact is an instance method
class Person {
    String name
    String email

    String getContact() {
        "$name <$email>"
    }
}

Person jenny = new Person(name: 'Jenny', email: 'jen@example.com')
println jenny.getContact()

Person dave = new Person(name: 'Dave', email: 'dave@example.com')
println dave.getContact()

When we look at the two instances of People (jenny and dave) we can see that the call to getContact() is specific to each instance of the class. This means that our request for jenny’s contact information doesn’t return us dave’s details.

There are times when we need to make sure we’re specific when referring to instance elements within a class. In order to do this, we use the this keyword:

Using this
class Person {
    String name
    String email

    void setName(name) {
        this.name = name
    }

    String getContact() {
        "$name <$email>"
    }
}

Person jenny = new Person()
jenny.setName('Jenny')
jenny.setEmail('jen@example.com')
println jenny.getContact()

In the example above I have provided a setName(name) method and set the instance’s name property using this.name = name. This example is demonstrating a primary use of this: to delineate between the method parameter and the instance property. this can be used for both member variables (e.g. this.email) and when calling member methods (this.setEmail()) but is limited to elements within the class - it can’t be called from outside the class (e.g. jenny.this.setName('Jenny')).

Overloading

I’ve deferred a really useful method feature for this section: method overloading. Overloading allows a class to declare a method with the same name more than once, provided each version of the method has different parameters.

In the example below I provide two implementations of the exercise method: one that takes no parameters and another that takes one parameter (duration).

A basic example of overloading
class Person {
    String name

    def exercise() {
        println 'I am exercising but I\'m not sure for how long'
    }

    def exercise(duration) {
        println "I am exercising for $duration minutes"
    }
}

Person sam = new Person(name: 'Sam')

sam.exercise()
sam.exercise(10)

Whilst my example has just two versions of the exercise method, I could keep adding more (def exercise(duration, location), def exercise(duration, location, activity)) to cover all of the cases I need. In many cases I’ll need a baseline of functionality and, in order to save me writing this functionality into each version of exercise, I can call from one method to another:

One method calling its cousin
class Person {
    String name

    def exercise() {
        //Generate a random number using java.util.Random
        Random random = new Random()

        //Use nextInt to get a random number between 0 and 120
        exercise(random.nextInt(120))
    }

    def exercise(duration) {
        println "I am exercising for $duration minutes"
    }
}

Person sam = new Person(name: 'Sam')

sam.exercise()

In the previous versions of exercise I haven’t used parameter types so my overloads must be differentiated by the number of parameters for each version of the method. However, if I provide specific parameter types I can have several variations of a method, differentiated by the parameter types:

One method calling its cousin
class Person {
    String name

    def exercise() {
        println 'I am exercising but I\'m not sure for how long'
    }

    def exercise(Integer duration) {
        println "I am exercising for $duration minutes"
    }

    def exercise(String activity) {
        println "I think I'll go for a $activity"
    }

    def exercise(String activity, Integer duration) {
        exercise(activity)
        exercise(duration)
    }
}

Person sam = new Person(name: 'Sam')

sam.exercise()
sam.exercise(10)
sam.exercise('walk')
sam.exercise('jog', 30)

Whilst I could have written another exercise method with exercise(Integer duration, String activity) I have already provided exercise(String activity, Integer duration) so such an addition would, at best, be redundant but could also be confusing if each variation did something functionally different. For example, say exercise(Integer duration, String activity) were to be written as:

def exercise(Integer duration, String activity) {
    println "I'll go for a $activity in $duration minutes"
}

I can technically do this as the parameters are different to exercise(String activity, Integer duration) but you can see that my intent is different to the intent seen in the other exercise methods. Avoid this type of coding - it really is a trap. Overloading best works when you have the same functionality that can work with different parameters. Using overloading to provide different functionality, depending on parameters, is likely to trip you up. Instead of overloading with that last example, I would have given the method a different signature that better reflects what the method is doing: delayExercise(Integer duration, String activity).

Default parameter values can make this confusing

Try the code below in a groovyConsole:

A basic example of overloading
class Person {
    String name

    def exercise() {
        println 'I am exercising but I\'m not sure for how long'
    }

    def exercise(duration = 10) {
        println "I am exercising for $duration minutes"
    }
}

Person sam = new Person(name: 'Sam')

sam.exercise()
sam.exercise(10)

Unfortunately that example won’t even run - Groovy reports that method "java.lang.Object exercise()" that is already defined. It’s not always clear where this clash lays - after all, I have explicitly written two versions of exercise, each with a different parameter list. However, my use of a default value for a parameter (exercise(duration = 10)) is implicitly defining two versions of the exercise method:

  1. exercise() - this is what clashes with the explicitly declared exercise()
  2. exercise(duration)

In larger classes this can get a little confusing so, when it happens to you, start looking at the overloaded methods with default parameter values.

Overloading != Overriding

Before we move on, it’s worth highlighting two pieces of terminology:

  • Overloading is when a class is defined with methods with the same name but different parameters to others within the class or its superclass(es).
  • Overriding is when a subclass redefines a method from its superclass. We’ll see this in the chapter on Inheritance

When we looked at Operator Overloading we saw that we could write a plus method that aligns with the + operator. The overloading aspect indicates that the functionality for the operator (+) is being defined for certain operands and this is using different parameter types in overloading.