Java Classes and Objects Explained — Build Real OOP Programs From Scratch
Every real-world application you've ever used — a banking app, a game, a ride-sharing platform — is built around things that have properties and behaviours. A bank account has a balance and an owner; it can accept deposits and process withdrawals. A car has a make, model and speed; it can accelerate and brake. Java's class system exists precisely to let you model these real-world things in code, cleanly and predictably. This is the foundation of Object-Oriented Programming, and it's why Java powers everything from Android apps to enterprise banking systems.
Before OOP existed, large programs were written as one long sequence of instructions. The bigger the program got, the messier and harder to maintain it became — like writing a novel with no chapters, no characters, just one endless paragraph. Classes solve this by letting you group related data and behaviour together into a single, reusable unit. Instead of scattered variables and functions floating around your codebase, you have a tidy, self-contained object that knows what it is and what it can do.
By the end of this article you'll know how to define a class with fields, a constructor, and methods; how to create objects from that class and work with them; and you'll understand the difference between a class and an object at a level that will never confuse you again — including in job interviews.
What a Class Actually Is — The Blueprint Explained
A class is a template you write once that describes two things: the data a thing can hold (called fields or instance variables) and the actions it can perform (called methods). You're not creating anything real yet — just defining the shape of something that could exist.
Think of a cookie cutter. The cutter is the class. It defines the shape — a star, a circle, whatever. But it isn't a cookie. Every time you press the cutter into dough, you get a new cookie — a new object — that has the shape defined by the cutter.
In Java, you define a class using the class keyword. The class name should be a noun (because a class represents a thing) and it should start with a capital letter — that's a hard Java convention. Everything that belongs to the class lives inside its curly braces.
Fields hold the state of an object. Every object created from the same class gets its own private copy of those fields. Change the colour of one cookie — it doesn't affect any other cookie. That independence is what makes objects so powerful and predictable.
// A class is a blueprint — we're describing what a BankAccount looks like // This file defines the class but doesn't create any account yet public class BankAccount { // --- FIELDS (instance variables) --- // These describe the STATE of a BankAccount object // Every BankAccount object will have its OWN copy of these String accountHolderName; // who owns this account String accountNumber; // unique identifier for the account double balance; // how much money is currently in it // --- CONSTRUCTOR --- // A constructor is a special method that runs automatically when // you create a new object. Its job is to set the initial state. // Notice: same name as the class, no return type public BankAccount(String holderName, String number, double openingBalance) { accountHolderName = holderName; // store the name we were given accountNumber = number; // store the account number balance = openingBalance; // set the starting balance } // --- METHODS --- // These describe the BEHAVIOUR a BankAccount can perform // Deposit money into the account public void deposit(double amount) { if (amount <= 0) { System.out.println("Deposit amount must be positive."); return; // stop here — don't proceed with bad input } balance = balance + amount; // increase the balance System.out.println("Deposited $" + amount + " — New balance: $" + balance); } // Withdraw money from the account public void withdraw(double amount) { if (amount > balance) { System.out.println("Insufficient funds. Current balance: $" + balance); return; // stop — can't withdraw more than available } balance = balance - amount; // reduce the balance System.out.println("Withdrew $" + amount + " — New balance: $" + balance); } // Print a summary of this account's current state public void printStatement() { System.out.println("--- Account Statement ---"); System.out.println("Holder : " + accountHolderName); System.out.println("Number : " + accountNumber); System.out.println("Balance: $" + balance); System.out.println("-------------------------"); } // main method — the entry point Java uses to run the program public static void main(String[] args) { // Creating two OBJECTS (instances) from the same BankAccount class // Each object is completely independent — its own data, its own state BankAccount alicesAccount = new BankAccount("Alice Chen", "ACC-1001", 500.00); BankAccount bobsAccount = new BankAccount("Bob Martins", "ACC-1002", 1200.00); // Working with Alice's account alicesAccount.deposit(250.00); // Alice deposits $250 alicesAccount.withdraw(100.00); // Alice withdraws $100 alicesAccount.printStatement(); // print Alice's current state System.out.println(); // blank line for readability // Working with Bob's account — completely separate from Alice's bobsAccount.withdraw(400.00); // Bob withdraws $400 bobsAccount.printStatement(); // print Bob's current state } }
Withdrew $100.0 — New balance: $650.0
--- Account Statement ---
Holder : Alice Chen
Number : ACC-1001
Balance: $650.0
-------------------------
Withdrew $400.0 — New balance: $800.0
--- Account Statement ---
Holder : Bob Martins
Number : ACC-1002
Balance: $800.0
-------------------------
Objects, the `new` Keyword, and Why Each Instance Is Independent
A class sitting in a file does nothing on its own — it's just a plan on paper. To get something you can actually work with, you have to instantiate it. That's a fancy word for 'create an object from the class', and you do it with the new keyword.
When Java sees new BankAccount(...), it does three things in quick succession: it allocates a fresh chunk of memory to hold this object's data, it runs the constructor to fill in the initial values, and it hands you back a reference — think of it like a postal address — that points to where this object lives in memory.
That reference is stored in a variable. When you write BankAccount alicesAccount = new BankAccount(...), the variable alicesAccount doesn't contain the object itself — it contains the address of the object. This is a crucial distinction we'll revisit in the gotchas section.
Every object you create is completely independent. Changing Alice's balance has zero effect on Bob's balance because they each have their own copy of the balance field in their own region of memory. This is exactly why objects are so useful — you can have hundreds of bank accounts in one program, each managing its own state, without them tangling with each other.
// Let's model a Car to reinforce how multiple objects stay independent public class CarShowroom { // --- The Car class blueprint --- // (In a real project this would be its own file — Car.java) static class Car { String make; // e.g. "Toyota" String model; // e.g. "Corolla" String colour; // e.g. "Red" int currentSpeedKph; // how fast it's going right now // Constructor — sets up a car's initial state when it's created public Car(String make, String model, String colour) { this.make = make; // 'this.make' = the field; 'make' = the parameter this.model = model; this.colour = colour; this.currentSpeedKph = 0; // every new car starts stationary } // Accelerate the car by a given amount public void accelerate(int kph) { currentSpeedKph += kph; // += means 'add kph to current speed' System.out.println(colour + " " + make + " " + model + " accelerates to " + currentSpeedKph + " kph"); } // Brake the car — slow it down public void brake(int kph) { currentSpeedKph -= kph; // reduce speed if (currentSpeedKph < 0) currentSpeedKph = 0; // can't go below zero System.out.println(colour + " " + make + " " + model + " slows to " + currentSpeedKph + " kph"); } } public static void main(String[] args) { // 'new' creates a fresh Car object and returns a reference to it Car redToyota = new Car("Toyota", "Corolla", "Red"); Car blueFord = new Car("Ford", "Focus", "Blue"); Car greenHonda = new Car("Honda", "Civic", "Green"); // Each car manages its own speed independently redToyota.accelerate(60); // red Toyota speeds up blueFord.accelerate(80); // blue Ford speeds up — red Toyota unaffected redToyota.accelerate(20); // red Toyota goes faster again greenHonda.accelerate(50); // green Honda joins in blueFord.brake(30); // blue Ford slows down — others unaffected // Let's confirm the final speeds of each object System.out.println("\n--- Final Speeds ---"); System.out.println("Red Toyota : " + redToyota.currentSpeedKph + " kph"); System.out.println("Blue Ford : " + blueFord.currentSpeedKph + " kph"); System.out.println("Green Honda : " + greenHonda.currentSpeedKph + " kph"); } }
Blue Ford Focus accelerates to 80 kph
Red Toyota Corolla accelerates to 80 kph
Green Honda Civic accelerates to 50 kph
Blue Ford Focus slows to 50 kph
--- Final Speeds ---
Red Toyota : 80 kph
Blue Ford : 50 kph
Green Honda : 50 kph
Constructors — Giving Your Objects a Proper Starting State
A constructor is the first code that runs when an object is born. Its entire job is to make sure the object starts life in a valid, usable state — not half-initialised with random default values.
Constructors look like methods but with two important differences: they have no return type (not even void), and they must have exactly the same name as the class. Java knows it's a constructor because of these two rules.
You can have multiple constructors in one class — each accepting different parameters. Java figures out which one to call based on what you pass to new. This is called constructor overloading, and it's incredibly practical. Sometimes you want to create a full account with all details upfront; other times you just know the holder's name. Both scenarios can be handled cleanly without duplicating code.
If you write no constructor at all, Java silently provides a no-argument default constructor that sets all fields to their default values (0 for numbers, null for objects, false for booleans). The moment you write even one constructor yourself, Java removes that free default — something that trips up a lot of beginners.
// Demonstrates constructor overloading — multiple constructors in one class public class ProductCatalog { static class Product { String name; double price; int stockQuantity; String category; // Constructor 1: Full details — use this when you have everything upfront public Product(String name, double price, int stockQuantity, String category) { this.name = name; this.price = price; this.stockQuantity = stockQuantity; this.category = category; } // Constructor 2: Price and name only — stock defaults to 0, category to "Uncategorised" // Useful when you're adding new products that haven't arrived in stock yet public Product(String name, double price) { this.name = name; this.price = price; this.stockQuantity = 0; // sensible default this.category = "Uncategorised"; // sensible default } // Method to restock — adds more units to current stock public void restock(int unitsAdded) { stockQuantity += unitsAdded; System.out.println(name + " restocked. New quantity: " + stockQuantity); } // Method to display product details in a readable format public void displayDetails() { System.out.println("Product : " + name); System.out.println("Category : " + category); System.out.println("Price : $" + price); System.out.println("In Stock : " + stockQuantity + " units"); System.out.println("---"); } } public static void main(String[] args) { // Using Constructor 1 — all four arguments provided Product laptop = new Product("ThinkPad X1", 1299.99, 15, "Electronics"); // Using Constructor 2 — only name and price; stock and category use defaults Product newArrival = new Product("Wireless Keyboard", 49.99); laptop.displayDetails(); newArrival.displayDetails(); // The new arrival just came in — let's update its stock newArrival.restock(50); System.out.println(); newArrival.displayDetails(); // display again to see updated stock } }
Category : Electronics
Price : $1299.99
In Stock : 15 units
---
Product : Wireless Keyboard
Category : Uncategorised
Price : $49.99
In Stock : 0 units
---
Wireless Keyboard restocked. New quantity: 50
Product : Wireless Keyboard
Category : Uncategorised
Price : $49.99
In Stock : 50 units
---
Putting It All Together — A Complete OOP Mini-Program
You now have all the pieces: a class defines the blueprint, fields hold the state, a constructor sets the initial state, and methods define the behaviour. Let's pull all of this together into a program that feels like real software — a simple student grade tracker.
This example introduces one more important concept: access modifiers. Notice the fields are marked private and the methods are public. This is called encapsulation — hiding the internal data and only letting the outside world interact with it through controlled methods. It's why you can't just reach into a bank app and edit your own balance. We keep fields private and expose getters and setters where appropriate. This is the correct, professional way to write Java classes.
Even though encapsulation is technically a deeper topic, you'll see it in literally every real Java codebase, so it's important to see it from the start and not be confused when you encounter it. The rule of thumb: fields are almost always private. Methods that should be usable from outside the class are public.
public class StudentGradeTracker { // The Student class — a self-contained blueprint for a student record static class Student { // PRIVATE fields — only code inside this class can touch them directly // This is encapsulation: protect the data, control how it's accessed private String studentName; private int studentId; private double[] examScores; // array to hold multiple exam scores private int scoresRecorded; // how many scores have been added so far // Constructor — sets up the student with a name, ID and exam capacity public Student(String name, int id, int maxExams) { this.studentName = name; this.studentId = id; this.examScores = new double[maxExams]; // reserve space for scores this.scoresRecorded = 0; // no exams taken yet } // PUBLIC method — adds an exam score to this student's record public void addExamScore(double score) { if (scoresRecorded >= examScores.length) { System.out.println("Cannot add more scores — all exam slots are full."); return; } if (score < 0 || score > 100) { System.out.println("Invalid score. Must be between 0 and 100."); return; } examScores[scoresRecorded] = score; // store the score at the next open slot scoresRecorded++; // move the slot counter forward System.out.println("Score " + score + " recorded for " + studentName); } // Calculates and returns the average of all recorded scores public double calculateAverage() { if (scoresRecorded == 0) return 0.0; // avoid dividing by zero double total = 0; for (int i = 0; i < scoresRecorded; i++) { total += examScores[i]; // add each score to the running total } return total / scoresRecorded; // average = total divided by count } // Converts a numeric average into a letter grade public String getLetterGrade() { double average = calculateAverage(); if (average >= 90) return "A"; if (average >= 80) return "B"; if (average >= 70) return "C"; if (average >= 60) return "D"; return "F"; } // Getter — the only approved way for outside code to read the student's name public String getStudentName() { return studentName; } // Prints a full, formatted report for this student public void printReport() { System.out.println("============================="); System.out.println("Student : " + studentName); System.out.println("ID : " + studentId); System.out.printf ("Average : %.1f%%\n", calculateAverage()); // 1 decimal place System.out.println("Grade : " + getLetterGrade()); System.out.println("Exams : " + scoresRecorded + " recorded"); System.out.println("============================="); } } // Main method — where execution starts public static void main(String[] args) { // Create two student objects — completely independent Student priya = new Student("Priya Sharma", 1001, 4); Student james = new Student("James Okafor", 1002, 4); // Add exam scores for Priya priya.addExamScore(88.5); priya.addExamScore(92.0); priya.addExamScore(79.5); priya.addExamScore(95.0); // Add exam scores for James james.addExamScore(74.0); james.addExamScore(68.5); james.addExamScore(71.0); // Attempt to add a 5th score to Priya's record (max is 4 — should warn us) priya.addExamScore(85.0); System.out.println(); // blank line before the reports // Print both student reports priya.printReport(); james.printReport(); } }
Score 92.0 recorded for Priya Sharma
Score 79.5 recorded for Priya Sharma
Score 95.0 recorded for Priya Sharma
Score 74.0 recorded for James Okafor
Score 68.5 recorded for James Okafor
Score 71.0 recorded for James Okafor
Cannot add more scores — all exam slots are full.
=============================
Student : Priya Sharma
ID : 1001
Average : 88.8%
Grade : B
Exams : 4 recorded
=============================
=============================
Student : James Okafor
ID : 1002
Average : 71.2%
Grade : C
Exams : 3 recorded
=============================
| Aspect | Class | Object |
|---|---|---|
| What it is | A blueprint / template defined once in code | A real instance created from that blueprint at runtime |
| Exists in | Your source code file (.java) | Memory (heap) while the program is running |
| Created with | The `class` keyword | The `new` keyword |
| How many? | Usually one definition per concept | Unlimited — create as many as you need |
| Holds data? | Defines what data will exist (field declarations) | Holds its own actual values for those fields |
| Memory usage | No memory allocated for data yet | Each object occupies its own memory on the heap |
| Analogy | The cookie-cutter | Each individual cookie made with that cutter |
🎯 Key Takeaways
- A class is a blueprint — it defines structure and behaviour but creates nothing until
newis called. The class is the plan; the object is the built house. - Every object created with
newgets its own independent copy of all instance fields. Changing one object's data never automatically changes another object's data. - Constructors run once at object creation to guarantee a valid starting state — they have no return type and must match the class name exactly.
- Make fields
privateand expose behaviour throughpublicmethods — this is encapsulation, and it's what separates professional Java code from beginner code.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Forgetting
newand treating the reference variable as the object — WritingBankAccount myAccount;declares a variable but it's null — it points to nothing. CallingmyAccount.deposit(100)throws a NullPointerException at runtime. Fix: always initialise withnew:BankAccount myAccount = new BankAccount(...); - ✕Mistake 2: Parameter name shadowing a field name without using
this— Writingpublic Car(String colour) { colour = colour; }is a no-op: you're assigning the parameter to itself, and the fieldthis.colourstays at its default value (null). Fix: always usethis.fieldName = parameterNamewhen the names match, e.g.this.colour = colour; - ✕Mistake 3: Assuming two variables pointing to the same object are independent — Writing
BankAccount accountA = new BankAccount(...); BankAccount accountB = accountA;does NOT create two objects. Both variables point to the same object in memory. DoingaccountB.deposit(500)also changes what you see throughaccountA. Fix: to get a truly separate object, callnewagain:BankAccount accountB = new BankAccount(...);
Interview Questions on This Topic
- QWhat is the difference between a class and an object in Java? Can you give a real-world example?
- QWhat happens if you define a class with no constructor? What does Java do, and what changes when you add your own constructor?
- QIf two variables reference the same object and you modify the object through one variable, what does the second variable see — and why?
Frequently Asked Questions
What is the difference between a class and an object in Java?
A class is the template — written once, it defines what fields and methods something will have. An object is a live instance of that class created at runtime with the new keyword. One class can produce hundreds of independent objects, each with their own data.
Can a Java class have multiple constructors?
Yes — this is called constructor overloading. You define multiple constructors with the same name but different parameter lists. Java picks the correct one based on what arguments you pass to new. It's useful when you want to support creating objects with varying levels of initial information.
Do I always need to write a constructor in my Java class?
Not always. If you write no constructor, Java automatically provides a no-argument default constructor that sets fields to their default values (0, null, false). However, as soon as you write any constructor yourself, that free default is removed. Write an explicit no-arg constructor if you still need one alongside your custom constructors.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.