I'm a data science student with a mathematics background, so I've mostly approached programming through functions, data, and formulas. Object-oriented programming still feels unintuitive to me, especially when engineers use classes and objects for problems that seem like they could be solved procedurally.
I have a Java exam in about a month and need to get comfortable with the fundamentals. What helped OOP finally click for you, and what resources or exercises would you recommend?
4 Answers
You do not need to believe that OOP is the best approach for every problem. Functional and procedural styles can be clearer, especially for data transformations and mathematical code. OOP is mainly useful when you have related state, behavior, and boundaries that need to be managed together.
For your exam, learn the core vocabulary and practice small programs rather than trying to understand an entire software architecture at once. Write classes such as `Book`, `BankAccount`, or `Scaler`; give them private fields, constructors, and methods; create several instances; and observe how each object maintains separate state. Then study interfaces, inheritance, and polymorphism using simple examples.
A useful starting point is to think of an object as a bundle of state and behavior. A class describes what the bundle looks like, while each object is one particular instance with its own values.
For example, a Java `Scaler` class could store a mean and standard deviation and provide a `transform()` method. Two scaler objects could use the same code while remembering statistics from different datasets. You pass the fitted object around instead of separately tracking all of its parameters.
The main benefit is encapsulation: code using the object only needs to know its public methods, not how the data is stored or how the calculations work internally.
Think of OOP as a modeling and organization tool, not as the only correct way to program. A class defines a category and its allowed operations; an object is a concrete member of that category.
Encapsulation keeps related data and operations together. Inheritance represents a more specific type based on a general one, such as `Dog` and `Cat` both being `Animal`. Polymorphism lets code work with the general `Animal` interface while each specific object provides its own behavior—for example, dogs bark and cats meow without the calling code checking the type.
In practice, interfaces and composition are often more useful than building huge inheritance hierarchies. Focus first on classes, objects, constructors, fields, methods, and interfaces; treat inheritance as a tool rather than the foundation of everything.
Another way to see the purpose is separation of concerns. Suppose a game has a `Monster` object with `attack()`, `takeDamage()`, and `spawn()` methods. The level-management code can call those methods without knowing how the monster calculates damage or stores its health.
That boundary makes larger programs easier to change and lets different parts of a team work independently. Objects can be treated like black boxes: other code sends them messages through a public interface, while their implementation can change without breaking every caller.
This also explains why private fields and methods matter. They prevent unrelated code from changing an object's state arbitrarily, so the class can enforce its own rules and protect its invariants.

A good exercise would be to implement a tiny scaler, then create two instances with different means and deviations. Seeing the same method produce different results from each object's stored state makes constructors, fields, and instance methods much less abstract.