1 Why Do We Need Constructors?
In the previous lesson, we built a Circle class step by step. We learned about private fields, getters, setters, and validation. Our class works well. But let's stop and think about something important.
Look at how we currently create and use a Circle:
This works fine. But notice something: between Step 1 and Step 2, the object exists but has no meaningful data. The radius is 0.0 by default. What if someone forgets Step 2 and goes straight to Step 3? They would get an area of 0.0 — a useless result, and possibly a silent bug.
Wouldn't it be better if we could create the object and give it its starting values at the same time? Like this:
This is exactly the role of a constructor.
Motivation: Why Constructors Matter
- Sometimes we need to initialize an object at the moment it is created, not later.
- In many cases, we can assign values later using setters, as we learned. However, there are situations where the object must start in a valid state immediately — meaning it needs its starting values at the moment it is created.
- This is especially important when certain attributes are mandatory, or when the object contains internal variables or resources that must be initialized before any method can safely run.
- In addition, if we design an object to be immutable (no setters, and its state cannot change after creation), then the constructor becomes essential — because the only chance to provide the required values is during object creation.
- The constructor is what gives life to an object — it is the very first code that runs when an object is born.
2 What Is So Special About a Constructor?
A constructor is not just any method. It is a very special kind of method with unique rules that set it apart from all other methods in Java. Understanding these rules is essential.
- A constructor must have the same name as the class itself. If the class is
Circle, the constructor must be namedCircle(). - Constructors do not have a return type — not even
void. This is what distinguishes them from regular methods. - Constructors are invoked using the
newoperator when an object is created. You never call a constructor directly like a regular method. - The role of a constructor is essentially to initialize objects — to set the starting values of the object's data fields when it is first created.
Here is what the constructor looks like in our Circle class:
Now every Circle object starts its life with a meaningful radius. No more objects with default 0.0 values. No more risk of forgetting to call the setter. The object is born ready.
3 Can a Class Have More Than One Constructor?
So far, our Circle class has only one attribute: radius. But what if we want our circle to also have a color? Now the class has two attributes. And here is the interesting question: when creating a Circle, the user might want to:
- Create a Circle with no values (a default circle)
- Create a Circle by specifying only the radius
- Create a Circle by specifying only the color
- Create a Circle by specifying both radius and color
Should we force the user to always provide all values? That would be rigid and inconvenient. Instead, Java allows us to define multiple constructors in the same class — each accepting a different set of parameters. This is called constructor overloading.
Just like regular methods can be overloaded (same name, different parameters), constructors can be overloaded too. Java determines which constructor to call based on the number and types of arguments you pass when using the new operator. This gives the user of the class flexibility — they can create objects in whatever way is most convenient for their situation.
4 The Default Constructor — A Very Important Concept
Now we need to discuss something that is very important and often confuses students. Pay close attention.
This is a rule in Java: every class must have at least one constructor. There is no exception to this. An object cannot be created without a constructor being called.
But wait — think back to our very first version of the Circle class in the previous lesson. Did we write a constructor? No, we did not! We only had a radius field and a computeArea() method. Yet we still wrote new Circle() and it worked perfectly fine. How is that possible if every class must have a constructor?
The answer is: Java created one for us.
When a programmer writes a class and does not explicitly define any constructor, Java automatically provides a hidden, invisible constructor called the implicit default constructor (also known as the no-arg constructor). This constructor takes no parameters and does nothing except create the object with default values (0 for numbers, null for objects, false for booleans).
It is as if Java silently added this to your class behind the scenes:
That is why new Circle() worked even though we never wrote a constructor. Java was quietly helping us.
Two Types of Default Constructors
A default constructor is a constructor that takes no parameters. But there are two ways a default constructor can exist:
- Created automatically by Java
- Only exists when the programmer writes no constructors at all
- Takes no parameters
- Assigns default values to all fields (
0,null,false) - You cannot see it in the source code — it is invisible
- Written manually by the programmer
- The programmer chooses to define a no-arg constructor
- Takes no parameters
- Can assign custom default values (like
radius = 1.0) - You can see it in the source code — it is part of the class
For example, in Section 3, the first constructor we wrote (Circle() with no parameters, setting radius = 1.0 and color = "red") is an explicit default constructor. The programmer deliberately wrote it and chose meaningful default values.
The Critical Rule You Must Never Forget
Here is the rule that catches many students off guard:
If the programmer defines ANY constructor in the class — even just one — Java will NO LONGER create the implicit default constructor.
In other words, the implicit default constructor is Java's way of being helpful only when you haven't written any constructors yourself. The moment you take control and define even a single constructor, Java steps back and says: "The programmer is handling constructors now. I will not interfere."
Let's see exactly what this means with an example:
Because the programmer defined Circle(double radius), Java no longer provides the implicit default constructor. So Circle() with no arguments does not exist anymore. The class only has the one constructor the programmer wrote.
If you still want to allow new Circle() (with no arguments), you must explicitly write a no-arg constructor yourself:
- Every Java class has at least one constructor.
- If the programmer writes no constructors, Java automatically provides an implicit default constructor (no parameters, default values).
- If the programmer writes any constructor (even one), the implicit default constructor disappears.
- If you still need a no-arg constructor alongside other constructors, you must write it explicitly yourself.
5
The this Keyword — "I Am Talking About Myself"
You may have noticed that in our constructors and setters, we keep writing this.radius = radius. A student raises their hand: "Professor, what is this this thing? Why do we need it? Can't we just write radius = radius?"
Excellent question. Let's explore it step by step.
The "Easy" Solution: Use a Different Name
Look at our setter. One student suggests: "Why not just name the parameter something different, like r?"
This works perfectly fine! There is no ambiguity — r is clearly the parameter, and radius is clearly the data field. The compiler has no trouble telling them apart.
But here is the problem: this is not professional. When someone reads the method signature setRadius(double r), what does r mean? It is vague. It is not self-documenting. In a real project with dozens of classes and hundreds of methods, using cryptic short names like r, c, or x makes the code harder to read and maintain.
The professional and natural choice is to name the parameter exactly what it represents: radius.
The Problem: Name Shadowing
Now we have a serious problem. Look at that line: radius = radius. There are two things named radius:
- The parameter
radius— a local variable that exists only inside this method - The data field
radius— the property that belongs to the object
When the compiler sees radius = radius, it is confused. Actually, it is not confused at all — it follows a simple rule: the local variable (parameter) always takes priority over the data field. This is called name shadowing.
radius = radiusThe compiler sees radius on both sides of the assignment and interprets both as the local parameter. So it assigns the parameter to itself!
The data field of the object is never touched. It remains 0.0. The setter does absolutely nothing useful. This is a silent bug — no error, no warning, just wrong behavior.
We called setRadius(5.0), but the radius is still 0.0. The setter is broken, and the code compiles with no errors. This is a very dangerous kind of bug.
The Solution: this to the Rescue
So how do we tell the compiler: "I don't mean the local parameter. I mean the data field that belongs to this object."?
The answer is the this keyword.
this?In Java, this is a reference to the current object — the object that is currently executing the method. Think of it as the object pointing to itself and saying: "When I say this.radius, I mean MY radius — the data field that belongs to me, the object, not the local variable of the method."
this CorrectlyNow there is zero ambiguity:
this.radius— the data field (the member variable of the object)radius(withoutthis) — the local parameter passed into the method
Let's visualize what happens when we call c1.setRadius(5.0):
thisthis is simply a reference variable that holds the address of the current object. When you write this.radius, you are accessing the radius field through the object's reference — just like when you write c1.getRadius() from outside the class, except here the object is referring to itself.
Think of it this way: if someone asks you your name, you say "My name is Ahmad." That word "my" is exactly what this does in Java. It says: "My radius", "My color" — the data that belongs to me, the object.
The same logic applies in constructors:
this Required?this is required when the parameter name is the same as the data field name (name shadowing). If the names are different (like setRadius(double r)), this is optional because there is no ambiguity. However, it is considered best practice in Java to name parameters meaningfully and use this to be explicit — it makes the code clearer and self-documenting.
6
Using this() to Call Another Constructor
Now let's discover another powerful use of this. Look at our Circle class with four constructors from Section 3:
Do you notice something? Look carefully at the four constructors. They all do the same kind of work: assigning values to radius and color. The code is repeated across all four constructors. If we later add a third field (say filled), we would need to update all four constructors. This is a maintenance headache.
Every constructor is setting the same fields. The logic for assigning radius and color is written four times. This violates a fundamental principle in programming: Don't Repeat Yourself (DRY).
Wouldn't it be better if the simpler constructors could just call the most complete constructor and let it do all the work?
This is exactly what this() does. When you write this(...) inside a constructor, it calls another constructor in the same class. Java matches the call based on the arguments you pass, just like it does with new.
this()Now only one constructor (Constructor 4) contains the actual assignment logic. All other constructors simply delegate to it by calling this(...) with the appropriate values. This is called constructor chaining.
Let's trace what happens when we write new Circle():
Now if we ever need to add a new field (say filled), we only need to update one constructor — Constructor 4 — and all the others automatically benefit.
this()this()can only be used inside a constructor, not in regular methods.this()must be the very first statement in the constructor. Nothing can come before it.- A constructor can call
this()at most once — you cannot chain to two different constructors.
Two Uses of this — Don't Confuse Them!
We have now seen this used in two completely different ways. Make sure you understand the distinction:
this.field- Refers to the current object's data field
- Resolves ambiguity when a parameter has the same name as a field
- Can be used in any method or constructor
- Example:
this.radius = radius;
this(args)- Calls another constructor in the same class
- Used for constructor chaining to avoid code duplication
- Can only be used inside a constructor, as the first statement
- Example:
this(1.0, "red");
7 Object Reference Variables
Now that we understand constructors and the this keyword, let's take a closer look at what actually happens when we write a line like this:
This single line does three things:
Circle c1— Declares a variable namedc1of typeCircle. This variable is called a reference variable. It does not hold the object itself — it holds a reference (a pointer) to where the object lives in memory.new Circle(5.0, "blue")— Creates a newCircleobject in memory, calls the constructor to initialize it withradius = 5.0andcolor = "blue", and returns a reference to the newly created object.=— Assigns that reference to the variablec1. Nowc1refers to (points to) the object.
Let's visualize this in memory:
(holds a reference to the object)
(contains the data fields and methods)
The variable c1 is NOT the object. It is a remote control that points to the object. The object itself lives somewhere in memory, and c1 holds a reference to it. This is why it is called a reference variable — it refers to (points to) the object.
This is also why the this keyword works the way it does: inside the object, this holds the same reference (0x3A7F), allowing the object to refer to itself.
8 Accessing an Object's Members
Now that we have a reference variable pointing to an object, how do we interact with it? How do we read its data or call its methods?
The answer is the dot operator (.) — also called the member access operator.
The syntax is simple: referenceVariable.member
Think of the dot (.) as saying "go to the object and access...". When you write c1.getRadius(), you are telling Java: "Go to the object that c1 points to, and call its getRadius() method."
The reference variable is the gateway. The dot is the door. And the member (field or method) is what you access on the other side.
9 Default Values for Data Fields
Here is something very important that every student must know: when an object is created, all its data fields are automatically initialized with default values, even if the constructor does not explicitly set them. Java guarantees this.
| Data Type | Default Value |
|---|---|
| int, long, short, byte | 0 |
| double, float | 0.0 |
| boolean | false |
| char | '\u0000' |
| Reference types (String, Object, etc.) | null |
Let's see this in action. Consider a class with several fields that the constructor does not initialize:
Every field has its default value — even though we never set any of them. Java initializes all data fields automatically.
Data Fields vs. Local Variables: A Critical Difference
Now here is where students often get confused. The rule above applies only to data fields (fields declared inside a class). It does NOT apply to local variables (variables declared inside a method).
Java refuses to compile this code. The local variable x was declared but never assigned a value. Unlike data fields, local variables do not get default values. You must initialize them yourself before using them.
- Declared inside a class, outside any method
- Automatically initialized with default values
- Can be used immediately without manual initialization
- Example:
private int age;→ defaults to0
- Declared inside a method
- No default value — uninitialized
- Must be initialized before use, or the code will not compile
- Example:
int x;→ compile error if used before assignment
10 Primitive Types vs. Reference Types
Now let's understand a very important difference in how Java handles primitive variables (like int, double) versus reference variables (like Circle, String). This difference affects what happens when you assign one variable to another.
Primitive Variables: Values Are Copied
With primitive types, each variable holds its own independent value. When you assign one to another, the value is copied:
int i = 5; int j = i;i = 10;Each variable has its own box with its own value. Changing one does not affect the other.
Reference Variables: Addresses Are Copied
With reference types, the behavior is fundamentally different. The variable does not hold the object — it holds a reference to the object. When you assign one reference variable to another, you are copying the reference, not the object itself.
Now watch what happens when we assign c1 to c2:
c2 = c1;After c2 = c1, the variable c2 no longer points to the Circle with radius = 10.0. Instead, both c1 and c2 now point to the same object (the Circle at 0x1A2B with radius = 5.0).
This means if we modify the object through c2, the change is visible through c1 as well — because they are pointing to the exact same object:
Garbage Collection: What Happens to the Abandoned Object?
But what about the original Circle that c2 used to point to — the one with radius = 10.0 and color = "red"? It is still sitting in memory, occupying space, but no variable points to it. We have no way to access it anymore.
Java has a built-in system called the Garbage Collector that periodically scans the memory looking for objects that have no reference variable pointing to them. When it finds such an orphaned object, it automatically frees the memory it occupies so the memory can be reused.
This happens automatically in the background — the programmer does not need to worry about it, does not need to delete objects manually, and does not need to write any cleanup code. The Garbage Collector takes care of everything. This is one of the great advantages of Java compared to languages like C and C++, where the programmer is responsible for freeing memory manually.
Primitive variables hold actual values. Assigning one to another copies the value. Each variable is independent.
Reference variables hold references to objects. Assigning one to another copies the reference. Both variables point to the same object. The abandoned object is cleaned up by the Garbage Collector.
11 Useful Built-in Classes: Date and Random
Java comes with hundreds of ready-made classes in its standard library. No one is expected to memorize all of them — but as a Java programmer, it is your responsibility to explore and learn the ones you need. The official Java documentation (Javadoc) is your guide.
Here, we will briefly introduce two commonly used classes to show you how easy it is to use existing classes once you understand objects.
The Date Class
The java.util.Date class represents a specific point in time. Creating a Date object with no arguments gives you the current date and time:
The Date class is one of the oldest classes in Java. For more advanced date/time work, Java provides newer classes like LocalDate, LocalTime, and LocalDateTime in the java.time package. But Date is a good starting point to understand how objects work.
The Random Class
The java.util.Random class generates pseudo-random numbers. Let's start with a simple example:
Every time you run this program, you get different numbers. But now, let's try something interesting...
The Seed: Why Two Programs Can Produce the Same "Random" Numbers
Here is a question that surprises many beginners: are these numbers truly random? The answer is no. They are called pseudo-random numbers — they are generated by a mathematical formula, not by true randomness.
The formula works like this: it starts with an initial number called a seed. From that seed, it applies a mathematical calculation to produce the first "random" number. Then it feeds that result back into the formula to produce the second number, and so on. Each number is determined by the one before it, forming a chain.
Think of it like this:
seed → formula → number1 → formula → number2 → formula → number3 → ...
The key insight is: if two Random objects start with the same seed, they will produce the exact same sequence of numbers. Every single time. Because the formula is the same, and the starting point is the same.
Let's prove it:
Both rand1 and rand2 were created with the same seed (42). Since the mathematical formula is the same and the starting point is the same, they produce the exact same sequence of numbers: 0, 68, 54, ... every single time, on every computer, forever.
Run this program 100 times — you will always get the same output. The numbers are deterministic, not random.
Now compare this with using no seed:
If you want different results every time (games, simulations), use new Random() without a seed — Java will automatically use the current time as the seed, which is different each run.
If you want reproducible results (testing, debugging, scientific experiments), use new Random(seed) with a specific seed — this guarantees the same sequence every time, making your results predictable and verifiable.
12 Summary
- Constructors initialize objects at the moment they are created — they give life to objects.
- A constructor must have the same name as the class and has no return type.
- A class can have multiple constructors (constructor overloading).
- Every Java class has at least one constructor — the implicit default constructor disappears if any constructor is defined.
this.fieldrefers to the current object's data field, resolving ambiguity with local variables.this(args)calls another constructor in the same class (constructor chaining).- A reference variable holds a reference to an object, not the object itself.
- The dot operator (
.) is used to access an object's fields and methods. - Data fields get default values automatically; local variables do not.
- Assigning one reference to another copies the reference — both point to the same object. The abandoned object is cleaned by the Garbage Collector.
- The Random class generates pseudo-random numbers using a mathematical formula. Same seed = same sequence.
In the next lesson, we will explore instance vs. static members, and how to work with arrays of objects. We will also explore more complex class designs with multiple interacting objects.