Lesson 9 • Intermediate
Object-Oriented Programming
This is the most important lesson in the entire Java course. Java is fundamentally object-oriented —everything lives inside a class. Even your main() method is inside a class. Understanding OOP is what separates beginners who can write scripts from developers who can build real applications. After this lesson, you'll think about code completely differently.
What You'll Learn
- ✅ What classes and objects are (and how they're different)
- ✅ Fields — the data each object stores
- ✅ Constructors — how to create objects with initial values
- ✅ Methods — what objects can do
- ✅ Encapsulation — protecting data with private + getters/setters
- ✅ The
thiskeyword — referring to the current object - ✅ Static vs instance — class-level vs object-level members
- ✅ The four pillars of OOP and why they matter
💡 Real-World Analogy: Blueprints & Houses
A class is like a blueprint for a house. It describes the rooms, their sizes, and how the wiring works — but it's not a house itself. When you build a house from the blueprint, that's an object (also called an instance). You can build many houses from one blueprint, each with different paint colors (field values) and different furniture inside.
Before OOP: Programs were long lists of instructions (procedural code). As software grew to thousands of lines, this became unmanageable. OOP lets you break problems into objects that each handle their own data and behavior — like departments in a company, each responsible for their own work.
1️⃣ Your First Class — The Three Core Parts
Every class has three parts: fields (data it stores), a constructor (how to create it), and methods (what it can do). Let's build a Car class step by step:
public class Car {
// ━━━ FIELDS (data each car stores) ━━━
String brand;
int year;
double mileage;
// ━━━ CONSTRUCTOR (how to create a car) ━━━
public Car(String brand, int year) {
this.brand = brand; // "this.brand" = the field
this.year = year; // "brand" = the parameter
this.mileage = 0; // default starting mileage
}
// ━━━ METHODS (what a car can do) ━━━
public void drive(double miles) {
this.mileage += miles;
}
public String displayInfo() {
return brand + " (" + year + ") — " + mileage + " miles";
}
}Now let's USE this class by creating objects:
// Creating objects from the Car blueprint
Car myCar = new Car("Toyota", 2023);
Car friendsCar = new Car("Honda", 2021);
// Each object has its OWN data
myCar.drive(150);
friendsCar.drive(3200);
System.out.println(myCar.displayInfo());
// → "Toyota (2023) — 150.0 miles"
System.out.println(friendsCar.displayInfo());
// → "Honda (2021) — 3200.0 miles"🔑 Key insight: Each object is independent. Driving myCar doesn't affect friendsCar. They share the same blueprint (class) but have their own copies of the data (fields).
2️⃣ The this Keyword
Inside a method or constructor, this refers to the current object — the specific car, student, or account that the method is being called on. It's most important when parameter names match field names:
// ❌ WITHOUT this — the parameter "shadows" the field
public Car(String brand) {
brand = brand; // Sets the parameter to itself! Field unchanged!
}
// ✅ WITH this — clearly distinguishes field from parameter
public Car(String brand) {
this.brand = brand; // this.brand = the field, brand = the parameter
}
// this also lets you chain constructors:
public Car(String brand) {
this(brand, 2024); // Calls the other constructor
}3️⃣ Encapsulation — Protecting Your Data
Encapsulation means hiding the internal data of an object and only allowing access through controlled methods. Think of a bank account: you can't reach into the vault directly. You go through the teller (methods) who validates your request.
public class BankAccount {
private double balance; // PRIVATE — can't be accessed from outside!
public BankAccount(double initial) {
if (initial < 0) {
throw new IllegalArgumentException("Can't start negative!");
}
this.balance = initial;
}
// GETTER — read-only access
public double getBalance() {
return balance;
}
// SETTER with validation — controlled write access
public void deposit(double amount) {
if (amount <= 0) {
System.out.println("Amount must be positive!");
return;
}
balance += amount;
}
public boolean withdraw(double amount) {
if (amount > balance) {
System.out.println("Insufficient funds!");
return false;
}
balance -= amount;
return true;
}
}
// Usage:
BankAccount acct = new BankAccount(1000);
acct.deposit(500); // ✅ Goes through validation
acct.withdraw(200); // ✅ Checks balance first
// acct.balance = -999; // ❌ Won't compile — balance is private!🔑 Why not just make everything public? Without encapsulation, anyone could write acct.balance = -999999. Encapsulation ensures your data stays valid by forcing all changes through methods that include validation.
Try It: Classes, Objects & Encapsulation
Create Car and BankAccount objects with encapsulated data
// 💡 Try modifying this code and see what happens!
// OOP Basics — Java logic simulated in JavaScript
console.log("=== Classes, Objects & Encapsulation ===\n");
// 1️⃣ The Car class
class Car {
constructor(brand, model, year) {
this.brand = brand;
this.model = model;
this.year = year;
this.mileage = 0;
}
drive(miles) {
this.mileage += miles;
}
displayInfo() {
return this.brand + " " + this.model + " (" + this.year + ") — " + this.mileage + " mi";
}
age() {
...4️⃣ Constructor Overloading — Multiple Ways to Create Objects
Just like methods, constructors can be overloaded — you can have multiple constructors with different parameter lists, giving users different ways to create objects:
public class Student {
private String name;
private String grade;
private double gpa;
// Full constructor
public Student(String name, String grade, double gpa) {
this.name = name;
this.grade = grade;
this.gpa = gpa;
}
// Partial constructor (defaults)
public Student(String name) {
this(name, "Freshman", 0.0); // Calls the full constructor
}
// Copy constructor
public Student(Student other) {
this(other.name, other.grade, other.gpa);
}
}
// Usage:
Student s1 = new Student("Alice", "Junior", 3.8);
Student s2 = new Student("Bob"); // Freshman, GPA 0.0
Student s3 = new Student(s1); // Copy of Alice5️⃣ Static vs Instance — Class-Level vs Object-Level
Instance members belong to each individual object (each car has its own mileage). Static members belong to the class itself and are shared by all objects.
public class Car {
// Instance field — each car has its own
private String brand;
private double mileage;
// Static field — shared by ALL cars
private static int totalCarsCreated = 0;
public Car(String brand) {
this.brand = brand;
this.mileage = 0;
totalCarsCreated++; // Incremented for EVERY car
}
// Static method — called on the CLASS, not an object
public static int getTotalCars() {
return totalCarsCreated;
}
}
Car a = new Car("Toyota");
Car b = new Car("Honda");
Car c = new Car("BMW");
System.out.println(Car.getTotalCars()); // 3 (shared counter)💡 When to use static:
- Static methods: Utility functions that don't need object data (
Math.max(),Integer.parseInt()) - Static fields: Shared counters, constants (
Math.PI) - Everything else: Use instance members (the default)
Try It: Constructor Overloading & Static Members
See how multiple constructors and static fields work
// 💡 Try modifying this code and see what happens!
// Constructor Overloading & Static — Java logic simulated in JavaScript
console.log("=== Constructors & Static Members ===\n");
// 1️⃣ Student with multiple constructors (simulated)
class Student {
static totalStudents = 0;
constructor(name, grade = "Freshman", gpa = 0.0) {
this.name = name;
this.grade = grade;
this.gpa = gpa;
Student.totalStudents++;
}
toString() {
return this.name + " (" + this.grade + ", GPA:
...6️⃣ The Four Pillars of OOP
These four principles are the foundation of object-oriented design. You've already learned the first two — the others come in the next lessons:
1. Encapsulation ✅ (this lesson)
Hide internal details, control access via methods. Like a TV remote — you press buttons, you don't rewire circuits.
2. Abstraction
Show only essential features, hide complexity. Like driving a car — you use the steering wheel without understanding the engine.
3. Inheritance (next lesson)
Create new classes from existing ones. "Dog is an Animal" — dogs automatically get Animal behaviors.
4. Polymorphism (next lesson)
Objects can take many forms. A draw() method works differently for Circle, Square, and Triangle.
7️⃣ toString() — Making Objects Printable
By default, printing an object shows something useless like Car@1b6d3586. Override toString() to make it human-readable:
public class Car {
private String brand;
private int year;
@Override
public String toString() {
return brand + " (" + year + ")";
}
}
Car car = new Car("Toyota", 2023);
System.out.println(car); // "Toyota (2023)" — automatically calls toString()
// Without @Override toString(), it would print: Car@1b6d3586Try It: Build a Student Grade System
Create a complete OOP system with classes, methods, and encapsulation
// 💡 Try modifying this code and see what happens!
// Student Grade System — Java OOP simulated in JavaScript
console.log("=== 🎓 Student Grade System ===\n");
class Student {
#grades = []; // private (encapsulated)
constructor(name, id) {
this.name = name;
this.id = id;
}
addGrade(subject, score) {
if (score < 0 || score > 100) {
console.log(" ⚠️ Invalid score: " + score);
return;
}
this.#grades.push({ subject, score });
}
getAverage() {
...Common Mistakes
- ❌ Forgetting
thiswhen names match:public Car(String brand) { brand = brand; } // ❌ Does nothing! public Car(String brand) { this.brand = brand; } // ✅ Correct - ❌ Making all fields public: Always start with
private. Add getters/setters only as needed. This protects your data. - ❌ Putting all logic in main(): If a method only uses data from one object, it belongs in that object's class. Ask: "who owns this data?"
- ❌ Not calling the constructor:
Car myCar;only declares a variable — it doesn't create an object! You neednew Car(...). - ❌ Comparing objects with ==: Like strings,
==compares references. Override.equals()for content comparison.
Pro Tips
💡 Name classes with nouns: Car, Student, BankAccount — not CarManager or DoStuff.
💡 Keep classes focused: A class should represent ONE concept. If it's doing too many things, split it up.
💡 Always override toString(): Makes debugging 10x easier when you can print objects meaningfully.
💡 Start private, open later: Begin with everything private. Make things public only when external access is actually needed.
💡 Getters without setters = read-only fields: Provide a getter but no setter for values that shouldn't change after creation (like a student's ID).
📋 Quick Reference
| Concept | Syntax | Purpose |
|---|---|---|
| Class | class Car { } | Define a blueprint |
| Object | new Car("Toyota") | Create an instance |
| this | this.brand = brand | Refer to current object |
| private | private int x; | Restrict field access |
| Getter | public int getX() | Read-only access |
| Setter | public void setX(int x) | Validated write access |
| static | static int count; | Shared across all instances |
| toString() | @Override toString() | Printable representation |
🎉 Lesson Complete!
You now understand classes, objects, encapsulation, constructors, the this keyword, and static members — the foundation of ALL Java programming. Every framework, library, and API you'll ever use in Java is built on these concepts.
Next up: Inheritance — create new classes from existing ones to maximize code reuse and build logical hierarchies.
Sign up for free to track which lessons you've completed and get learning reminders.