FirstHack Learn
Log in Sign up free
Lessons in this course 0/6 All courses Object-Oriented Programming

CSE

Progress0 / 6 lessons
  1. 1. What a class models, and why OOP was invented
  2. 2. Encapsulation, and why getters exist
  3. 3. Inheritance, and when NOT to use it
  4. 4. Polymorphism, with a runnable example
  5. 5. Abstraction: interfaces versus abstract classes
  6. 6. The SOLID principles in plain English

Courses › Object-Oriented Programming

What a class models, and why OOP was invented

The problem parallel arrays create, and how bundling data with behaviour fixes it.

10 min read · Lesson 1 of 6 · Free

The problem OOP was built to solve

Before classes, you stored related data in parallel arrays.

Java
public class Parallel {
    public static void main(String[] args) {
        String[] names = {"Anita Sharma", "Ravi Kumar", "Meera Nair"};
        int[] rolls    = {101, 102, 103};
        double[] cgpa  = {8.40, 7.10, 9.05};

        for (int i = 0; i < names.length; i++) {
            System.out.println(rolls[i] + " " + names[i] + " " + cgpa[i]);
        }
    }
}

It works. Now try to maintain it.

You want to sort students by cgpa. You must sort three arrays together, keeping index 2 of each array pointing at the same person. Get one swap wrong and Meera has Ravi's marks. Nothing in the language stops you.

You want to add a phone number. Add a fourth array, and update every loop and every function signature.

You want to remove a student. Shift three arrays. Miss one, and the data is silently corrupted from that index onward.

The real problem is that the language has no idea these three arrays are connected. The relationship exists only in your head and in your comments.

A class states the relationship

Java
public class Student {
    String name;
    int roll;
    double cgpa;

    public static void main(String[] args) {
        Student s = new Student();
        s.name = "Anita Sharma";
        s.roll = 101;
        s.cgpa = 8.40;

        System.out.println(s.roll + " " + s.name + " CGPA " + s.cgpa);
    }
}

Now the language knows. A Student is one thing with three fields that travel together. Sorting an array of Student can never separate a name from its cgpa, because they are not separable any more.

A class is a blueprint. An object is a thing built from it. Student is the blueprint; s is one object. new Student() allocates memory on the heap for one student and hands back a reference.

That is why you can have a thousand Student objects and one Student class. The class exists once, at compile time. The objects exist many times, at run time.

Constructors: no half-built objects

The code above builds a student in four statements. Between statement one and statement four, the object exists with a null name and a cgpa of 0.0. That gap is where bugs live.

A constructor closes the gap.

Java
public class Student {
    String name;
    int roll;
    double cgpa;

    Student(String name, int roll, double cgpa) {
        this.name = name;
        this.roll = roll;
        this.cgpa = cgpa;
    }

    public static void main(String[] args) {
        Student s = new Student("Anita Sharma", 101, 8.40);
        System.out.println(s.roll + " " + s.name + " CGPA " + s.cgpa);
    }
}

An object now cannot exist in a half-filled state. this.name = name means "the field called name gets the value of the parameter called name" — this refers to the object being built, and it is needed here only because the two share a name.

⚠️

The moment you write any constructor, Java stops giving you the free no-argument one. So after adding Student(String, int, double), the line new Student() no longer compiles. This surprises everyone once. If you need both, declare both.

Behaviour belongs with the data

The second half of OOP is that a class holds methods, not just fields. The data and the operations on it live in one place.

Java
public class Student {
    String name;
    int roll;
    double cgpa;

    Student(String name, int roll, double cgpa) {
        this.name = name;
        this.roll = roll;
        this.cgpa = cgpa;
    }

    double percentage() {
        return cgpa * 9.5;
    }

    boolean isEligibleForHonours() {
        return cgpa >= 8.5;
    }

    @Override
    public String toString() {
        return roll + " " + name + " (" + cgpa + ")";
    }

    public static void main(String[] args) {
        Student[] batch = {
            new Student("Anita Sharma", 101, 8.40),
            new Student("Ravi Kumar", 102, 7.10),
            new Student("Meera Nair", 103, 9.05)
        };

        for (Student s : batch) {
            System.out.printf("%s  %.1f%%  honours=%b%n",
                              s, s.percentage(), s.isEligibleForHonours());
        }
    }
}

Output:

Code
101 Anita Sharma (8.4)  79.8%  honours=false
102 Ravi Kumar (7.1)  67.5%  honours=false
103 Meera Nair (9.05)  86.0%  honours=true

The honours rule now lives in exactly one place. When the college changes the cutoff from 8.5 to 8.0, you edit one line. In the parallel-array version, that >= 8.5 comparison would be scattered across every file that ever checked eligibility.

That is the real argument for OOP, and it has nothing to do with the four pillars you are told to memorise. OOP is about keeping each fact in one place so that changing it is one edit.

What a class should model

A class should model one clear thing that your program talks about. A Student. An Invoice. A DatabaseConnection.

Signs you got it wrong: the class is named Manager, Data, Helper or Utils, or it has 40 fields, or you cannot describe what it is in one sentence without using "and".

💡

Before writing a class, write the sentence "A ___ knows ___ and can ___." A Student knows its name, roll and cgpa, and can compute its percentage. If that sentence is hard to write, the class is the wrong shape and no amount of syntax will fix it.