6.3 Constructor
When creating an object from a class, a special code block that initializes instance variables to desired values at the moment of creation is called a Constructor.
1. What Is a Constructor?
A constructor looks like a method but has a few unique characteristics.
Three Characteristics of a Constructor
- Its name must be exactly the same as the class name.
- It has no return type— not even
void. - It is automatically called exactly once when an object is created with the
newkeyword.
public class Person {
String name;
int age;
// Constructor: same name as class, no return type
Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person object created: " + name + ", age " + age);
}
}
public class PersonTest {
public static void main(String[] args) {
// The constructor runs automatically when new Person(...) is called
Person p1 = new Person("Hong Gildong", 25);
// Output: Person object created: Hong Gildong, age 25
Person p2 = new Person("Yi Sunsin", 40);
// Output: Person object created: Yi Sunsin, age 40
}
}
Even without a constructor, you can assign values to fields directly after creating an object. However, using a constructor guarantees initialization at the moment of object creation, preventing incomplete objects from being used.
2. Default Constructor
A constructor with no parameters is called a Default Constructor.
Condition for the Compiler to Provide One Automatically
Only when no constructor at all is declared in the class does the compiler automatically add an empty default constructor.
// Developer writes no constructor
class Dog {
String name;
}
// Compiler automatically adds: Dog() { }
Dog d = new Dog(); // works fine
When the Default Constructor Disappears
class Dog {
String name;
// Once a parameterized constructor is declared,
// the compiler no longer adds the default constructor automatically!
Dog(String name) {
this.name = name;
}
}
public class DogTest {
public static void main(String[] args) {
// Dog d = new Dog(); // Compile error! No default constructor
Dog d = new Dog("Rex"); // works fine
}
}
As soon as you declare any parameterized constructor, the default constructor is no longer added automatically. If you still need a default constructor, declare one explicitly.
3. Constructor Overloading
Constructors can be overloaded just like methods. Multiple constructors with different parameter types, counts, or orders can be declared.
public class Person {
String name;
int age;
String email;
// Constructor 1: default constructor
Person() {
name = "No Name";
age = 0;
email = "";
}
// Constructor 2: name only
Person(String name) {
this.name = name;
this.age = 0;
this.email = "";
}
// Constructor 3: name + age
Person(String name, int age) {
this.name = name;
this.age = age;
this.email = "";
}
// Constructor 4: all fields
Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
void printInfo() {
System.out.printf("Name: %s, Age: %d, Email: %s%n", name, age, email);
}
}
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person("Hong Gildong");
Person p3 = new Person("Yi Sunsin", 40);
Person p4 = new Person("Jang Bogo", 35, "jbg@korea.com");
p1.printInfo(); // Name: No Name, Age: 0, Email:
p2.printInfo(); // Name: Hong Gildong, Age: 0, Email:
p3.printInfo(); // Name: Yi Sunsin, Age: 40, Email:
p4.printInfo(); // Name: Jang Bogo, Age: 35, Email: jbg@korea.com
}
}
4. Constructor Chaining with this()
The constructors above have duplicated initialization code. Using this() can remove that duplication.
Rules for this()
- It can only be written as the first line of a constructor.
- It can only be used inside a constructor, not a method.
public class Person {
String name;
int age;
String email;
// Default constructor: delegates to the constructor with the most parameters
Person() {
this("No Name", 0, ""); // constructor chaining!
}
Person(String name) {
this(name, 0, ""); // constructor chaining!
}
Person(String name, int age) {
this(name, age, ""); // constructor chaining!
}
// Core constructor: where the actual initialization logic lives
Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
}
By concentrating initialization logic in the constructor with the most parameters, only that constructor needs to be modified when initialization logic changes. This reduces code duplication and improves maintainability.
5. Copy Constructor
Used to create a new object with the same state as an existing object.
public class Point {
int x;
int y;
// Regular constructor
Point(int x, int y) {
this.x = x;
this.y = y;
}
// Copy constructor: receives an existing Point object and initializes to same values
Point(Point other) {
this.x = other.x;
this.y = other.y;
}
@Override
public String toString() {
return "Point(" + x + ", " + y + ")";
}
}
public class CopyConstructorTest {
public static void main(String[] args) {
Point original = new Point(3, 7);
Point copy = new Point(original); // use copy constructor
System.out.println("Original: " + original); // Point(3, 7)
System.out.println("Copy: " + copy); // Point(3, 7)
// Changing the copy does not affect the original (independent objects)
copy.x = 100;
System.out.println("Original after change: " + original); // Point(3, 7)
System.out.println("Copy after change: " + copy); // Point(100, 7)
}
}
6. Immutable Object
An object whose state cannot be changed after creation. Uses private final fields and initializes only through the constructor.
public final class ImmutablePoint {
// final fields: cannot be changed once set
private final int x;
private final int y;
// Can only be initialized in the constructor
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
// Only getters — no setters
public int getX() { return x; }
public int getY() { return y; }
// To move to a new position, return a new object
public ImmutablePoint move(int dx, int dy) {
return new ImmutablePoint(x + dx, y + dy);
}
@Override
public String toString() {
return "ImmutablePoint(" + x + ", " + y + ")";
}
}
public class ImmutableTest {
public static void main(String[] args) {
ImmutablePoint p1 = new ImmutablePoint(3, 5);
System.out.println("Original: " + p1); // ImmutablePoint(3, 5)
// p1.x = 10; // Compile error! final field cannot be modified
// Moving creates a new object (original is unchanged)
ImmutablePoint p2 = p1.move(2, 3);
System.out.println("Original after move: " + p1); // ImmutablePoint(3, 5)
System.out.println("p2 after move: " + p2); // ImmutablePoint(5, 8)
}
}
- Thread-Safe: Safe for concurrent access since the state never changes.
- Predictable: The value is always the same no matter where it is read, making debugging easy.
- Usage in Java:
String,Integer,LocalDate, and others are immutable classes.
7. Introduction to the Builder Pattern
Constructors with many parameters are hard to read. The Builder pattern solves this.
public class Computer {
// Required fields
private final String cpu;
private final String ram;
// Optional fields
private final String storage;
private final String gpu;
private final boolean wifi;
// private constructor: can only be created through the Builder
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.storage = builder.storage;
this.gpu = builder.gpu;
this.wifi = builder.wifi;
}
// Inner Builder class
public static class Builder {
private final String cpu; // required
private final String ram; // required
private String storage = "256GB SSD"; // default value
private String gpu = "Integrated Graphics"; // default value
private boolean wifi = true; // default value
// Required values in Builder constructor
public Builder(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
// Optional values via method chaining
public Builder storage(String storage) {
this.storage = storage;
return this;
}
public Builder gpu(String gpu) {
this.gpu = gpu;
return this;
}
public Builder wifi(boolean wifi) {
this.wifi = wifi;
return this;
}
public Computer build() {
return new Computer(this);
}
}
@Override
public String toString() {
return String.format("Computer{cpu='%s', ram='%s', storage='%s', gpu='%s', wifi=%b}",
cpu, ram, storage, gpu, wifi);
}
}
public class BuilderTest {
public static void main(String[] args) {
// Basic configuration
Computer basic = new Computer.Builder("Intel i5", "8GB")
.build();
// High-end configuration
Computer gaming = new Computer.Builder("Intel i9", "32GB")
.storage("2TB NVMe SSD")
.gpu("RTX 4090")
.wifi(true)
.build();
System.out.println(basic);
System.out.println(gaming);
}
}
Output:
Computer{cpu='Intel i5', ram='8GB', storage='256GB SSD', gpu='Integrated Graphics', wifi=true}
Computer{cpu='Intel i9', ram='32GB', storage='2TB NVMe SSD', gpu='RTX 4090', wifi=true}
8. Practical Example: Complete Person Class
public class Person {
private String name;
private int age;
private String email;
private String phone;
// Default constructor
public Person() {
this("Default Name", 0, "", "");
}
// Name only
public Person(String name) {
this(name, 0, "", "");
}
// Name + age
public Person(String name, int age) {
this(name, age, "", "");
}
// Name + age + email
public Person(String name, int age, String email) {
this(name, age, email, "");
}
// Core constructor
public Person(String name, int age, String email, String phone) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name is required.");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
this.name = name;
this.age = age;
this.email = email;
this.phone = phone;
}
// Copy constructor
public Person(Person other) {
this(other.name, other.age, other.email, other.phone);
}
// Getters
public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }
public String getPhone() { return phone; }
@Override
public String toString() {
return String.format("Person{name='%s', age=%d, email='%s', phone='%s'}",
name, age, email, phone);
}
}
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person("Yi Sunsin");
Person p3 = new Person("Jang Bogo", 35);
Person p4 = new Person("King Sejong", 50, "sejong@joseon.kr", "010-1234-5678");
Person p5 = new Person(p4); // copy
System.out.println(p1);
System.out.println(p2);
System.out.println(p3);
System.out.println(p4);
System.out.println(p5);
// Validation test
try {
Person invalid = new Person("", -5); // exception thrown
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
Output:
Person{name='Default Name', age=0, email='', phone=''}
Person{name='Yi Sunsin', age=0, email='', phone=''}
Person{name='Jang Bogo', age=35, email='', phone=''}
Person{name='King Sejong', age=50, email='sejong@joseon.kr', phone='010-1234-5678'}
Person{name='King Sejong', age=50, email='sejong@joseon.kr', phone='010-1234-5678'}
Error: Name is required.
Summary
| Concept | Key Content |
|---|---|
| Constructor | Same name as class, no return type, automatically called by new |
| Default constructor | Automatically added by compiler when no constructor exists |
| Constructor overloading | Multiple constructors with different parameters |
this() | Calls another constructor in the same class; must be on the first line |
| Copy constructor | Creates a new object with the same state as an existing object |
| Immutable object | private final fields + initialized only through constructor |
| Builder pattern | Readable object creation when there are many parameters |