Constructors
Building objects in a valid state from the first moment, default vs custom constructors, and overloading them.
Finished reading?
Mark this session so you can track where you are.
Building objects in a valid state from the first moment, default vs custom constructors, and overloading them.
Finished reading?
Mark this session so you can track where you are.
You can build an object and you can give it behavior. But every object so far has been born empty and then dressed by hand, one field at a time, hoping you did not forget one. This session is about the part of Java that fixes that: a small piece of setup code that runs the instant an object is created, so the object is correct from its very first breath.
this.name = name: which name is which, and why the dot is needed.new in order: allocate, run the constructor, hand back.new, and give them methods that take parameters and return values. A constructor is going to look a lot like a method, so keep parameters and this fresh in mind. We will not touch inheritance yet, so there is no super here, only the one class in front of us.Here is a Student class with three fields. We make one student and try to set them up by hand, the only way we know so far.
public class Main { public static void main(String[] args) { Student s = new Student(); s.name = "Mara"; s.age = 19; System.out.println(s.name + ", age " + s.age + ", gpa " + s.gpa); }} class Student { String name; int age; double gpa;}Mara, age 19, gpa 0.0
Read the output carefully. It prints Mara, age 19, gpa 0.0. The gpa is 0.0because we forgot to set it, and Java did not stop us. When you write new Student() with no setup, every field starts at a built-in default value: 0 for an int, 0.0 for a double, false for a boolean, and null for any object type like String. The object exists, but it is half-finished, and nothing reminded us.
Now scale this up. Three fields is easy to remember. A real Student might have a name, an age, a gpa, a student number, an email, and a major. Every time anyone, anywhere in the program, makes a student, they must remember to set all six in the right order, with the right types, and never miss one. The first time someone forgets, you have a broken student wandering through your program looking completely normal.
Setting fields by hand after
newis a promise you make every single time, and a promise is only as strong as the most tired person making it at 2am.
The fix is to attach the setup to the class itself, so it cannot be skipped. That piece of setup is called a constructor. It is a special block of code that Java runs automatically as part of new, before the new object is handed back to you. Here is the same Student, now with a constructor.
public class Main { public static void main(String[] args) { Student s = new Student("Mara", 19); System.out.println(s.name + " is " + s.age); }} class Student { String name; int age; Student(String name, int age) { this.name = name; this.age = age; }}Mara is 19
Press Run and step through. The interesting moment is new Student("Mara", 19). Watch the constructor run with name set to "Mara" and age set to 19, watch those values land in the object's fields, and only then does s come back pointing at a complete student. There was never a moment where s existed with an empty name. It was correct from the start.
A constructor looks almost like a method, with two deliberate differences. Spot them in the code above.
Student is named Student, with the same capital letter. This is how Java recognizes it.void, not int, nothing. A method like void greet() has void in front; a constructor has nothing in front of its name. It does not return a value to you, it sets up the object.greet.Student.void, int, String.void.new.The line this.name = name stops a lot of beginners cold, because the word name appears twice and they look identical. They are not the same thing. Let us slow all the way down.
Inside that constructor there are two separate things both called name. One is the parameter name, the value "Mara" that was just passed in. The other is the field name, the slot inside the object where the student's name is meant to live. When two things in the same place share a name, the closer one wins. The parameter is closer, so a bare name means the parameter. The field is hidden, or shadowed, by it.
this.namemeans “thenamefield of this object”. Plainnamemeans “the parameter that was passed in”. Sothis.name = namereads: take the value that was passed in, and store it into this object's own field.
The word this is how an object refers to itself from the inside. When the constructor runs for the student being born, this points at exactly that student. So this.name reaches into that one specific object and points at its name field, the same way s.name reaches in from the outside.
Yes, and people do. If you wrote the parameter as n instead of name, there would be no clash, and name = n would already mean the field. But naming the parameter after the field it fills is so common and so readable that the this.field = field pattern is everywhere in real Java. Learning to read it now saves you confusion in every codebase you ever open.
Now that a constructor is in the picture, it is worth being exact about what new Student("Mara", 19) actually does, in order. It is three steps, always the same three.
Student and fills every field with its default: name is null, age is 0. The object exists now, but it is blank.this.name = name and this.age = age run, turning the blank object into a real student.new finish and give you back a reference to the finished object, which the assignment stores in s.You can watch this order with your own eyes by printing from inside the constructor. The print between the two outer prints can only have come from step 2, in the middle of new.
public class Main { public static void main(String[] args) { System.out.println("before new"); Student s = new Student("Mara", 19); System.out.println("after new: " + s.name); }} class Student { String name; int age; Student(String name, int age) { System.out.println("constructor running"); this.name = name; this.age = age; }}before new constructor running after new: Mara
The output is before new, then constructor running, then after new: Mara. The middle line proves the constructor ran in the gap, during new, before s was ready. By the time you hold s, the setup is already done.
Go back to the very first example. It had no constructor at all, yet new Student() worked. How? When you write a class with no constructor, Java silently gives you one for free: an empty no-argument constructor that takes nothing and does nothing extra, just the allocate step with all the defaults. That free gift is why new Student() compiled in the first example.
Here is the rule that surprises people. The moment you write any constructor of your own, that free no-argument one is gone. Java only hands you the freebie when you have written none. So in the second example, where we wrote Student(String name, int age), the empty new Student() no longer exists. Trying to use it is now a compile error.
public class Main { public static void main(String[] args) { Student s = new Student(); // no longer allowed System.out.println(s.name); }} class Student { String name; int age; // Once this exists, the free no-arg constructor is gone. Student(String name, int age) { this.name = name; this.age = age; }}Student.java: error: constructor Student in class Student
cannot be applied to given types;
required: String,int
found: no arguments
Student s = new Student();
^This trips up almost everyone once. You add a constructor with parameters, and suddenly old code that said new Student() stops compiling. Nothing was deleted; the free no-argument constructor was never really yours to keep. If you still want new Student() to work, you must write the no-argument constructor yourself, by hand, alongside the others.
Sometimes there is more than one sensible way to make an object. You might know everything about a new student, or only their name, with the rest taking sensible starting values. Java lets you write several constructors for the same class, as long as they differ in their parameters. This is constructor overloading, the same idea as overloading a method by name, applied to the class name.
public class Main { public static void main(String[] args) { Student a = new Student("Mara", 19, 3.8); Student b = new Student("Tom", 20); Student c = new Student("Lin"); System.out.println(a.name + " " + a.age + " gpa " + a.gpa); System.out.println(b.name + " " + b.age + " gpa " + b.gpa); System.out.println(c.name + " " + c.age + " gpa " + c.gpa); }} class Student { String name; int age; double gpa; Student(String name, int age, double gpa) { this.name = name; this.age = age; this.gpa = gpa; } Student(String name, int age) { this.name = name; this.age = age; this.gpa = 0.0; } Student(String name) { this.name = name; this.age = 18; this.gpa = 0.0; }}Mara 19 gpa 3.8 Tom 20 gpa 0.0 Lin 18 gpa 0.0
Three constructors, all named Student, told apart by how many values they take. When you write new Student("Tom", 20), Java looks for the constructor that fits two arguments and runs that one. The output is Mara 19 gpa 3.8, then Tom 20 gpa 0.0, then Lin 18 gpa 0.0. Each student is fully set up, even the ones we gave less information, because each constructor filled in its own sensible defaults.
It matches on the arguments you give: how many, and their types. new Student("Lin") has one String, so the one-parameter constructor runs. There is no clever guessing; if no constructor matches the arguments you wrote, it simply will not compile. The set of constructors is the menu of legal ways to make a Student.
Look closely at those three constructors and a discomfort sets in. The first one sets all three fields. The second copies two of those lines and adds a default for gpa. The third copies one line and adds defaults for the other two. The setup is duplicated, and if you ever add a fourth field, you must remember to touch all three constructors. That is the same forgetting problem we started with, moved one level down.
Java offers a tidy fix: one constructor can hand off to another using this(...). Reading this(name, 18, 0.0)as the first line of a constructor means “before doing anything else, run the constructor of this same class that matches these arguments”. The full setup lives in one place, and the shorter constructors just call it with their defaults filled in.
public class Main { public static void main(String[] args) { Student a = new Student("Mara", 19, 3.8); Student b = new Student("Tom", 20); Student c = new Student("Lin"); System.out.println(a.name + " " + a.age + " gpa " + a.gpa); System.out.println(b.name + " " + b.age + " gpa " + b.gpa); System.out.println(c.name + " " + c.age + " gpa " + c.gpa); }} class Student { String name; int age; double gpa; // The one real constructor: it sets everything. Student(String name, int age, double gpa) { this.name = name; this.age = age; this.gpa = gpa; } // Hands off to the full one, supplying a default gpa. Student(String name, int age) { this(name, age, 0.0); } // Hands off again, supplying a default age and gpa. Student(String name) { this(name, 18, 0.0); }}Mara 19 gpa 3.8 Tom 20 gpa 0.0 Lin 18 gpa 0.0
The two versions above produce identical output, so we traced the spelled-out one and show this this(...)version as a read-along. The playground's interpreter does not yet handle a constructor calling this(...), and an honest read-along beats a Run button that breaks. Type it into a real Java compiler and you will get exactly the output shown.
Write the real setup once, in the constructor with the most parameters. Let the smaller constructors delegate to it with
this(...), supplying defaults. One source of truth, many convenient doors into it.
A constructor guarantees an object starts out complete. It does not yet stop someone writing s.gpa = -5.0 a moment later and ruining it. Sealing the object so it can only ever hold sane values is the job of encapsulation. And the keyword this we leaned on here has more to it, including how two variables can point at the same object; that gets its own session in the this keyword and object references. Once classes start building on other classes in inheritance, constructors gain one more move, but you do not need any of that yet.
Try each one yourself first, then open the answer.
this.age = age inside a constructor, what does each age refer to, and which value ends up stored where?new Thing() legal? Now you add Thing(int x) and write nothing else. Is new Thing() still legal?Student. Which constructor runs for new Student("Lin"), and what are age and gpa afterward?this(name, 18, 0.0) and one that just writes the three assignments itself? Does the finished object differ?Take these away. They continue exactly what we just did.
Book class with fields title (String), pages (int), and inStock (boolean). Give it one constructor that takes all three and sets them with the this.field = field pattern. In main, make two different books and print all three fields of each.Book class and add a second constructor that takes only title and pages, and defaults inStock to true. Make a book with each constructor and confirm both come out fully set up. Then try new Book() with no arguments and explain in one sentence why it does not compile.title = title instead of this.title = title. Predict what the title field will be after construction, then run it and see. Write one sentence explaining the result.Book constructors so the two-argument one delegates to the three-argument one with this(...). Mark which line must be first and why. You do not need to run it; reason it out from the rules.