Constructors¶
Consider the following class for representing a person:
class Person {
String name;
int age;
void print() {
println("Name: " + name);
println(" Age: " + age);
}
}
You can use it like this:
Person a = new Person();
a.name = "Ada";
a.age = 31;
a.print();
// Name: Ada
// Age: 31
If you forget to assign initial values to a.name
and a.age
you get
this:
Person a = new Person();
a.print();
// Name: null
// Age: 0
This doesn’t really make sense for a person, and so we never want Person
object to have unassigned values like this.
A good way to avoid this problem is to use a constructor. A constructor is a special function that initializes an object:
class Person {
String name;
int age;
Person(String name_init, int age_init) { // constructor
name = name_init;
age = age_init;
}
void print() {
println("Name: " + name);
println(" Age: " + age);
}
}
Now we can write code like this:
Person a = new Person("Ada", 31);
a.print();
This lets us initialize the name and age at the same time as we construct it. It also stops us from forgetting to accidentally initialize a person, e.g.:
Person a = new Person(); // compiler error!
a.print();
We are no longer allowed to create a Person
object by calling new
Person()
because that expression expects to find a constructor in the
Person
class that takes no parameters. But there is no such constructor:
the Person
constructor needs a name and age as input.
It might seem annoying that this is now an error, but it stops us from making
a subtle mistake. As we saw above, the default Person
object makes no
sense, and so this constructor forces us to always pass in initial data.
Of course, nothing stops us from doing something like this:
Person a = new Person("", -31);
a.print();
This runs, but a person can’t have the empty string as a name, and their age can’t be negative.
We can deal with that kind of error by we can check the initial data in the constructor like this:
class Person {
String name;
int age;
// constructor
Person(String name_init, int age_init) {
if (name_init.length() == 0) { // make sure name is
println("Error: empty name"); // not the empty string
exit();
}
if (age_init <= 0) { // make sure age is positive
println("Error: age must be positive");
exit();
}
name = name_init;
age = age_init;
}
void print() {
println("Name: " + name);
println(" Age: " + age);
}
}
We’ve added two if-statements to the constructor. The first ensures that
name
is not the empty string. If it is empty, then it prints an error
message and ends the program by calling exit()
. The second if-statement
ensures that the age is positive, and, if it’s not, ends the program with an
error message.
Now we get an error when the program runs if we try to initialize it with bad data:
Person a = new Person("", 31); // run-time error
a.print();
Or:
Person a = new Person("Ada", -31); // run-time error
a.print();
This is an example of data validation, i.e. we validate the data passed into an object to ensure it makes sense. This helps catch and prevent errors.
Here are a couple of important details about constructors that make them different from other functions:
- The name of a constructor is always the name of its class.
- Constructors don’t have a return type (not even
void
).
A Point Class¶
Here’s another example of how to use a constructor. Here we’ve created a class
called Point
that stores an (x, y) point:
class Point {
float x;
float y;
Point() { // constructor
x = 0;
y = 0;
}
Point(float x_init, float y_init) { // constructor
x = x_init;
y = y_init;
}
Point(Point other) { // copy constructor
x = other.x;
y = other.y;
}
void print() {
println("(" + x + ", " + y + ")");
}
}
This class has three constructors, Point()
, Point(x, y)
, and
Point(other)
. Any of them can be used to create a Point
object:
Point origin = new Point();
Point p = new Point(3, 4);
Point q = new Point(p);
origin.print(); // (0, 0)
p.print(); // (3, 4)
q.print(); // (3, 4)
The Point(other)
constructor is called a copy constructor because it
makes a copy of an object.
In general, a class can have as many constructors as it needs, so long as the parameter lists are different for each constructor.
A Timer Class¶
Our final example of using constructors is in a handy class called Timer
that we can, among other things, use to control the speed of animations.
Here is the Timer
class:
class Timer {
int startTime;
Timer() { // constructor
reset();
}
void reset() {
startTime = millis();
}
int elapsedMillis() {
return millis() - startTime;
}
}
You could use it like this:
Timer timer;
void setup() {
timer = new Timer();
}
void draw() {
if (timer.elapsedMillis() > 1000) {
println("1 second has passed");
timer.reset();
}
}
You can think of timer
as being similar to a real stop-watch. When it is
created, or when it is reset, it is set to “0”. Then when
timer.elapsedMillis()
is called, the number of milliseconds since the last
reset is returned.
So, in this program, after 1000 milliseconds (which equals 1 second) has passed, we print a message and also reset the timer so that the message will be printed again and again, forever.
Timers are very useful in animation. Below we provide two example programs:
- A program that draws a random rectangle on the screen every second.
- A program that draws a “jiggling” square, that quickly rotates back and forth. A timer is used to decided when the rotation changes direction.
Random Rectangle Source Code¶
Timer timer;
void setup() {
size(500, 500);
timer = new Timer();
background(255);
}
void draw() {
// draw a new random rectangle every second
if (timer.elapsedMillis() >= 1000) {
drawRandomRect();
timer.reset();
}
}
void drawRandomRect() {
pushMatrix();
noFill();
stroke(random(256),random(256),random(256));
translate(random(width), random(height));
rotate(radians(random(360)));
rect(0, 0, random(5, 200), random(5, 200));
popMatrix();
}
class Timer {
int startTime;
Timer() { // constructor
reset();
}
void reset() {
startTime = millis();
}
int elapsedMillis() {
return millis() - startTime;
}
}
Jiggling Square Source Code¶
Sprite box;
float angle; // angle of the box
float dA; // rate of change of angle
Timer timer;
int changeMillis;
void setup() {
size(500, 500);
rectMode(CENTER);
box = new Sprite();
box.x = 250;
box.y = 250;
angle = 0;
dA = 0.5;
timer = new Timer();
changeMillis = 100;
}
void draw() {
background(255);
pushMatrix();
translate(box.x, box.y);
rotate(radians(angle));
fill(255, 0, 0);
noStroke();
rect(0, 0, 200, 200);
popMatrix();
if (timer.elapsedMillis() >= changeMillis) {
dA = -dA;
timer.reset();
}
angle += dA;
}
class Sprite {
float x;
float y;
float dx;
float dy;
void update() {
x += dx;
y += dy;
}
}
class Timer {
int startTime;
Timer() { // constructor
reset();
}
int elapsedMillis() {
return millis() - startTime;
}
void reset() {
startTime = millis();
}
}
Questions¶
- Mark each of the following statements as either true or false:
- The return type of a constructor is
void
. - A constructor must always have the same name as the class it is in.
- You can call a constructor on an object as many times as you like.
- A class can have multiple constructors.
- If you don’t create a constructor for a class, then Processing automatically adds a constuctor that takes no parameters and does nothing.
- The return type of a constructor is
- What does the
millis()
function return? - What is a copy constructor? Give an example of one.