On Constructors

A student asked me to try to explain constructors in a more tangible way. This is the analogy I came up with: a constructor is a little like a barista in a coffee shop. Since this is just an analogy, it's not a perfect explanation. But it might help you make sense of what's going on.

For the example code, I'll use the HighScore class I talked about in lab.

Code: HighScore and it constructors Coffee shop analogy
Suppose I'm writing an arcade game. Each time a user plays, I want to record her name (or initials) and her score. The name is a String and the score is an int. Suppose I'm in a coffee shop. I've purchased a tall latte and a blueberry muffin. The latte is a kind of coffee, and the blueberry muffin is a kind of muffin.
Now, as I collect a lot of scores, it would be nice to bundle each player's name and score together in a single object. This way, I can pass their name and score around together as a single thing. I can build a list of different HighScores, each containing a score and name. If I sort this list by score, the names will automatically get sorted too, because each name is in the same object as its associated score. Carrying a coffee and a muffin around separately means my hands are full. I'd like to bundle these together into a disposable tray so I can carry them around one-handed. This would be even more useful if I also want to get a coffee and muffin for a friend. If I hand the tray to someone, they'll get both the coffee and the muffin that's stored in the tray.
So an Java object is like a container (tray) that holds certain kinds of data (food items).
I can write a very simple HighScore class to do this:
public class HighScore {
  String name;
  int score;
}
Every instance of this class will hold one String value and one int value.
I need a little cardboard tray with a slot for my coffee and a slot for my muffin. Each tray has the same shape. Each tray will hold one coffee and one muffin.
So, conceptually, I can now build a couple objects:
HighScore[name="AAA", score=1000]
HighScore[name="ZMT", score=500]
(This is not code; it is just a depiction of how I'm bundling my data into objects.)
Then I could load up a number of such trays with different contents:
Tray[coffee=Latte, muffin=Blueberry]
Tray[coffee=Cappucino, muffin=Cranberry]
To build an object, I must call a constructor. So every class must have at least one constructor in it, though it can have more than one. To get a hold of one of these trays, I need to ask a barista to give me one from behind the counter. Every coffee shop should have at least one barista, though it can have more than one.
So a Java constructor is like a coffee-shop barista. It is a sort of gate-keeper that determines how I can build a new object (get a new tray).
If I don't write a constructor, the Java compiler will insert a default constructor for me that looks like this:

public class HighScore {
  String name;
  int score;

  public HighScore() { 
  }
}
This constructor will build an empty object. It takes no parameters and does nothing.
A standard barista will hand me an empty tray if I ask.
So, assuming my class has only this inserted default constructor, I would build a HighScore object like this in my main method:

public class HighScore {
  String name;
  int score;
}


public class ArcadeGame {
  public static void main(String[] args) {
    HighScore hs = new HighScore();
    hs.name = "ZMT";
    hs.score = 500;
  }
}
"Excuse me, barista. Could you give me an empty tray for my coffee and muffin here? Ah, thank you."

I put my latte cup into the coffee slot and my blueberry muffin into the muffin depression.
Note how I had to load the new object with data myself because the constructor didn't do it for me. This is tedious if I want to make a lot of objects. Note how I had to load my tray with my food items myself because the barista didn't do it for me. Maybe I'd like a higher level of service.
I could write my own HighScore constructor to do the work of loading a new object for me while it is being constructed. After writing such a constructor, my HighScore class looks like this:

public class HighScore {
  String name;
  int score;
 
  public HighScore(String n, int s) {
    this.name = n;
    this.score = s;
  }
}
I could find a friendly, eager barista that wants to do my work for me.
Now I would use this constructor to build an object like this in main:

public class ArcadeGame {
  public static void main(String[] args) {
    HighScore hs = new HighScore("ZMT", 500);
  }
}
Let's trace what happens here.
"Excuse me, barista. Could you put my coffee and muffin into a tray for me?"
So I am passing two values to the constructor: "ZMT" and 500. The HighScore constructor parameter n gets initialized to "ZMT". The parameter s gets initialized to 500. I hand my coffee and muffin to the barista. He takes the coffee with his left hand and the muffin with his right hand.
Now every constructor automatically creates an empty object. Then it executes any code in that constructor. So we execute these lines:
    this.name = n;
    this.score = s;
in order to store the two values in the object itself. That is, we need to copy the values from the parameters into the instance variables.
The barista gets out an empty tray. He places the coffee that is in his left hand into the coffee slot of the tray. He places the muffin that is in his right hand into the tray's muffin depression.
So, after the constructor finishes running, back in main I have a HighScore object stored in the variable hs. In that object is stored "ZMT" and 500. The barista hands me my tray that now contains my latte and blueberry muffin.

Hopefully that helps a little!