Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Methods

Methods, also known as member functions, are functions defined inside the body of a class. They have implicit access to the properties of a class, by virtue of being defined within the same scope as those properties.

The syntax for defining a method is basically the same as that for standalone functions. You can write a method with a block body or expression body, as you prefer. The method can accept input via its parameter list and return values to the caller in the same way as a standalone function.

Task 12.4.1

This task will give you more practice at implementing methods. The code is provided as an Amper project, in the subdirectory tasks/task12_4_1 of your repository.

  1. Examine Point.kt, in the src subdirectory of the project.

  2. Edit Circle.kt. In this file, implement a new class named Circle. Give your class two val properties: centre, a Point object representing coordinates of the circle’s centre; and radius, a Double value representing the circle’s radius.

    Check that project code compiles with

    ./amper build
    

    Repeat this after each of step of developing Circle.

  3. Add an init block to Circle that guarantees radius will be greater than zero when a Circle object is created.

  4. Add methods area() and perimeter() to Circle. These methods should have no parameters. They should return the area and perimeter, respectively, of the circle.

    Area and perimeter can be computed using the formulae \( \pi r^2 \) and \( 2\pi r \), respectively. The constant \( \pi \) can be imported as the name PI, with

    import kotlin.math.PI
    
  5. Add a method contains() to Circle. This should have a single parameter, of type Point. It should return true if the given point is inside the circle, false otherwise. The point should be considered inside if the distance from it to the centre of the circle is less than or equal to the radius.

    If you like, use infix when defining the method, so that it can be invoked like this:

    if (circle contains point) {
        ...
    }
    
  6. Edit Main.kt. In this file, write a program that creates a Circle object and then demonstrates the use of its three methods.

    Run the program with

    ./amper run
    

Overriding

One feature of methods that they don’t share with standalone functions is their ability to override (i.e., substitute for) other methods, or be overridden themselves. We will consider a simple example of this here, deferring more detailed discussion to when we cover inheritance.

All classes in Kotlin inherit implicitly from a superclass named Any. This provides all Kotlin classes with certain capabilities, one of them being the ability to generate a string representation of an object, via the toString() method. The default representation is generic and not particularly useful, so it is common to override toString() with a version that yields something better1.

Note: toString() is called automatically on any object you pass to print() and println(), so overriding it can be very useful for tasks like debugging.

Task 12.4.2

  1. Edit Point.kt, in the tasks/task12_4_2 subdirectory of your repository. Add a main() function that creates a Point object and then calls println() on that object.

    Compile and run the program. What do you see?

  2. Add a new method to the Point class:

    override fun toString() = "($x, $y)"
    
  3. Recompile the program and run it again. What has changed?

Important

You must use the override keyword when overriding a method, and the new version must have the same signature as the version being overridden.

Also, note that methods must grant explicit permission to be overridden in subclasses. The example above works because the Any superclass grants that permission for toString(). We will discuss this further when we cover inheritance.

Method or Extension Function?

Almost all of the methods we’ve seen so far could have been written instead as extension functions. For example, Point could have been implemented like this:

class Point(var x: Double, var y: Double)

fun Point.distance() = hypot(x, y)
fun Point.distanceTo(p: Point) = hypot(x - p.x, y - p.y)

From the perspective of users of Point, there is no discernable difference between this version of the class and the version with methods rather than extension functions.

But methods have some unique advantages. As we’ve seen already, they support overrriding, whereas extension functions do not.

Another advantage is that methods have more privileged access to members of a class. A method can make use of anything defined in its class, including private members. By contrast, an extension function only has access to the public API of the class.

Extension functions have their uses, though. If you don’t have access to the source code of a class, and that class prevents inheritance, then extension is the only way in which you can add new functions to that class.


  1. You may remember doing something similar for Python. Python classes have inherited ‘dunder’ methods, much like Kotlin classes. Overriding the __str__ method in a Python class is the equivalent of overriding toString() in a Kotlin class.