Skip to content

Java OOP — Object-Oriented Programming Explained

DodaTech Updated 2026-06-20 11 min read

In this tutorial, you'll learn about Java OOP. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Java OOP uses classes and objects with Encapsulation, inheritance, polymorphism, and abstraction — the pillars that make enterprise code maintainable at scale.

What You'll Learn

You'll understand the four pillars of OOP — Encapsulation, inheritance, polymorphism, and abstraction — and how to apply them in Java with classes, interfaces, abstract classes, and constructors. This knowledge is the foundation for the Java Collections and Design Patterns tutorials.

Why OOP Matters

OOP is not just a theoretical concept — it's how real-world Java applications are built. Every Spring Boot service, every Android app, and every enterprise system relies on OOP principles. DodaTech's Durga Antivirus Pro uses OOP extensively: ScanEngine, SignatureDB, and QuarantineManager are classes that extend a common base, and each scanner plugin implements a shared Scanner interface. Without OOP, the 500,000+ lines of code would be unmaintainable.

Real-World Use: Antivirus Scanner Architecture

Think about how an antivirus scanner works. You have different scan types — quick scan, full scan, custom scan — but they all share common behavior (opening files, checking signatures, reporting results). OOP lets you model this naturally: a base ScanEngine class provides shared logic, and specific scanners extend it. This is exactly how Durga Antivirus Pro is architected.

OOP Learning Path

flowchart LR
  A[Java Basics] --> B[Java OOP]
  B --> C[Classes & Objects]
  C --> D[Encapsulation]
  C --> E[Inheritance]
  C --> F[Polymorphism]
  C --> G[Abstraction]
  D --> H[Java Collections]
  F --> I[Java Generics]
  F --> J[Java Lambdas]
  B:::current

  classDef current fill:#3b82f6,color:#fff,stroke:#333,stroke-width:2px

Classes and Objects

A class is a blueprint. An object is an instance of that blueprint. Think of a class like a cookie cutter and objects like the cookies you make with it.

public class NetworkDevice {
    // Fields (state)
    String hostname;
    String ipAddress;
    boolean isOnline;
    int portCount;

    // Constructor — builds the object
    public NetworkDevice(String hostname, String ipAddress, int portCount) {
        this.hostname = hostname;
        this.ipAddress = ipAddress;
        this.isOnline = false;
        this.portCount = portCount;
    }

    // Method (behavior)
    public void connect() {
        this.isOnline = true;
        System.out.println(hostname + " connected at " + ipAddress);
    }

    public void disconnect() {
        this.isOnline = false;
        System.out.println(hostname + " disconnected");
    }

    public String getStatus() {
        return hostname + " (" + ipAddress + ") — " +
               (isOnline ? "Online" : "Offline");
    }
}
public class NetworkDemo {
    public static void main(String[] args) {
        NetworkDevice router = new NetworkDevice("Main-Router", "192.168.1.1", 4);
        NetworkDevice switch1 = new NetworkDevice("Floor-1-Switch", "192.168.1.2", 24);

        router.connect();
        switch1.connect();
        System.out.println(router.getStatus());
        System.out.println(switch1.getStatus());

        router.disconnect();
        System.out.println(router.getStatus());
    }
}

Expected output:

Main-Router connected at 192.168.1.1
Floor-1-Switch connected at 192.168.1.2
Main-Router (192.168.1.1) — Online
Floor-1-Switch (192.168.1.2) — Online
Main-Router disconnected
Main-Router (192.168.1.1) — Offline

Each NetworkDevice object has its own hostname, ipAddress, isOnline, and portCount — they don't interfere with each other. The new keyword allocates memory for the object and calls the constructor.

Encapsulation — Protecting Data

Encapsulation means keeping fields private and exposing them through public methods. This prevents external code from setting invalid values directly.

public class SecureDevice {
    private String hostname;
    private String ipAddress;
    private boolean isOnline;

    public SecureDevice(String hostname, String ipAddress) {
        this.hostname = hostname;
        setIpAddress(ipAddress);  // Use setter for validation
    }

    public String getHostname() {
        return hostname;
    }

    public String getIpAddress() {
        return ipAddress;
    }

    public void setIpAddress(String ipAddress) {
        // Validate IP format — security-critical
        if (ipAddress == null || !ipAddress.matches(
                "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$")) {
            throw new IllegalArgumentException("Invalid IP: " + ipAddress);
        }
        this.ipAddress = ipAddress;
    }

    public boolean isOnline() {
        return isOnline;
    }
}
public class EncapsulationDemo {
    public static void main(String[] args) {
        SecureDevice device = new SecureDevice("DNS-01", "8.8.8.8");
        System.out.println(device.getHostname() + " → " + device.getIpAddress());

        // This would throw an exception:
        // device.setIpAddress("not-an-ip");

        // This is safe:
        device.setIpAddress("1.1.1.1");
        System.out.println("Updated: " + device.getIpAddress());
    }
}

Expected output:

DNS-01 → 8.8.8.8
Updated: 1.1.1.1

Why Encapsulation matters for security: Without it, any part of the code could set ipAddress to "'; DROP TABLE devices; --" — a SQL Injection attempt. By forcing all writes through setIpAddress, we validate input in one place. DodaTech uses this pattern across all backend services to sanitize user input before it reaches the database layer.

Inheritance — Reusing Behavior

Inheritance lets a child class reuse fields and methods from a parent class. Use it when you have an "is-a" relationship: a Firewall is a NetworkDevice.

// Parent class
public class NetworkDevice {
    protected String hostname;
    protected String ipAddress;

    public NetworkDevice(String hostname, String ipAddress) {
        this.hostname = hostname;
        this.ipAddress = ipAddress;
    }

    public void configure() {
        System.out.println("Configuring base device " + hostname);
    }
}

// Child class
public class Firewall extends NetworkDevice {
    private int ruleCount;

    public Firewall(String hostname, String ipAddress, int ruleCount) {
        super(hostname, ipAddress);  // Call parent constructor
        this.ruleCount = ruleCount;
    }

    public void addRule(String rule) {
        ruleCount++;
        System.out.println("Rule added to " + hostname + ": " + rule);
    }

    @Override
    public void configure() {
        super.configure();  // Call parent version first
        System.out.println("Applying " + ruleCount + " firewall rules");
    }
}
public class InheritanceDemo {
    public static void main(String[] args) {
        Firewall fw = new Firewall("Edge-FW-01", "10.0.0.1", 50);
        fw.configure();
        fw.addRule("ALLOW HTTPS FROM 10.0.0.0/16");
        fw.addRule("DENY ALL FROM 0.0.0.0/0");
    }
}

Expected output:

Configuring base device Edge-FW-01
Applying 50 firewall rules
Rule added to Edge-FW-01: ALLOW HTTPS FROM 10.0.0.0/16
Rule added to Edge-FW-01: DENY ALL FROM 0.0.0.0/0

The super keyword calls the parent constructor and methods. @Override tells the compiler we're intentionally replacing a method from the parent — this catches typos at compile time.

Polymorphism — Many Forms

Polymorphism lets you treat different objects as instances of a common parent type. The correct method is called at runtime based on the actual object type.

public class PolymorphismDemo {
    public static void main(String[] args) {
        // Polymorphic array — all types are NetworkDevice
        NetworkDevice[] devices = {
            new Firewall("FW-01", "10.0.0.1", 100),
            new NetworkDevice("Switch-01", "10.0.0.2", 48)
        };

        for (NetworkDevice d : devices) {
            d.configure();  // Calls the correct override at runtime
        }
    }
}

Expected output:

Configuring base device FW-01
Applying 100 firewall rules
Configuring base device Switch-01

The compiler only knows these are NetworkDevice references, but at runtime, Java checks the actual object type and calls Firewall.configure() for the first element and NetworkDevice.configure() for the second. This is called dynamic method dispatch.

Abstraction — Hiding Complexity

Abstraction means hiding implementation details and showing only essential features. Use abstract classes when classes share state; use interfaces when you only need to define a contract.

// Abstract class — partial implementation
abstract class ScannerEngine {
    protected String engineName;

    public ScannerEngine(String engineName) {
        this.engineName = engineName;
    }

    // Concrete method — shared behavior
    public void initialize() {
        System.out.println("Initializing " + engineName);
        loadSignatures();
    }

    // Abstract method — subclasses must implement
    protected abstract void loadSignatures();
    public abstract ScanResult scan(String filePath);
}

// Interface — pure contract
interface Reportable {
    String generateReport();
    int getThreatCount();
}

// Concrete implementation
class SignatureScanner extends ScannerEngine implements Reportable {
    private int threatCount = 0;

    public SignatureScanner() {
        super("Signature Scanner v2.1");
    }

    @Override
    protected void loadSignatures() {
        System.out.println("Loaded 15,342 virus signatures");
    }

    @Override
    public ScanResult scan(String filePath) {
        System.out.println("Scanning " + filePath + " with signature matching");
        threatCount++;
        return new ScanResult(filePath, false, "Clean");
    }

    @Override
    public String generateReport() {
        return engineName + ": " + threatCount + " threats found";
    }

    @Override
    public int getThreatCount() {
        return threatCount;
    }
}
// Simplified ScanResult for this example
class ScanResult {
    String filePath;
    boolean infected;
    String message;

    ScanResult(String filePath, boolean infected, String message) {
        this.filePath = filePath;
        this.infected = infected;
        this.message = message;
    }
}

public class AbstractionDemo {
    public static void main(String[] args) {
        SignatureScanner scanner = new SignatureScanner();
        scanner.initialize();
        scanner.scan("/var/logs/system.log");
        scanner.scan("/tmp/downloads/suspicious.exe");

        System.out.println(scanner.generateReport());
    }
}

Expected output:

Initializing Signature Scanner v2.1
Loaded 15,342 virus signatures
Scanning /var/logs/system.log with signature matching
Scanning /tmp/downloads/suspicious.exe with signature matching
Signature Scanner v2.1: 2 threats found

This is the exact pattern DodaTech's antivirus engine uses: a base ScannerEngine class provides initialization logic, and specific scanners (signature-based, heuristic, behavioral) extend it with their own detection algorithms. The Reportable interface ensures every scanner can produce standardized reports for the dashboard.

What Is the Difference Between an Abstract Class and an Interface?

An abstract class can have both concrete and abstract methods, constructors, and fields. An interface can only have abstract methods (before Java 8) or default/static methods (Java 8+). A class can extend only one abstract class but implement multiple interfaces. Use abstract classes for shared state and interfaces for shared behavior contracts.

Common Mistakes Beginners Make

  1. Forgetting super() in constructors: Java automatically inserts a no-arg super() call. If the parent class lacks a no-arg constructor, you must explicitly call super(args) — otherwise the code won't compile.

  2. Using == instead of .equals() for objects: == compares memory addresses, not object content. Always use .equals() for value comparison, and override it in your custom classes.

  3. Overriding without @Override: Without @Override, a typo in the method signature creates an overload instead of an override — and the method won't be called polymorphically.

  4. Making fields public: Exposed fields break Encapsulation. External code can set invalid values and you lose the ability to add validation later. Always use private with getters/setters.

  5. Deep inheritance hierarchies: More than 3-4 levels of inheritance makes code hard to debug and maintain. Prefer composition (has-a) over inheritance (is-a) where possible.

  6. Not using interfaces for loose coupling: Coding to concrete classes instead of interfaces makes your code rigid. List<String> list = new ArrayList<>() lets you switch to LinkedList later without changing other code.

  7. Mutable objects as map keys: If an object's hashCode() changes after insertion into a HashMap, you'll never find it again. Use immutable objects (like String or Integer) as keys.

Practice Questions

  1. What is the difference between method overloading and method overriding?
  2. Why would you make a class abstract?
  3. Can you have multiple inheritance in Java? Why or why not?
  4. What does the super keyword do?
  5. What is polymorphism and why is it useful?

Answers:

  1. Overloading is multiple methods with the same name but different parameters (compile-time). Overriding is replacing a parent method in a child class (runtime).
  2. When the class is too generic to be instantiated on its own and needs subclasses to provide specific implementations for some methods.
  3. No, Java doesn't support multiple inheritance of classes (diamond problem). But a class can implement multiple interfaces, which provides similar flexibility without ambiguity.
  4. super refers to the parent class. It calls parent constructors (super()) and parent methods (super.methodName()).
  5. Polymorphism lets objects of different types be treated as a common parent type, with the correct method called at runtime. It enables flexible, extensible code — you can add new subclasses without changing existing code that uses the parent type.

Challenge

Build a device monitoring system with an abstract Monitor class and concrete implementations (PingMonitor, PortMonitor, SNMPMonitor). Each monitor should implement a Monitorable interface with check() and getStatus() methods. Store them in a polymorphic list and simulate a monitoring cycle.

Real-World Task

Create a plugin system for a log analyzer: define a LogProcessor interface with process(String line) and getResult() methods. Implement ErrorDetector, IPExtractor, and AnomalyScanner processors. Build a LogPipeline that runs all processors on each log line and collects results. This mirrors DodaTech's security log analysis pipeline.

FAQ

What is the difference between an abstract class and an interface in Java?

: An abstract class can have constructors, fields, and both concrete and abstract methods. An interface (pre-Java 8) has only abstract methods. A class can extend one abstract class but implement many interfaces. Use abstract classes for shared state; use interfaces for capability contracts.

Why is Encapsulation important?

: Encapsulation protects internal state from invalid external modification, allows adding validation logic in one place, and lets you change internal implementation without affecting callers. It's a fundamental security principle — unvalidated data entering an object can lead to injection attacks.

Can you override a private method?

: No. Private methods are not inherited, so they cannot be overridden. If you define a method with the same name in a child class, it's a new method, not an override.

What's Next

Java Exception Handling Guide
Java Generics Guide
Java Collections Framework
Design Patterns in Java

Related topics: Java, Java Collections, Design Patterns, OOP, Spring

Practice these concepts daily. Write one class hierarchy, extract one interface, and refactor one procedural script into objects. You'll internalize OOP faster through practice than through reading.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro