Courses/Java/Object-Oriented Programming

    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 this keyword — 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 it Yourself »
    JavaScript
    // 💡 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 Alice

    5️⃣ 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 it Yourself »
    JavaScript
    // 💡 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@1b6d3586

    Try It: Build a Student Grade System

    Create a complete OOP system with classes, methods, and encapsulation

    Try it Yourself »
    JavaScript
    // 💡 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 this when 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 need new 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

    ConceptSyntaxPurpose
    Classclass Car { }Define a blueprint
    Objectnew Car("Toyota")Create an instance
    thisthis.brand = brandRefer to current object
    privateprivate int x;Restrict field access
    Getterpublic int getX()Read-only access
    Setterpublic void setX(int x)Validated write access
    staticstatic 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.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service