Object-Oriented Programming: Concepts and Java Examples
Object-Oriented Programming (OOP) is a programming approach that organizes software around objects, their state, and their behavior. It can make related code easier to understand, test, extend, and maintain when used thoughtfully.
These notes use Java examples, but the central OOP ideas apply to many class-based languages.
1. OOP Basics
OOP models a program as collaborating objects. An object commonly has state (stored data), behavior (operations), and identity (it is a distinct object even when another object has the same state).
OOP does not require every program to imitate a real-world object exactly. It can also model concepts such as an order, request, document, connection, timer, or game rule.
| Principle | Meaning | Example idea |
|---|---|---|
| Encapsulation | Bundle data with related operations and control access to internal state. | A bank account validates deposits instead of exposing its balance for direct editing. |
| Inheritance | Create a specialized class from a more general class. | A Dog class extends an Animal class. |
| Polymorphism | Use a common type or operation while allowing different implementations. | An Animal reference can hold a Dog or Cat. |
| Abstraction | Define essential behavior while hiding unnecessary implementation details. | A payment interface specifies what a payment can do without exposing each provider's internal process. |
2. Classes and Objects
A class defines the structure and behavior that objects of a particular type can have. An object is an instance created from that class.
| Class | Object |
|---|---|
| A blueprint or definition. | An instance created from a class. |
| Defines fields, constructors, and methods. | Has its own state and identity. |
| One class can create many objects. | Many objects can be created from one class. |
For example: Student. |
For example: one particular student object. |
Runnable Java example
public class Main {
static class Student {
private final String name;
private int marks;
Student(String name, int marks) {
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException("Marks must be from 0 to 100.");
}
this.name = name;
this.marks = marks;
}
void printDetails() {
System.out.println(name + ": " + marks);
}
}
public static void main(String[] args) {
Student student = new Student("Asha", 85);
student.printDetails();
}
}
nameandmarksare fields that represent state.printDetails()is a method that represents behavior.new Student(...)creates an object.this.namerefers to the field of the current object.
3. Encapsulation
Encapsulation means placing data and the methods that work on it together in a class, while preventing uncontrolled changes to internal state. It is not merely adding getters and setters; a well-designed class exposes meaningful operations and enforces valid rules.
How to apply encapsulation
- Keep internal fields private unless wider visibility is genuinely needed.
- Validate data before changing object state.
- Expose domain operations such as
deposit()orwithdraw(). - Return only the information that callers need.
- Prefer immutable state where changes are unnecessary.
import java.math.BigDecimal;
public class Main {
static class BankAccount {
private final String accountId;
private BigDecimal balance = BigDecimal.ZERO;
BankAccount(String accountId) {
this.accountId = accountId;
}
void deposit(BigDecimal amount) {
if (amount == null || amount.signum() <= 0) {
throw new IllegalArgumentException("Deposit must be positive.");
}
balance = balance.add(amount);
}
BigDecimal getBalance() {
return balance;
}
String getAccountId() {
return accountId;
}
}
public static void main(String[] args) {
BankAccount account = new BankAccount("ACC-101");
account.deposit(new BigDecimal("250.50"));
System.out.println(account.getAccountId());
System.out.println(account.getBalance());
}
}
The example uses BigDecimal rather than double
because binary floating-point values can introduce rounding issues in
monetary calculations.
4. Inheritance and Composition
Inheritance allows one class to reuse and specialize accessible behavior from another class. The existing class is the superclass or base class; the specialized class is the subclass or derived class.
public class Main {
static class Animal {
void eat() {
System.out.println("Animal is eating.");
}
}
static class Dog extends Animal {
void bark() {
System.out.println("Dog barks.");
}
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // inherited method
dog.bark(); // Dog-specific method
}
}
Inheritance in Java
- Single inheritance: one class extends one superclass.
- Multilevel inheritance: a subclass can itself be extended.
- Hierarchical inheritance: multiple subclasses can extend the same superclass.
- A Java class cannot extend multiple classes.
- A Java class can implement multiple interfaces.
- Constructors are not inherited, although a subclass constructor can call a superclass constructor using
super(...).
Use inheritance carefully
Inheritance should usually express a stable is-a relationship:
a Dog is an Animal. It is often better to use
composition for a has-a relationship:
a Car has an Engine.
5. Polymorphism
Polymorphism means “many forms.” It enables code to work through a common type or operation while different objects provide suitable behavior.
Method overloading and method overriding
| Feature | Method overloading | Method overriding |
|---|---|---|
| Where it occurs | Usually within one class. | Between a superclass and subclass. |
| Method signature | Same method name, different parameter lists. | Same method signature as an inherited instance method. |
| Selection | Resolved at compile time. | Selected at runtime for overridden instance methods. |
| Return type alone | Cannot distinguish overloaded methods by return type alone. | Must be compatible with the inherited method's return type. |
public class Main {
static class Animal {
void makeSound() {
System.out.println("Animal makes a sound.");
}
}
static class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog barks.");
}
}
static class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Cat meows.");
}
}
public static void main(String[] args) {
Animal first = new Dog();
Animal second = new Cat();
first.makeSound();
second.makeSound();
}
}
The variables are declared as Animal, but the method selected at
runtime depends on the actual object: Dog or Cat.
This is runtime polymorphism.
6. Abstraction, Abstract Classes, and Interfaces
Abstraction defines what an object must be able to do without requiring every caller to know how it does it. It helps separate a useful contract from the implementation details behind that contract.
Abstract class versus interface
| Feature | Abstract class | Interface |
|---|---|---|
| Instantiation | Cannot be directly instantiated. | Cannot be directly instantiated. |
| State and constructors | Can have instance fields and constructors. | Does not provide normal instance constructors. |
| Methods | Can contain abstract and concrete methods. | Can contain abstract, default, static, and supported private methods. |
| Inheritance | A class can extend one class. | A class can implement multiple interfaces. |
| Fields | Can have instance fields with different access levels. | Fields are implicitly public, static, and final. |
public class Main {
interface Printable {
void print();
}
static class Report implements Printable {
@Override
public void print() {
System.out.println("Printing report.");
}
}
public static void main(String[] args) {
Printable item = new Report();
item.print();
}
}
Choose an abstract class when related types need shared state, constructors, or reusable implementation. Choose an interface when you want to define a capability or contract that unrelated classes can implement.
7. Constructors, static, and final
Constructors
A constructor initializes an object when it is created. It has the same name
as its class and does not have a return type, not even void.
- A no-argument constructor accepts no parameters.
- A parameterized constructor accepts one or more parameters.
- Constructors can be overloaded.
- A programmer may write a copy constructor, but Java does not provide one automatically.
- If a class declares no constructor, the compiler can provide a default no-argument constructor.
- If a class declares any constructor, the compiler does not add a no-argument constructor automatically.
this(...) calls another constructor in the same class.
super(...) calls a superclass constructor. A constructor call
must be the first statement in a constructor.
static and final
| Keyword | Meaning | Typical use |
|---|---|---|
static |
Belongs to the class rather than to each object. | Constants, utility methods, counters, and the main method. |
final variable |
Can be assigned only once. | An immutable reference or constant value. |
final method |
Cannot be overridden by a subclass. | Protecting required behavior. |
final class |
Cannot be extended. | Preventing inheritance when a type should not be specialized. |
8. Access Modifiers in Java
Access modifiers control which code can use a class member. At top level,
classes can be public or package-private. Members can also be
private or protected.
| Access level | Same class | Same package | Subclass in another package | Non-subclass in another package |
|---|---|---|---|---|
private |
Yes | No | No | No |
| package-private (no modifier) |
Yes | Yes | No | No |
protected |
Yes | Yes | Yes, through inheritance rules | No |
public |
Yes | Yes | Yes | Yes, subject to module visibility where applicable |
A subclass in another package cannot use a protected instance member through an arbitrary superclass object. Cross-package protected access is intended for code that is implementing the subclass relationship.
9. OOP Design and Procedural Programming
Procedural programming organizes code primarily around functions and the steps they perform. OOP organizes code around objects that own state and behavior. Both approaches can be useful, and both can use careful top-down or bottom-up design.
| Aspect | Procedural style | Object-oriented style |
|---|---|---|
| Primary organization | Functions and procedures | Classes, objects, and interfaces |
| Data and behavior | May be handled separately | Often grouped within objects |
| Reuse | Functions, modules, and libraries | Composition, interfaces, classes, and carefully chosen inheritance |
| Best suited for | Simple transformations, algorithms, and step-based tasks | Systems with interacting entities, state, and changing behavior |
Benefits and limits of OOP
- Modularity: related data and behavior can be kept together.
- Maintainability: clear responsibilities can make changes easier to isolate.
- Reusability: interfaces and composition can help reuse tested behavior.
- Flexibility: polymorphism can let one caller work with several implementations.
- Testability: small, focused classes and interfaces are often easier to test.
- Limitation: excessive inheritance, unnecessary abstractions, or oversized classes can make software harder to understand.
10. Quick Revision and Practice Questions
| Concept | Key point |
|---|---|
| Class | A definition or blueprint for creating objects. |
| Object | An instance with state, behavior, and identity. |
| Encapsulation | Control access to state through meaningful operations and validation. |
| Inheritance | Creates a specialized class from a more general class. |
| Composition | Builds one object using other objects; often preferable to inheritance for code reuse. |
| Overloading | Same method name with different parameter lists; resolved at compile time. |
| Overriding | A subclass replaces an inherited instance-method implementation; selected at runtime. |
| Abstract class | Can share state, constructors, concrete methods, and abstract methods. |
| Interface | Defines a contract that multiple unrelated classes can implement. |
Practice questions
-
What is the difference between a class and an object?
Answer: A class defines the structure and behavior of a type, while an object is an instance created from that class. -
Why is encapsulation useful?
Answer: It protects internal state, allows validation, and reduces dependence on implementation details. -
Can a Java class extend multiple classes?
Answer: No. A class can extend one class, but it can implement multiple interfaces. -
What is the difference between overloading and overriding?
Answer: Overloading uses different parameter lists and is resolved at compile time. Overriding replaces an inherited instance method and is selected at runtime. -
When is composition usually preferable to inheritance?
Answer: When one object needs another object's behavior but is not truly a specialized form of that object.