The Basics
Let’s begin with an example of the simplest possible class definition in Kotlin:
class Point
We can create an instance of this class (i.e., a Point object)
like so:
val p = Point()
This invokes the default constructor of the Point class.
In this case, the default constructor does nothing. Indeed, the class itself
is essentially useless. To make it useful, we need to add some Kotlin code
to Point that represents the attributes or behaviour associated with points.
Let’s assume that Point is supposed to represent a point in 2D space.
Its attributes will therefore be the \(x\) and \(y\) coordinates of that
point. We represent these as properties of the class. We can define and
initialize these properties like so:
class Point {
val x = 0.0
val y = 0.0
}
You can see here that properties look just like variables, but defined inside a class.
The code to initialize x and y to zero is executed as part of the
object construction process. After creating a Point object, we can access
its x and y properties with the ‘dot’ operator:
val p = Point()
println(p.x) // prints 0.0
println(p.y) // prints 0.0
Let’s try this out.
-
In the
tasks/task12_1subdirectory of your repository, create a filePoint.ktcontaining the simplePointclass definition shown above. -
Add a
main()function containing code to create aPointobject and print the values of itsxandyproperties. Compile and run the program. -
Add the following line to
main(), immediately after the line that creates thePointobject:p.x = 1.0What happens when you try to recompile?
-
Change the property definitions to use
varrather thanval. Verify that this fixes the problem.
val is used to define read-only properties in a class.
If you need the ability to assign a new value to a property after an object
has been created, you need to define that property using var.
Writing a Constructor
You should now have a class definition like this:
class Point {
var x = 0.0
var y = 0.0
}
This is the first genuinely useful version of the Point class. However,
the process of representing a point at any location other than the origin is
inconvenient:
val p = Point()
p.x = 4.5
p.y = 7.0
It would be better if we could supply the desired values for x and y
as part of the object construction process. We achieve this by adding an
explicit primary constructor to the class:
class Point constructor(x: Double, y: Double) {
var x = x
var y = y
}
The constructor keyword introduces this new constructor. It is followed by
a parameter list, specifying that two Double values must be provided to
create a Point object. These values are then assigned to the x and y
properties.
The compiler isn’t bothered by the repeated use of x and y here.
It knows that the usage of x and y on the right-hand side of the
assignments refers to the constructor parameters and not the properties.
If you find it confusing, you can use different names for the constructor parameters.
Let’s try this out now.
-
Return to
Point.kt. Modify the definition of thePointclass so that it looks like the one above, but don’t changemain().What happens when you try to compile the code?
-
To fix the issue, modify
main()so that coordinate values are supplied when creating the object. Check that the program now compiles and runs as expected.
If you write a primary constructor, this becomes the expected way of creating an instance of the class. We’ll consider how you can support different ways of constructing an object later.
Compact Syntax
The need to define some class properties and a constructor that assigns values to those properties is so frequent that Kotlin provides a much more compact syntax for doing this.
The class definition
class Point constructor(x: Double, y: Double) {
var x = x
var y = y
}
Can be written more concisely as
class Point(var x: Double, var y: Double)
When you see a class definition with a primary constructor, take a close
look at its parameter list. Each item in the list represents a value that
must be provided when creating an object. If you see val or var in
front of an item then that item also specifies a property of the class.
Let’s try this out.
-
Return to
Point.kt. Modify the class definition by removing the keywordconstructor:class Point(x: Double, y: Double) { var x = x var y = y }Recompile the program and run it, to verify that behaviour is unchanged.
-
Now merge the property definitions into the constructor parameter list so that the entire class definition is a single line of code, as shown above.
Recompile and run, to verify that behaviour is unchanged.
It is common to see classes defined in this very concise way. It may feel quite strange at first, particularly if you are used to the more verbose class definitions of other programming languages.
The best way of adjusting to it is to get lots of practice at writing small classes.
Mutable or Immutable?
Consider these two different versions of the Point class:
class Point(var x: Double, var y: Double) // mutable
class Point(val x: Double, val y: Double) // immutable
One is mutable, meaning that it is possible to give a Point object a
different value for x or y after creation. The other is immutable,
meaning that the property values are fixed when the Point object is
created and cannot change thereafter.
Despite the restrictions, you shouldn’t think of the immutable version as being necessarily inferior. Anything you can do with the mutable version can still be achieved with the immutable version, albeit at the cost of creating an additional object.
For example, here’s how you could create a point and then translate it by
10 units along the \(x\) axis, using both versions of the Point class:
// If Point is mutable:
val p = Point(x, y)
p.x += 10.0
// If Point is immutable:
var p = Point(x, y)
p = Point(p.x + 10.0, p.y)
Immutable types have some advantages, too. For one thing, they are safer to use in multi-threaded applications, because race conditions cannot occur when two threads share access to the same variable.
Adding a Method
It would be useful if a Point had a way of telling us how far it was from
the origin. We can implement this as a method or member function
of the class.
The distance of a point \( (x, y) \) from the origin is \[ d = \sqrt{x^2 + y^2} \]
We can use the hypot() function from the standard library to perform
this calculation. Our new method simply needs to call this function.
Here is the complete implementation of the class:
import kotlin.math.hypot
class Point(var x: Double, var y: Double) {
fun distance() = hypot(x, y)
}
Notice that the method is defined in just the same way as a regular standalone function. In this case, we’ve used an expression body, but we could have also written it with a block body.
The body of the method has implicit access to properties x and y.
Finishing Task 12.1
There are three more things that you need to do to complete this extended task:
-
Modify the definition of
PointinPoint.ktso that it has adistance()method, as shown above. Then add a methoddistanceTo(). This method should accept aPointobject as its sole parameter. It should return the distance between the receiver of the method call and the specifiedPointobject.The distance between two points \( p \) and \( q \) is given by \[ d = \sqrt{(x_p - x_q)^2 + (y_p - y_q)^2} \]
You should be able to implement this in a straightforward way using the
hypot()function from the standard library, as you did for thedistance()method. -
Modify the main program so that it
- Reads an x coordinate from the user (prompting for input appropriately)
- Reads a y coordinate from the user
- Creates a
Pointobject using these coordinates - Computes and prints distance of the point from the origin
- Computes and prints the distance of the point from (4.5, 7.0)
-
Implement an equivalent class definition and program in Python, in a file named
point.py. Make sure that the class and program behave the same as the Kotlin implementation.