Loading learning content...
In the previous page, we established that a class is a blueprint—a template that defines structure and behavior without itself being a concrete entity. Now we turn to the other half of the foundational OOP equation: the object.
If a class is an architect's blueprint, an object is an actual building constructed from that blueprint. It occupies real space (memory), has a specific address (identity), contains actual materials (data), and can be used by real people (other code).
Understanding objects—what they are, how they're created, and how they differ from the classes that define them—is essential for every design decision you'll make in object-oriented programming.
By the end of this page, you will understand what an object truly is, how instantiation transforms a class blueprint into a living entity, the concepts of object identity and state, and the complete lifecycle of an object from creation to destruction.
An object is a concrete instance of a class—a specific entity that exists in memory at runtime. Let's unpack this definition:
1. Concrete — An object is real, not abstract. It occupies actual memory, holds actual data values, and can be manipulated by running code.
2. Instance — An object is a specific realization of a class template. The class defines what properties and behaviors exist; the object is where those properties have actual values.
3. Runtime entity — Objects exist when the program runs. They are created, used, and eventually destroyed during execution. Unlike classes, which are static definitions, objects are dynamic.
The linguistic analogy:
Think of the relationship between a class and an object like the relationship between a noun and a specific thing:
Dog → the concept of "dog"myDog → a specific dog named Max living at 123 Main StreetThe concept of "dog" defines what a dog is (has four legs, can bark, is a mammal). But the concept doesn't eat, sleep, or chase squirrels. Only specific dogs—actual instances of the concept—do those things.
| Aspect | Class | Object |
|---|---|---|
| Nature | Abstract template | Concrete entity |
| Existence | Compile-time / Definition-time | Runtime |
| Memory | Metadata only (type info) | Heap allocation (instance data) |
| Quantity | One definition per type | Many instances per class |
| Contains | Attribute declarations | Actual attribute values |
| Methods | Method definitions | Bound methods with this/self |
| Identity | Type identity (by name) | Instance identity (by reference) |
| Creation | Written by developer | Constructed by runtime |
| Modification | Immutable after compilation | State changes during execution |
The class-object distinction is what allows OOP to scale. You define a behavior ONCE in a class, but execute it on THOUSANDS of different objects, each with its own state. This is code reuse at its most fundamental level.
Instantiation is the process of creating an object from a class. It's the moment when an abstract blueprint becomes a concrete reality. Understanding what happens during instantiation illuminates the entire class-object relationship.
What happens during instantiation:
Memory Allocation — The runtime allocates space in memory (typically on the heap) to hold the object's data. The amount of memory depends on the class's attributes.
Field Initialization — Each attribute defined in the class is given storage in the object. Initially, these may be set to default values (null, 0, false, etc.).
Constructor Execution — The class's constructor method runs, initializing the object's state with meaningful values. The constructor may validate inputs, establish invariants, and perform any setup logic.
Reference Creation — A reference (pointer) to the newly created object is returned. This reference is how other code will interact with the object.
123456789101112131415161718192021222324252627282930313233343536373839404142
// The class definition (blueprint)public class Dog { private String name; private String breed; private int age; // Constructor: called during instantiation public Dog(String name, String breed, int age) { this.name = name; this.breed = breed; this.age = age; System.out.println("A dog named " + name + " has been created!"); } public void bark() { System.out.println(this.name + " says: Woof!"); }} // Client code: creating objectspublic class Main { public static void main(String[] args) { // INSTANTIATION: Creating objects from the Dog class // 1. Memory is allocated for a Dog object // 2. Fields (name, breed, age) are initialized // 3. Constructor runs with provided arguments // 4. Reference is assigned to 'max' Dog max = new Dog("Max", "Golden Retriever", 3); // Another instance from the SAME class Dog bella = new Dog("Bella", "Labrador", 5); // Each object has its own state max.bark(); // Output: Max says: Woof! bella.bark(); // Output: Bella says: Woof! // 'max' and 'bella' are different objects // They share the same class (same structure, same methods) // But they have different attribute VALUES }}In most OOP languages, the new keyword signals instantiation. It tells the runtime: 'Allocate memory for an object of this class type and invoke the constructor.' Python is an exception—it uses the class name as a callable (Dog()), but the underlying mechanism is the same.
Every object has a unique identity—a way to distinguish it from every other object, even objects of the same class with identical attribute values. This is one of the most important and subtle concepts in OOP.
Identity vs Equality:
These are fundamentally different questions with different answers.
Consider this scenario:
You create two Point objects, both representing the coordinate (5, 10):
Point p1 = new Point(5, 10);
Point p2 = new Point(5, 10);
p1 and p2 are not identical — they are different objects in different memory locationsp1 and p2 may be equal — they represent the same mathematical pointBut if you do:
Point p3 = p1;
p1 and p3 are identical — they reference the exact same objectp1 affects what you see through p3123456789101112131415161718192021222324252627282930313233
public class Point { private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } // For demonstration public void setX(int x) { this.x = x; } public int getX() { return x; }} public class IdentityDemo { public static void main(String[] args) { Point p1 = new Point(5, 10); Point p2 = new Point(5, 10); Point p3 = p1; // p3 references the SAME object as p1 // IDENTITY CHECK: using == operator System.out.println(p1 == p2); // false: different objects System.out.println(p1 == p3); // true: same object // PROOF: modifying through p3 affects p1 System.out.println(p1.getX()); // 5 p3.setX(100); // modify through p3 System.out.println(p1.getX()); // 100 <-- p1 sees the change! // But p2 is unaffected (it's a different object) System.out.println(p2.getX()); // 5 }}Confusing identity with equality is one of the most common sources of bugs in OOP. When you assign one object reference to another (p3 = p1), you are NOT copying the object—you are creating another reference to the SAME object. Changes through either reference affect the shared object.
Why identity matters:
Aliasing — Multiple references to the same object (aliases) can cause unexpected side effects. You modify an object in one place and are surprised when it changes elsewhere.
Collections — When you put objects in hash-based collections (HashMap, HashSet), identity and equality determine whether you can find them again.
Comparison Logic — In business logic, you often need to distinguish "is this the same customer record?" (identity) from "do these two customer objects represent the same person?" (equality).
Memory Management — Understanding that references share objects helps you reason about memory usage and garbage collection.
An object's state is the collection of all its attribute values at a given point in time. State is what makes each object instance unique—even objects of the same class differ by their state.
State characteristics:
Each object has independent state — Changing one object's attributes doesn't affect another object of the same class.
State can change over time — Unless the object is immutable, methods can modify attribute values, transitioning the object from one state to another.
State determines behavior — An object's response to method calls often depends on its current state. A BankAccount with balance 100 behaves differently on withdraw(150) than one with balance 1000.
State should be valid — Good design ensures objects are always in a consistent, valid state. This is called maintaining invariants.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
public class TrafficLight { // State: the current color private String color; // Invariant: color must be one of RED, YELLOW, GREEN public TrafficLight() { this.color = "RED"; // Initial state } public String getColor() { return this.color; } // State transition: changes the object's state public void next() { switch (this.color) { case "RED": this.color = "GREEN"; break; case "GREEN": this.color = "YELLOW"; break; case "YELLOW": this.color = "RED"; break; } } // Behavior that depends on state public boolean shouldStop() { return this.color.equals("RED") || this.color.equals("YELLOW"); }} // Demonstrationpublic class Main { public static void main(String[] args) { TrafficLight light1 = new TrafficLight(); TrafficLight light2 = new TrafficLight(); // Both start in the same state System.out.println(light1.getColor()); // RED System.out.println(light2.getColor()); // RED // Modify light1's state light1.next(); light1.next(); // States are independent System.out.println(light1.getColor()); // YELLOW System.out.println(light2.getColor()); // RED (unchanged) // State affects behavior System.out.println(light1.shouldStop()); // true (yellow) light1.next(); System.out.println(light1.shouldStop()); // true (red) light1.next(); System.out.println(light1.shouldStop()); // false (green) }}Every object goes through a lifecycle—from creation to destruction. Understanding this lifecycle is essential for managing resources and avoiding bugs like memory leaks or use-after-free errors.
The four phases of object lifecycle:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
public class FileProcessor { private String filename; private FileReader reader; // PHASE 1: Creation public FileProcessor(String filename) throws IOException { this.filename = filename; this.reader = new FileReader(filename); // Acquire resource System.out.println("FileProcessor created for: " + filename); } // PHASE 2: Active Use public String readLine() throws IOException { return reader.readLine(); } // Helping with PHASE 4: Release resources before collection public void close() throws IOException { if (reader != null) { reader.close(); System.out.println("FileProcessor closed for: " + filename); } }} public class LifecycleDemo { public static void processFile() throws IOException { // PHASE 1: Object is created FileProcessor processor = new FileProcessor("data.txt"); // PHASE 2: Object is used String line = processor.readLine(); System.out.println(line); // Clean up before scope ends processor.close(); // PHASE 3: After method returns, 'processor' goes out of scope // The object becomes unreachable } // PHASE 4: Eventually, GC reclaims the object's memory public static void main(String[] args) throws IOException { processFile(); // Force garbage collection (for demonstration only) System.gc(); }}Object lifecycle is especially critical for objects that hold external resources (files, network connections, database handles). If you don't properly close these before the object becomes unreachable, you leak resources. Modern languages provide constructs like try-with-resources (Java), context managers (Python), or RAII (C++) to help manage this.
Garbage Collection vs Manual Management:
Garbage-collected languages (Java, Python, JavaScript, C#): The runtime automatically detects and reclaims unreachable objects. Developers don't manually free memory.
Manual memory management (C, C++): Developers must explicitly delete or free objects. Forgetting causes memory leaks; deleting too early causes crashes.
Reference counting (Swift, some Python objects): Objects track how many references point to them. When the count hits zero, the object is immediately destroyed.
Understanding your language's lifecycle model is crucial for writing correct, efficient code.
Let's consolidate our understanding by contrasting the static world of classes with the dynamic world of objects:
Classes live in code; objects live in memory:
When you compile (or interpret) a program containing class definitions:
When the program runs and instantiation occurs:
this/self allows methods to operate on the specific object's data| Static View (Code) | Dynamic View (Memory) |
|---|---|
| class Dog { String name; } | Dog object at address 0x7F3A: name='Max' |
| One class definition | Thousands of Dog objects possible |
| Method bark() defined once | bark() called on different objects |
| Type Dog exists | Instance of Dog exists |
| Validated at compile time | Allocated at runtime |
| 100 bytes of code | Each object: 24 bytes of heap memory |
The 'this' (or 'self') reference:
How do methods know which object they're operating on? When you call max.bark(), how does the bark() method know to use Max's name and not Bella's?
The answer is the implicit this (or self in Python) reference. Every instance method receives a hidden parameter that points to the object the method was invoked on.
// When you write:
max.bark();
// It's as if you wrote:
Dog.bark(max); // 'max' becomes 'this' inside bark()
This is how a single method definition can serve millions of different objects—each call provides a different this reference.
Think of methods as machines in a factory. The class defines the machine's design. Each object is a workpiece that gets placed into the machine. The machine (method) operates on whatever workpiece (object) is currently loaded—and this/self is how it knows which workpiece it's working on.
We've now completed our understanding of the second half of the class-object relationship. Let's consolidate:
this/self enables them to operate on different objects' data.What's next:
With classes and objects understood individually, we can now explore their relationship in depth. The next page examines how classes and objects relate to each other—the type system, inheritance considerations, and why this relationship is the foundation of everything else in OOP.
You now understand objects as instances—concrete, runtime entities created from class blueprints. Combined with your understanding of classes, you have the complete foundation. Every design pattern, every architecture, every OOP technique builds on this class-object duality.