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

  1. 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.
  2. What does the millis() function return?
  3. What is a copy constructor? Give an example of one.