Constructors
A new phone walks you through setup before you can use it: language, Wi-Fi, your name. It won't let you skip to the home screen half-configured.
In Classes and Objects, creating a student took four lines: one to build the object, and one for each field. Forget a line, and the student's name is null, waiting to crash your program later.
A constructor fixes that. It's a special block of code that runs automatically every time an object is created, so you can make sure every new object starts out complete and ready to use.
Setting up a new phone
When you turn on a brand-new phone, it doesn't drop you onto a blank home screen. It runs a setup routine first: pick your language, connect to Wi-Fi, enter your name. Only when the essentials are in place does the phone become usable.
A constructor is that setup routine. new builds the object, and the constructor immediately fills it in with the values you provide.
Writing a constructor
class Student {
String name;
int gradeLevel;
Student(String studentName, int level) {
name = studentName;
gradeLevel = level;
}
}A constructor looks like a method, with two differences:
- Its name is exactly the same as the class name, capital letter included:
Student. - It has no return type, not even
void.
Now creating a student takes one line, and you pass the values in the parentheses after new Student:
Student maria = new Student("Maria", 11);
Student ben = new Student("Ben", 10);
System.out.println(maria.name + ", grade " + maria.gradeLevel); // Maria, grade 11The arguments go to the constructor's parameters, in order, just like calling a method in Parameters and Return Values. The constructor copies them into the new object's fields.
this: the object being built
Those parameter names, studentName and level, are a bit awkward. The natural name for the parameter is name, the same as the field. But then which name is which?
Student(String name, int gradeLevel) {
name = name; // does nothing useful!
gradeLevel = gradeLevel;
}Inside the constructor, the parameter name hides the field name, so name = name just copies the parameter into itself. The field stays null.
The fix is the keyword this, which means "the object being built right now." this.name is always the field:
Student(String name, int gradeLevel) {
this.name = name;
this.gradeLevel = gradeLevel;
}Read it as "this object's name gets the name that was passed in." You'll see this pattern in nearly every Java class ever written, and static and this looks at this more closely.
The default constructor
In the last lesson, new Student() worked even though the class had no constructor at all. That's because when you don't write any constructor, Java quietly gives your class an empty one with no parameters, called the default constructor.
The moment you write your own constructor, Java stops providing that free one:
class Student {
String name;
Student(String name) {
this.name = name;
}
}Student s = new Student();
// error: constructor Student in class Student cannot be applied to given types;This is usually exactly what you want. If a student must have a name, the class shouldn't allow nameless students. The compiler now enforces that for you.
Several constructors
Sometimes there are several sensible ways to create an object. A student might be enrolled with a name and grade level, or, early in the process, with just a name. You can offer both, using the overloading you already know: same name, different parameters.
class Student {
String name;
int gradeLevel;
Student(String name, int gradeLevel) {
this.name = name;
this.gradeLevel = gradeLevel;
}
Student(String name) {
this(name, 7);
}
}The second constructor uses this(...) with parentheses, which means "call another constructor of this class." It passes the name along with a default grade level of 7. This keeps all the real setup work in one constructor, the same trick you used for default values in Method Overloading.
When you use this(...) this way, it must be the very first line in the constructor.
Student maria = new Student("Maria", 11); // grade 11
Student newKid = new Student("Ana"); // grade 7, by defaultChecking values in a constructor
Because the constructor runs every time, it's the perfect place to reject bad data before it gets into an object:
Student(String name, int gradeLevel) {
if (gradeLevel < 1 || gradeLevel > 12) {
gradeLevel = 1;
}
this.name = name;
this.gradeLevel = gradeLevel;
}Quietly fixing a bad value like this is only a start. In Exceptions, you'll learn to refuse bad data loudly instead, so the mistake gets noticed. And in the next lesson, Encapsulation, you'll make sure nobody can sneak a bad value in after the object is built, either.
Fields that start with a value
Some fields should start with the same value for every new object, like an empty list of scores. You can set those right where the field is declared:
class Student {
String name;
ArrayList<Integer> scores = new ArrayList<>();
Student(String name) {
this.name = name;
}
}Every new student gets their own fresh, empty scores list, ready for add. Without the = new ArrayList<>(), the field would start as null, and the first scores.add(...) would crash.
Try it
This program gives the Student class two constructors and a method that adds a score. It creates students both ways and prints each one. Predict the output.
Running Java here
Running Java right in the browser is coming to Syntaxia soon. On your own computer, save this program as Main.java and run it with java Main.java, or use your editor's Run button.
Check your prediction
Grade 15 isn't valid for Carlo. Using 7.
Maria (grade 11), scores: [91, 88]
Ana (grade 7), scores: [79]
Carlo (grade 7), scores: []The warning prints first, because the constructor runs the moment new Student("Carlo", 15) is reached, before any of the summaries. Ana was created with the one-parameter constructor, which passed grade 7 to the other one. And Carlo never got any scores, but his list still exists, so it prints as an empty [] instead of crashing.
Try it yourself
- Add a third constructor that takes no arguments and creates a student named
"New student"in grade 7. Usethis(...)so you don't repeat any setup code. - Remove the line
ArrayList<Integer> scores = new ArrayList<>();and replace it with justArrayList<Integer> scores;. Predict what happens whenmaria.addScore(91)runs. Then put it back. - In the two-parameter constructor, change
this.name = name;toname = name;. What doesprintSummaryshow for the names now?
Check your understanding
Up next
Your constructor checks the grade level, but anyone can still write maria.gradeLevel = 99; afterward and skip the check completely. In Encapsulation: private, Getters, and Setters, you'll lock the fields away so every change has to go through your checks.
