Lists Review and Performance : 03/31/2023
Linked Lists : 03/30/2023
Algorithms and Lists : 03/29/2023
Lambda Expressions : 03/24/2023
Anonymous Classes : 03/23/2023
Practice with Interfaces : 03/22/2023
Implementing Interfaces : 03/21/2023
Using Interfaces : 03/20/2023
Working with Exceptions : 03/10/2023
Throwing Exceptions : 03/09/2023
Catching Exceptions : 03/08/2023
References and Polymorphism : 03/07/2023
References : 03/06/2023
Data Modeling 2 : 03/03/2023
Equality and Object Copying : 03/02/2023
Polymorphism : 03/01/2023
Inheritance : 02/28/2023
Data Modeling 1 : 02/27/2023
Companion Objects : 02/24/2023
Encapsulation : 02/23/2023
Constructors : 02/22/2023
Objects, Continued : 02/21/2023
Introduction to Objects : 02/20/2023
Compilation and Immutability : 02/17/2023
Practice with Collections : 02/16/2023
Maps and Sets : 02/15/2023
Lists and Type Parameters : 02/14/2023
Imports and Libraries : 02/13/2023
Multidimensional Arrays : 02/10/2023
Practice with Strings : 02/09/2023
null : 02/08/2023
Algorithms and Strings : 02/07/2023
Strings : 02/06/2023
Functions and Algorithms : 02/03/2023
Practice with Functions : 02/02/2023
More About Functions : 02/01/2023
Errors and Debugging : 01/31/2023
Functions : 01/30/2023
Practice with Loops and Algorithms : 01/27/2023
Algorithms : 01/26/2023
Loops : 01/25/2023
Arrays : 01/24/2023
Compound Conditionals : 01/23/2023
Conditional Expressions and Statements : 01/20/2023
Operations on Variables : 01/19/2023
Variables and Types : 01/18/2023
Welcome to CS 124 : 01/17/2023
Constructors
class Dimensions {
val width: Double
val height: Double
constructor(setWidth: Double, setHeight: Double) {
width = setWidth
height = setHeight
}
fun area(): Double {
return width * height
}
}
val room = Dimensions(8.8, 10.0)
println(room.area())
Let’s continue our discussion of Kotlin objects.
Remember that bit of syntax that looked like a method call when we create a new
Kotlin object?
Well, it was!
Next we’ll talk about what it does.
Each time an instance of a Kotlin class is created, code is run as part of a special function called a constructor.
The best way to see this is to examine a different syntax for class declaration:
class Room {
var name: String = ""
var width: Double = 0.0
var height: Double = 0.0
}
val livingRoom = Room()
println(livingRoom.width)
println(livingRoom.name)
livingRoom.name = "Living Room"
println(livingRoom.name)
Beginning with the empty constructor class syntax shown above, we can now explicitly declare a constructor method using the following syntax:
class Room {
var name: String = ""
var width: Double
var height: Double
constructor(setWidth: Double, setHeight: Double) {
println("I run every time a Room is created!")
width = setWidth
height = setHeight
}
}
// Now we have to pass setWidth and setHeight to the constructor
val livingRoom = Room(8.0, 10.0)
val diningRoom = Room(10.0, 12.0)
However, this pattern of using the constructor to set initial property values is so common that Kotlin provides a shortcut—which we’ve already been using!
Let’s see how:
class Room {
var name: String = ""
var width: Double
var height: Double
constructor(setWidth: Double, setHeight: Double) {
println("I run every time a Room is created!")
width = setWidth
height = setHeight
}
}
// Now we have to pass setWidth and setHeight to the constructor
val livingRoom = Room(8.0, 10.0)
val diningRoom = Room(10.0, 12.0)
As shown above, Kotlin provides us with two different ways to declare properties on our classes.
First, we can add them to the primary constructor prefaced with val
or var
:
class Person(val name: String, var age: Double)
val geoff = Person("Geoff", 41.0)
geoff.age = 42.0 // Happy birthday!
In this case we must provide the name (String
) and age (Double
) every time we create a Person
.
Alternatively, we can declare the fields inside the class declaration and omit the primary constructor:
class Person {
var name: String = ""
var age: Double = 0.0
}
val geoff = Person()
geoff.name = "Geoff"
geoff.age = 42.0
In this case we don’t provide the name or age when we create a Person
, but each Person
still has a name
and age
property.
In contrast to the example above we also must provide default values for each field, since Kotlin needs to know how to set them since they are not set when the instance is created.
Finally, in this case it usually makes less sense to declare the fields as immutable using val
, since to store data in them we will need to change them after the instance is created.
We can also mix these two approaches:
class Person(val name: String) {
var age: Double = 0.0
}
val geoff = Person("Geoff")
geoff.age = 42.0
When if we want to make sure that a person’s age is always positive?
We’d like to continue to use the primary constructor syntax, since it is compact and elegant:
class Person(val name: String, var age: Double)
However, we also need to run some additional code when the Person
is created to make sure that the age
is positive.
To do this we can use an initializer block:
class Person(val name: String, var age: Double) {
init {
require(age >= 0.0) { "People can't have negative ages" }
}
}
val geoff = Person("Geoff", 42.0)
val justWrong = Person("Bad", -1.0)
init
blocks are run every time any constructor is called, and in the order in which they appear in the class.
You can put any code you want in them, and they can be used to set properties during creation and perform other tasks.
Finally, sometimes we want more than one way to create an instance of a class.
For our Person
class, perhaps we want to make the age
optional and have it be zero by default.
Kotlin provides two ways of doing this.
First, we can use both a primary constructor and one or more secondary constructors:
class Person(val name: String, var age: Double) {
constructor(name: String) : this(name, 0.0)
init {
require(age >= 0.0) { "People can't have negative ages" }
}
}
val geoff = Person("Geoff", 42.0)
val newPerson = Person("New")
println(newPerson.age)
Note that we now have two constructors: one that takes two arguments (a name and an age) and a second that only takes a name.
This is due to the secondary constructor declared as constructor(name: String) : this(name, 0.0)
.
Secondary constructors are declared using the constructor
syntax we saw above.
But, when a primary constructor exists a secondary constructor must call it using this
and pass all of the required fields, as shown above.
However, a better alternative is usually to use default parameter values for the primary constructor:
class Person(val name: String, var age: Double = 0.0) {
init {
require(age >= 0.0) { "People can't have negative ages" }
}
}
val geoff = Person("Geoff", 42.0)
val newPerson = Person("New")
println(newPerson.age)
We have not discussed default parameter values for Kotlin methods yet, but we will soon.
This approach works for any Kotlin method, including constructors, and allows us to provide default values for any and all required parameters to a method or function.
We’ll return to this, but for now simply observe that it eliminates the need for the secondary constructor.
More Practice
Need more practice? Head over to the practice page.