🇸🇦 العربية

Constructors & this Keyword

Giving Life to Objects from the Moment They Are Created

CPCS203 - Module 04: Objects and Classes

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:

Circle c1 = new Circle(); // Step 1: Create the object c1.setRadius(5.0); // Step 2: Set the radius c1.computeArea(); // Step 3: Now we can use it

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:

Circle c1 = new Circle(5.0); // Create AND initialize in one step!

This is exactly the role of a constructor.

Motivation: Why Constructors Matter

Why We Need Constructors
  • 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.

Constructor Rules
  • A constructor must have the same name as the class itself. If the class is Circle, the constructor must be named Circle().
  • Constructors do not have a return type — not even void. This is what distinguishes them from regular methods.
  • Constructors are invoked using the new operator 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:

Circle.java — With Constructor
public class Circle { private double radius; // Constructor — same name as the class, no return type! public Circle(double radius) { this.radius = radius; } public double getRadius() { return radius; } public void setRadius(double radius) { if (radius >= 0) { this.radius = radius; } } public double computeArea() { return Math.PI * radius * radius; } }
Main.java — Using the Constructor
Circle c1 = new Circle(5.0); // Created with radius = 5.0 Circle c2 = new Circle(10.0); // Created with radius = 10.0 System.out.println(c1.computeArea()); // 78.54 System.out.println(c2.computeArea()); // 314.16
The Power of Constructors

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.

Circle.java — With Multiple Constructors
public class Circle { private double radius; private String color; // Constructor 1: No parameters (default values) public Circle() { this.radius = 1.0; this.color = "red"; } // Constructor 2: Only radius public Circle(double radius) { this.radius = radius; this.color = "red"; } // Constructor 3: Only color public Circle(String color) { this.radius = 1.0; this.color = color; } // Constructor 4: Both radius and color public Circle(double radius, String color) { this.radius = radius; this.color = color; } public double computeArea() { return Math.PI * radius * radius; } }
Main.java — Creating Circles in Different Ways
Circle c1 = new Circle(); // radius=1.0, color="red" Circle c2 = new Circle(5.0); // radius=5.0, color="red" Circle c3 = new Circle("blue"); // radius=1.0, color="blue" Circle c4 = new Circle(7.5, "green"); // radius=7.5, color="green"
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.

Fundamental Rule: Every Java Class Has At Least One Constructor

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:

What Java Adds Implicitly (You Don't See This in Your Code)
public class Circle { private double radius; // This constructor is INVISIBLE — Java creates it for you // ONLY if you don't write any constructor yourself public Circle() { // does nothing — fields get default values (radius = 0.0) } public double computeArea() { return Math.PI * radius * radius; } }

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:

Implicit Default Constructor
  • 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
Explicit Default Constructor
  • 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

WARNING: When Does the Implicit Default Constructor Disappear?

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:

Circle.java — With Only a Parameterized Constructor
public class Circle { private double radius; // The programmer defined ONE constructor (with a parameter) public Circle(double radius) { this.radius = radius; } public double computeArea() { return Math.PI * radius * radius; } }
Main.java — What Works and What Doesn't
Circle c1 = new Circle(5.0); // WORKS — matches the constructor we defined Circle c2 = new Circle(); // ERROR! Does not compile! // "no suitable constructor found for Circle(no arguments)"
Why Does This Fail?

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:

Circle.java — Adding an Explicit Default Constructor
public class Circle { private double radius; // Explicit default constructor (no parameters) public Circle() { this.radius = 1.0; // sensible default value } // Parameterized constructor public Circle(double radius) { this.radius = radius; } public double computeArea() { return Math.PI * radius * radius; } }
Circle c1 = new Circle(); // NOW it works! Uses the explicit default constructor Circle c2 = new Circle(5.0); // Also works! Uses the parameterized constructor
Summary: The Default Constructor
  • 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?"

Circle.java — Using a Different Parameter Name
public void setRadius(double r) { radius = r; // No confusion! 'r' is the parameter, 'radius' is the field. }

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.

Circle.java — Same Name for Parameter and Field
public void setRadius(double radius) { radius = radius; // Wait... which radius is which?! }

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.

What Actually Happens with radius = radius

The 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.

public void setRadius(double radius) { radius = radius; // What the compiler actually understands: // local_parameter = local_parameter; (assigns to itself!) // The object's data field 'radius' is NEVER modified. }
Main.java — The Silent Bug
Circle c1 = new Circle(); c1.setRadius(5.0); System.out.println(c1.getRadius()); // 0.0 — the radius was NEVER set!

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.

What Is 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."

Circle.java — Using this Correctly
public void setRadius(double radius) { this.radius = radius; // What the compiler understands: // this.radius → the data field of THIS object // radius → the local parameter (the value passed in) // So: "Take the value passed in, and store it in MY data field." }

Now there is zero ambiguity:

  • this.radius — the data field (the member variable of the object)
  • radius (without this) — the local parameter passed into the method

Let's visualize what happens when we call c1.setRadius(5.0):

// Suppose c1 holds the reference 0x3A7F (pointing to a Circle object) c1.setRadius(5.0); // Inside the method, 'this' holds the same reference as c1 (0x3A7F) // So: // this.radius = radius; // ↓ ↓ // (0x3A7F).radius = 5.0; // ↓ // The object's data field is now 5.0!
Understanding this

this 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:

Constructor — Same Idea
public Circle(double radius, String color) { this.radius = radius; // MY radius = the radius you gave me this.color = color; // MY color = the color you gave me }
When Is 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:

Circle.java — Four Constructors (Current Version)
public class Circle { private double radius; private String color; // Constructor 1: No parameters public Circle() { this.radius = 1.0; this.color = "red"; } // Constructor 2: Only radius public Circle(double radius) { this.radius = radius; this.color = "red"; } // Constructor 3: Only color public Circle(String color) { this.radius = 1.0; this.color = color; } // Constructor 4: Both radius and color public Circle(double radius, String color) { this.radius = radius; this.color = color; } }

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.

The Problem: Code Duplication

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.

Circle.java — Refactored with this()
public class Circle { private double radius; private String color; // Constructor 1: No parameters → calls Constructor 4 public Circle() { this(1.0, "red"); // calls Circle(double, String) } // Constructor 2: Only radius → calls Constructor 4 public Circle(double radius) { this(radius, "red"); // calls Circle(double, String) } // Constructor 3: Only color → calls Constructor 4 public Circle(String color) { this(1.0, color); // calls Circle(double, String) } // Constructor 4: The "main" constructor — does all the real work public Circle(double radius, String color) { this.radius = radius; this.color = color; } }
How It Works

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():

Circle c1 = new Circle(); // Step 1: new Circle() → calls Constructor 1 (no parameters) // Step 2: Constructor 1 executes this(1.0, "red") // → calls Constructor 4 with radius=1.0 and color="red" // Step 3: Constructor 4 executes this.radius = 1.0; this.color = "red"; // Result: c1 has radius=1.0 and color="red"
Circle c2 = new Circle(5.0); // Step 1: new Circle(5.0) → calls Constructor 2 (double parameter) // Step 2: Constructor 2 executes this(5.0, "red") // → calls Constructor 4 with radius=5.0 and color="red" // Step 3: Constructor 4 executes this.radius = 5.0; this.color = "red"; // Result: c2 has radius=5.0 and color="red"

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.

Important Rules for 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:

Circle c1 = new Circle(5.0, "blue");

This single line does three things:

  • Circle c1 — Declares a variable named c1 of type Circle. 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 new Circle object in memory, calls the constructor to initialize it with radius = 5.0 and color = "blue", and returns a reference to the newly created object.
  • = — Assigns that reference to the variable c1. Now c1 refers to (points to) the object.

Let's visualize this in memory:

Variable
c1
0x3A7F
Reference variable
(holds a reference to the object)
Circle Object (0x3A7F)
radius: 5.0
color: "blue"
computeArea() method
The actual object in memory
(contains the data fields and methods)
Key Understanding

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

Accessing Members via the Dot Operator
Circle c1 = new Circle(5.0, "blue"); // Calling a method (behavior) double area = c1.computeArea(); // c1.computeArea() → calls the method // Calling the getter (read the data) double r = c1.getRadius(); // c1.getRadius() → returns 5.0 // Calling the setter (modify the data) c1.setRadius(7.5); // c1.setRadius(7.5) → changes the radius System.out.println("Radius: " + c1.getRadius()); // 7.5 System.out.println("Area: " + c1.computeArea()); // 176.71
What Does the Dot Mean?

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.

Default Values for Data Fields
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:

Student.java — No Initialization in Constructor
public class Student { private String name; private int age; private double gpa; private boolean graduated; private char grade; // No constructor defined — Java provides the implicit default constructor public void printInfo() { System.out.println("Name: " + name); System.out.println("Age: " + age); System.out.println("GPA: " + gpa); System.out.println("Graduated: " + graduated); System.out.println("Grade: ['" + grade + "']"); } }
Main.java — Printing Without Initializing
Student s = new Student(); s.printInfo();
Output
Name: null Age: 0 GPA: 0.0 Graduated: false Grade: ['']

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).

Main.java — Local Variable Without Initialization
public class Main { public static void main(String[] args) { int x; // local variable — NOT initialized System.out.println(x); // ERROR! "variable x might not have been initialized" } }
This Will NOT Compile!

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.

Data Fields (Class Level)
  • Declared inside a class, outside any method
  • Automatically initialized with default values
  • Can be used immediately without manual initialization
  • Example: private int age; → defaults to 0
Local Variables (Method Level)
  • 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:

Primitive Assignment
int i = 5; int j = i; // j gets a COPY of i's value // Now: i = 5, j = 5 (two independent copies) i = 10; // changing i does NOT affect j System.out.println("i = " + i); // i = 10 System.out.println("j = " + j); // j = 5 — unchanged!
After int i = 5; int j = i;
i
5
copied to
j
5
After i = 10;
i
10
independent
j
5

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.

Reference Assignment
Circle c1 = new Circle(5.0, "blue"); Circle c2 = new Circle(10.0, "red");
After creating c1 and c2:
c1
0x1A2B
Circle Object (0x1A2B)
radius: 5.0
color: "blue"
c2
0x7F3E
Circle Object (0x7F3E)
radius: 10.0
color: "red"

Now watch what happens when we assign c1 to c2:

c2 = c1; // c2 now holds the SAME reference as c1
After c2 = c1;
c1
0x1A2B
Circle Object (0x1A2B)
radius: 5.0
color: "blue"
c2
0x1A2B
Circle Object (0x7F3E)
radius: 10.0
color: "red"
No variable points here anymore!
This object is still in memory, occupying space, but it is unreachable — no reference variable refers to it.
What Happened?

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:

c2.setRadius(99.0); System.out.println(c1.getRadius()); // 99.0 — c1 sees the change! System.out.println(c2.getRadius()); // 99.0 — 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.

Garbage Collection

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.

Circle Object (0x7F3E)
radius: 10.0
color: "red"
Garbage Collector
Detects the unreachable object, reclaims the memory, and frees the space automatically.
Primitive vs. Reference: Summary

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:

import java.util.Date; Date now = new Date(); System.out.println(now); // Output: Wed Feb 11 14:30:25 AST 2026 (current date and time)
Note

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:

import java.util.Random; Random rand = new Random(); System.out.println(rand.nextInt(100)); // random int from 0 to 99 System.out.println(rand.nextInt(100)); // another random int from 0 to 99 System.out.println(rand.nextDouble()); // random double from 0.0 to 1.0

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.

How Pseudo-Random Numbers Work

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:

Same Seed = Same Numbers
Random rand1 = new Random(42); // seed = 42 Random rand2 = new Random(42); // seed = 42 (same!) System.out.println("rand1: " + rand1.nextInt(100)); // 0 System.out.println("rand2: " + rand2.nextInt(100)); // 0 — same! System.out.println("rand1: " + rand1.nextInt(100)); // 68 System.out.println("rand2: " + rand2.nextInt(100)); // 68 — same! System.out.println("rand1: " + rand1.nextInt(100)); // 54 System.out.println("rand2: " + rand2.nextInt(100)); // 54 — same again!
Why Are They Identical?

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:

No Seed = Different Numbers Each Run
Random rand = new Random(); // no seed — Java uses the current time as seed System.out.println(rand.nextInt(100)); // different every time you run! System.out.println(rand.nextInt(100)); // different every time you run!
When to Use a 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

What We Learned
  • 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.field refers 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.
What's Next?

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.

King Abdulaziz University
Faculty of Computing and Information Technology (FCIT)
Prepared by Dr. Abdulmohsen Almalawi
Last Updated: February 11, 2026