Java OOP for Beginners: Learn with Pizza, Pokemon, and Superheroes!

A fun and beginner-friendly guide to Object-Oriented Programming in Java. Learn Classes, Objects, Encapsulation, Inheritance, Polymorphism, and Abstraction with real-world examples.

D
Dery Febriantara Developer
Java OOP for Beginners: Learn with Pizza, Pokemon, and Superheroes!

Welcome to the most fun Java OOP tutorial you’ll ever read! Forget boring textbook examples - we’re going to learn OOP using Pizza, Pokemon, Superheroes, and more!

What is OOP Anyway?

Object-Oriented Programming (OOP) is like playing with LEGO blocks. Instead of writing one giant messy code, you create small, reusable pieces (objects) that work together.

Think of it this way:

  • Without OOP: One giant recipe that makes everything at once (messy kitchen!)
  • With OOP: Separate recipes for dough, sauce, toppings that combine to make pizza (organized!)

The 4 Pillars of OOP

Before we dive in, here’s what we’ll learn:

PillarOne-Line Explanation
EncapsulationHide your secrets, show only what’s needed
InheritanceKids inherit traits from parents
PolymorphismSame action, different results
AbstractionHide complexity, show simplicity

Let’s explore each one with fun examples!


1. Classes and Objects: The Blueprint

What’s a Class?

A Class is like a blueprint or recipe. It defines what something should have and do.

A Object is the actual thing created from that blueprint.

// This is a CLASS - the blueprint for making pizza
class Pizza {
    // Properties (what pizza HAS)
    String name;
    String size;
    int slices;
    double price;

    // Constructor - how to make a pizza
    Pizza(String name, String size, int slices, double price) {
        this.name = name;
        this.size = size;
        this.slices = slices;
        this.price = price;
    }

    // Method (what pizza can DO)
    void describe() {
        System.out.println("🍕 " + name + " Pizza");
        System.out.println("   Size: " + size);
        System.out.println("   Slices: " + slices);
        System.out.println("   Price: $" + price);
    }
}

// Now let's CREATE actual pizzas (OBJECTS)!
public class PizzaShop {
    public static void main(String[] args) {
        // Creating objects from the Pizza class
        Pizza margherita = new Pizza("Margherita", "Medium", 8, 12.99);
        Pizza pepperoni = new Pizza("Pepperoni", "Large", 12, 18.99);
        Pizza hawaiian = new Pizza("Hawaiian", "Small", 6, 9.99);

        // Each pizza is a separate object!
        margherita.describe();
        pepperoni.describe();
        hawaiian.describe();
    }
}

Output:

🍕 Margherita Pizza
   Size: Medium
   Slices: 8
   Price: $12.99
🍕 Pepperoni Pizza
   Size: Large
   Slices: 12
   Price: $18.99
🍕 Hawaiian Pizza
   Size: Small
   Slices: 6
   Price: $9.99

Real-World Analogy

Blueprint (Class)Actual Thing (Object)
Car designYour Honda Civic
House planYour actual house
Cookie cutterEach cookie
Pokemon speciesYour specific Pikachu

2. Encapsulation: Keep Your Secrets Safe!

What is Encapsulation?

Imagine your bank account. You don’t want everyone to directly access and change your balance, right? Encapsulation is about protecting your data and controlling access.

The Problem Without Encapsulation

// BAD: Anyone can mess with your money!
class BadBankAccount {
    public double balance = 1000; // Anyone can change this!
}

public class Hacker {
    public static void main(String[] args) {
        BadBankAccount account = new BadBankAccount();
        account.balance = 1000000; // 😈 Hacked! Free money!
        account.balance = -500;    // 😱 Negative balance?!
    }
}

The Solution: Encapsulation!

class BankAccount {
    // PRIVATE - only this class can access directly
    private double balance;
    private String accountHolder;

    // Constructor
    public BankAccount(String name, double initialDeposit) {
        this.accountHolder = name;
        if (initialDeposit >= 0) {
            this.balance = initialDeposit;
        } else {
            this.balance = 0;
            System.out.println("⚠️ Nice try! Can't start with negative balance.");
        }
    }

    // GETTER - safe way to READ balance
    public double getBalance() {
        return balance;
    }

    // PUBLIC methods - controlled access
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("✅ Deposited $" + amount);
            System.out.println("💰 New balance: $" + balance);
        } else {
            System.out.println("❌ Invalid deposit amount!");
        }
    }

    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println("✅ Withdrew $" + amount);
            System.out.println("💰 Remaining balance: $" + balance);
        } else if (amount > balance) {
            System.out.println("❌ Insufficient funds! You only have $" + balance);
        } else {
            System.out.println("❌ Invalid withdrawal amount!");
        }
    }

    // Show account info
    public void showAccount() {
        System.out.println("👤 Account Holder: " + accountHolder);
        System.out.println("💰 Balance: $" + balance);
    }
}

public class Bank {
    public static void main(String[] args) {
        BankAccount myAccount = new BankAccount("John Doe", 1000);

        myAccount.showAccount();

        // Try to access balance directly? NOPE!
        // myAccount.balance = 1000000; // ❌ ERROR! balance is private

        // Must use methods (safe!)
        myAccount.deposit(500);      // ✅ Works!
        myAccount.withdraw(200);     // ✅ Works!
        myAccount.withdraw(10000);   // ❌ Blocked - insufficient funds
        myAccount.deposit(-100);     // ❌ Blocked - invalid amount
    }
}

Output:

👤 Account Holder: John Doe
💰 Balance: $1000.0
✅ Deposited $500.0
💰 New balance: $1500.0
✅ Withdrew $200.0
💰 Remaining balance: $1300.0
❌ Insufficient funds! You only have $1300.0
❌ Invalid deposit amount!

Encapsulation Cheat Sheet

KeywordWho Can Access?Use Case
privateOnly same classSensitive data (passwords, balance)
protectedSame class + childrenInherited properties
publicEveryoneMethods you want others to use
(default)Same packagePackage-internal stuff

3. Inheritance: Family Traits!

What is Inheritance?

Just like you inherit traits from your parents (eye color, height), classes can inherit properties and methods from other classes!

Pokemon Evolution Example

// PARENT CLASS (Superclass)
class Pokemon {
    protected String name;
    protected String type;
    protected int hp;
    protected int level;

    public Pokemon(String name, String type, int hp) {
        this.name = name;
        this.type = type;
        this.hp = hp;
        this.level = 1;
    }

    public void introduce() {
        System.out.println("🎮 " + name + " appeared!");
        System.out.println("   Type: " + type);
        System.out.println("   HP: " + hp);
        System.out.println("   Level: " + level);
    }

    public void attack() {
        System.out.println("⚔️ " + name + " used TACKLE!");
    }

    public void levelUp() {
        level++;
        hp += 10;
        System.out.println("🎉 " + name + " leveled up to " + level + "!");
    }
}

// CHILD CLASS - inherits from Pokemon
class FirePokemon extends Pokemon {
    private int firepower;

    public FirePokemon(String name, int hp, int firepower) {
        super(name, "Fire 🔥", hp);  // Call parent constructor
        this.firepower = firepower;
    }

    // NEW method only for FirePokemon
    public void flamethrower() {
        System.out.println("🔥 " + name + " used FLAMETHROWER!");
        System.out.println("   Firepower: " + firepower);
    }

    // OVERRIDE parent's attack method
    @Override
    public void attack() {
        System.out.println("🔥 " + name + " used EMBER!");
    }
}

// Another CHILD CLASS
class WaterPokemon extends Pokemon {
    private int waterPressure;

    public WaterPokemon(String name, int hp, int waterPressure) {
        super(name, "Water 💧", hp);
        this.waterPressure = waterPressure;
    }

    public void hydroPump() {
        System.out.println("💧 " + name + " used HYDRO PUMP!");
        System.out.println("   Water Pressure: " + waterPressure);
    }

    @Override
    public void attack() {
        System.out.println("💧 " + name + " used WATER GUN!");
    }
}

// Electric Pokemon
class ElectricPokemon extends Pokemon {
    private int voltage;

    public ElectricPokemon(String name, int hp, int voltage) {
        super(name, "Electric ⚡", hp);
        this.voltage = voltage;
    }

    public void thunderbolt() {
        System.out.println("⚡ " + name + " used THUNDERBOLT!");
        System.out.println("   Voltage: " + voltage + "V");
    }

    @Override
    public void attack() {
        System.out.println("⚡ " + name + " used THUNDER SHOCK!");
    }
}

public class PokemonBattle {
    public static void main(String[] args) {
        // Create different Pokemon
        FirePokemon charmander = new FirePokemon("Charmander", 39, 80);
        WaterPokemon squirtle = new WaterPokemon("Squirtle", 44, 65);
        ElectricPokemon pikachu = new ElectricPokemon("Pikachu", 35, 100);

        // They all have introduce() from parent!
        charmander.introduce();
        System.out.println();

        squirtle.introduce();
        System.out.println();

        pikachu.introduce();
        System.out.println();

        // Battle time!
        System.out.println("=== BATTLE START! ===\n");

        charmander.attack();        // Uses overridden attack
        charmander.flamethrower();  // Special fire move
        System.out.println();

        squirtle.attack();
        squirtle.hydroPump();
        System.out.println();

        pikachu.attack();
        pikachu.thunderbolt();
        pikachu.levelUp();
    }
}

Output:

🎮 Charmander appeared!
   Type: Fire 🔥
   HP: 39
   Level: 1

🎮 Squirtle appeared!
   Type: Water 💧
   HP: 44
   Level: 1

🎮 Pikachu appeared!
   Type: Electric ⚡
   HP: 35
   Level: 1

=== BATTLE START! ===

🔥 Charmander used EMBER!
🔥 Charmander used FLAMETHROWER!
   Firepower: 80

💧 Squirtle used WATER GUN!
💧 Squirtle used HYDRO PUMP!
   Water Pressure: 65

⚡ Pikachu used THUNDER SHOCK!
⚡ Pikachu used THUNDERBOLT!
   Voltage: 100V
🎉 Pikachu leveled up to 2!

Inheritance Diagram

        Pokemon (Parent)
        ├── name, type, hp, level
        ├── introduce()
        ├── attack()
        └── levelUp()

    ┌───────┼───────┐
    │       │       │
    ▼       ▼       ▼
FirePokemon  WaterPokemon  ElectricPokemon
+ firepower  + waterPressure  + voltage
+ flamethrower()  + hydroPump()  + thunderbolt()

4. Polymorphism: Same Name, Different Behavior!

What is Polymorphism?

Poly = Many, Morph = Forms

It means the same method name can behave differently depending on the object!

Superhero Example

// Parent class
class Superhero {
    protected String name;
    protected String realName;

    public Superhero(String name, String realName) {
        this.name = name;
        this.realName = realName;
    }

    public void introduce() {
        System.out.println("🦸 I am " + name + "!");
    }

    // This method will be different for each hero
    public void usePower() {
        System.out.println("💪 Using generic superhero power!");
    }

    public void catchphrase() {
        System.out.println("🗣️ \"With great power comes great responsibility!\"");
    }
}

class SpiderMan extends Superhero {
    public SpiderMan() {
        super("Spider-Man", "Peter Parker");
    }

    @Override
    public void usePower() {
        System.out.println("🕷️ *shoots web*");
        System.out.println("🕸️ THWIP! THWIP!");
    }

    @Override
    public void catchphrase() {
        System.out.println("🗣️ \"My Spider-Sense is tingling!\"");
    }

    public void wallCrawl() {
        System.out.println("🧱 *crawling on walls*");
    }
}

class Batman extends Superhero {
    private int gadgetCount;

    public Batman() {
        super("Batman", "Bruce Wayne");
        this.gadgetCount = 50;
    }

    @Override
    public void usePower() {
        System.out.println("🦇 *throws batarang*");
        System.out.println("💰 *uses expensive gadget*");
        gadgetCount--;
        System.out.println("   Gadgets remaining: " + gadgetCount);
    }

    @Override
    public void catchphrase() {
        System.out.println("🗣️ \"I'm Batman.\"");
    }

    public void summonBatmobile() {
        System.out.println("🚗 *Batmobile arrives*");
    }
}

class Superman extends Superhero {
    public Superman() {
        super("Superman", "Clark Kent");
    }

    @Override
    public void usePower() {
        System.out.println("👀 *heat vision activated*");
        System.out.println("💨 *super breath*");
        System.out.println("✈️ *flying at supersonic speed*");
    }

    @Override
    public void catchphrase() {
        System.out.println("🗣️ \"Up, up, and away!\"");
    }

    public void xrayVision() {
        System.out.println("👁️ *using X-ray vision*");
    }
}

public class HeroAssemble {
    public static void main(String[] args) {
        // POLYMORPHISM: Store different heroes in same type array!
        Superhero[] avengers = new Superhero[3];
        avengers[0] = new SpiderMan();
        avengers[1] = new Batman();
        avengers[2] = new Superman();

        System.out.println("=== HEROES ASSEMBLE! ===\n");

        // Same method call, DIFFERENT behavior!
        for (Superhero hero : avengers) {
            hero.introduce();
            hero.usePower();
            hero.catchphrase();
            System.out.println("-------------------\n");
        }

        // This is the POWER of polymorphism!
        System.out.println("=== POLYMORPHISM MAGIC ===");
        System.out.println("Same method 'usePower()' called on different objects");
        System.out.println("But each hero does something DIFFERENT!");
    }
}

Output:

=== HEROES ASSEMBLE! ===

🦸 I am Spider-Man!
🕷️ *shoots web*
🕸️ THWIP! THWIP!
🗣️ "My Spider-Sense is tingling!"
-------------------

🦸 I am Batman!
🦇 *throws batarang*
💰 *uses expensive gadget*
   Gadgets remaining: 49
🗣️ "I'm Batman."
-------------------

🦸 I am Superman!
👀 *heat vision activated*
💨 *super breath*
✈️ *flying at supersonic speed*
🗣️ "Up, up, and away!"
-------------------

=== POLYMORPHISM MAGIC ===
Same method 'usePower()' called on different objects
But each hero does something DIFFERENT!

Method Overloading (Compile-Time Polymorphism)

Same method name, different parameters:

class Calculator {
    // Same name, different parameters = OVERLOADING

    public int add(int a, int b) {
        System.out.println("Adding 2 integers");
        return a + b;
    }

    public int add(int a, int b, int c) {
        System.out.println("Adding 3 integers");
        return a + b + c;
    }

    public double add(double a, double b) {
        System.out.println("Adding 2 doubles");
        return a + b;
    }

    public String add(String a, String b) {
        System.out.println("Concatenating strings");
        return a + b;
    }
}

public class CalculatorTest {
    public static void main(String[] args) {
        Calculator calc = new Calculator();

        System.out.println(calc.add(5, 3));           // 8
        System.out.println(calc.add(5, 3, 2));        // 10
        System.out.println(calc.add(5.5, 3.3));       // 8.8
        System.out.println(calc.add("Hello, ", "World!")); // Hello, World!
    }
}

5. Abstraction: Hide the Complexity!

What is Abstraction?

When you drive a car, do you need to know how the engine works internally? NO! You just use the steering wheel, pedals, and buttons.

Abstraction = Hide complex details, expose only what’s necessary.

Coffee Machine Example

// ABSTRACT CLASS - can't create object directly
abstract class CoffeeMachine {
    protected String brand;
    protected int waterLevel;

    public CoffeeMachine(String brand) {
        this.brand = brand;
        this.waterLevel = 100;
    }

    // ABSTRACT METHOD - must be implemented by children
    public abstract void brew();

    // CONCRETE METHOD - already implemented
    public void addWater() {
        waterLevel = 100;
        System.out.println("💧 Water tank filled!");
    }

    public void turnOn() {
        System.out.println("🔌 " + brand + " machine turned ON");
        System.out.println("☕ Ready to make coffee!");
    }

    protected void heatWater() {
        System.out.println("🌡️ Heating water...");
    }

    protected void grindBeans() {
        System.out.println("⚙️ Grinding coffee beans...");
    }
}

// Concrete class - implements abstract methods
class EspressoMachine extends CoffeeMachine {
    private int pressure;

    public EspressoMachine(String brand, int pressure) {
        super(brand);
        this.pressure = pressure;
    }

    @Override
    public void brew() {
        System.out.println("\n☕ Making ESPRESSO...");
        grindBeans();
        heatWater();
        System.out.println("💨 Applying " + pressure + " bars of pressure");
        System.out.println("☕ Espresso ready! Strong and bold!");
        waterLevel -= 20;
    }

    public void makeDoubleShot() {
        System.out.println("☕☕ Double shot coming up!");
        brew();
        brew();
    }
}

class DripCoffeeMaker extends CoffeeMachine {
    private int cups;

    public DripCoffeeMaker(String brand, int cups) {
        super(brand);
        this.cups = cups;
    }

    @Override
    public void brew() {
        System.out.println("\n☕ Making DRIP COFFEE for " + cups + " cups...");
        heatWater();
        System.out.println("💧 Dripping water through filter...");
        System.out.println("⏰ Please wait 5 minutes...");
        System.out.println("☕ Drip coffee ready! Smooth and mild!");
        waterLevel -= (cups * 15);
    }
}

class FrenchPress extends CoffeeMachine {
    private int steepMinutes;

    public FrenchPress(String brand, int steepMinutes) {
        super(brand);
        this.steepMinutes = steepMinutes;
    }

    @Override
    public void brew() {
        System.out.println("\n☕ Making FRENCH PRESS coffee...");
        grindBeans();
        System.out.println("🫖 Adding coarse grounds to press...");
        heatWater();
        System.out.println("⏰ Steeping for " + steepMinutes + " minutes...");
        System.out.println("🔽 Pressing down the plunger...");
        System.out.println("☕ French press ready! Rich and full-bodied!");
        waterLevel -= 30;
    }
}

public class CoffeeShop {
    public static void main(String[] args) {
        // Can't do this! Abstract class can't be instantiated
        // CoffeeMachine machine = new CoffeeMachine("Generic"); // ❌ ERROR!

        // But we CAN create concrete implementations
        EspressoMachine espresso = new EspressoMachine("DeLonghi", 15);
        DripCoffeeMaker drip = new DripCoffeeMaker("Mr. Coffee", 12);
        FrenchPress french = new FrenchPress("Bodum", 4);

        System.out.println("=== COFFEE SHOP OPENS ===\n");

        espresso.turnOn();
        espresso.brew();

        System.out.println("\n---");

        drip.turnOn();
        drip.brew();

        System.out.println("\n---");

        french.turnOn();
        french.brew();

        // User doesn't need to know HOW each machine works internally
        // They just call brew() and get coffee!
        System.out.println("\n=== ABSTRACTION BENEFIT ===");
        System.out.println("Customer just presses 'brew' button");
        System.out.println("Each machine handles it differently inside!");
    }
}

Interface: Pure Abstraction

An interface is like a contract - it says WHAT a class must do, but not HOW:

// INTERFACE - 100% abstract contract
interface Playable {
    void play();
    void pause();
    void stop();
}

interface Recordable {
    void record();
    void saveRecording(String filename);
}

// A class can implement MULTIPLE interfaces!
class MusicPlayer implements Playable {
    private String currentSong;

    @Override
    public void play() {
        System.out.println("🎵 Playing: " + currentSong);
    }

    @Override
    public void pause() {
        System.out.println("⏸️ Paused: " + currentSong);
    }

    @Override
    public void stop() {
        System.out.println("⏹️ Stopped playing");
        currentSong = null;
    }

    public void loadSong(String song) {
        currentSong = song;
        System.out.println("📂 Loaded: " + song);
    }
}

class VoiceRecorder implements Playable, Recordable {
    private boolean isRecording = false;

    @Override
    public void play() {
        System.out.println("🔊 Playing recording...");
    }

    @Override
    public void pause() {
        System.out.println("⏸️ Playback paused");
    }

    @Override
    public void stop() {
        System.out.println("⏹️ Stopped");
        isRecording = false;
    }

    @Override
    public void record() {
        isRecording = true;
        System.out.println("🔴 Recording... Speak now!");
    }

    @Override
    public void saveRecording(String filename) {
        System.out.println("💾 Saved recording as: " + filename);
    }
}

public class MediaApp {
    public static void main(String[] args) {
        MusicPlayer spotify = new MusicPlayer();
        spotify.loadSong("Bohemian Rhapsody");
        spotify.play();
        spotify.pause();
        spotify.stop();

        System.out.println();

        VoiceRecorder recorder = new VoiceRecorder();
        recorder.record();
        recorder.stop();
        recorder.saveRecording("my_voice.mp3");
        recorder.play();
    }
}

Putting It All Together: Game Example!

Let’s build a simple RPG game using ALL OOP concepts:

// ABSTRACTION - Abstract base class
abstract class GameCharacter {
    // ENCAPSULATION - private fields with protected access for children
    private String name;
    protected int health;
    protected int maxHealth;
    protected int attackPower;
    protected int defense;
    protected int level;
    protected int experience;

    public GameCharacter(String name, int health, int attack, int defense) {
        this.name = name;
        this.health = health;
        this.maxHealth = health;
        this.attackPower = attack;
        this.defense = defense;
        this.level = 1;
        this.experience = 0;
    }

    // ENCAPSULATION - getter
    public String getName() { return name; }
    public int getHealth() { return health; }
    public boolean isAlive() { return health > 0; }

    // ABSTRACTION - abstract method
    public abstract void specialAttack(GameCharacter target);

    // Concrete methods
    public void attack(GameCharacter target) {
        int damage = Math.max(1, attackPower - target.defense / 2);
        target.takeDamage(damage);
        System.out.println("⚔️ " + name + " attacks " + target.getName() +
                           " for " + damage + " damage!");
    }

    public void takeDamage(int damage) {
        health = Math.max(0, health - damage);
        System.out.println("💔 " + name + " has " + health + "/" + maxHealth + " HP");
    }

    public void heal(int amount) {
        int oldHealth = health;
        health = Math.min(maxHealth, health + amount);
        System.out.println("💚 " + name + " healed for " + (health - oldHealth) + " HP!");
    }

    public void gainExp(int exp) {
        experience += exp;
        System.out.println("✨ " + name + " gained " + exp + " XP!");
        if (experience >= level * 100) {
            levelUp();
        }
    }

    protected void levelUp() {
        level++;
        maxHealth += 10;
        health = maxHealth;
        attackPower += 3;
        defense += 2;
        experience = 0;
        System.out.println("🎉 " + name + " LEVELED UP to Level " + level + "!");
    }

    public void showStats() {
        System.out.println("╔══════════════════════════╗");
        System.out.println("║ " + name);
        System.out.println("║ Level: " + level);
        System.out.println("║ HP: " + health + "/" + maxHealth);
        System.out.println("║ ATK: " + attackPower + " | DEF: " + defense);
        System.out.println("╚══════════════════════════╝");
    }
}

// INHERITANCE - Different character classes
class Warrior extends GameCharacter {
    private int rage = 0;

    public Warrior(String name) {
        super(name, 120, 15, 10);
    }

    // POLYMORPHISM - Override special attack
    @Override
    public void specialAttack(GameCharacter target) {
        rage += 20;
        if (rage >= 50) {
            int damage = attackPower * 2;
            target.takeDamage(damage);
            System.out.println("🔥 " + getName() + " uses BERSERKER RAGE!");
            System.out.println("💥 MASSIVE " + damage + " damage!");
            rage = 0;
        } else {
            attack(target);
            System.out.println("😤 Rage building: " + rage + "/50");
        }
    }

    public void shieldBlock() {
        defense += 5;
        System.out.println("🛡️ " + getName() + " raises shield! DEF +" + 5);
    }
}

class Mage extends GameCharacter {
    private int mana;
    private int maxMana;

    public Mage(String name) {
        super(name, 70, 20, 5);
        this.mana = 100;
        this.maxMana = 100;
    }

    @Override
    public void specialAttack(GameCharacter target) {
        if (mana >= 30) {
            mana -= 30;
            int damage = attackPower * 3;
            target.takeDamage(damage);
            System.out.println("🔮 " + getName() + " casts FIREBALL!");
            System.out.println("🔥 " + damage + " magical damage!");
            System.out.println("💙 Mana: " + mana + "/" + maxMana);
        } else {
            System.out.println("💙 Not enough mana! Mana: " + mana + "/" + maxMana);
            attack(target);
        }
    }

    public void meditate() {
        int restore = 40;
        mana = Math.min(maxMana, mana + restore);
        System.out.println("🧘 " + getName() + " meditates. Mana +" + restore);
    }
}

class Archer extends GameCharacter {
    private int arrows;

    public Archer(String name) {
        super(name, 85, 18, 7);
        this.arrows = 20;
    }

    @Override
    public void specialAttack(GameCharacter target) {
        if (arrows >= 3) {
            arrows -= 3;
            System.out.println("🏹 " + getName() + " fires TRIPLE SHOT!");
            for (int i = 0; i < 3; i++) {
                int damage = attackPower;
                target.takeDamage(damage);
            }
            System.out.println("🎯 Arrows remaining: " + arrows);
        } else {
            System.out.println("🎯 Not enough arrows! Arrows: " + arrows);
            attack(target);
        }
    }

    public void restockArrows() {
        arrows = 20;
        System.out.println("🏹 " + getName() + " restocked arrows!");
    }
}

// Enemy class
class Monster extends GameCharacter {
    private String type;

    public Monster(String name, String type, int health, int attack, int defense) {
        super(name, health, attack, defense);
        this.type = type;
    }

    @Override
    public void specialAttack(GameCharacter target) {
        int damage = attackPower + 5;
        target.takeDamage(damage);
        System.out.println("👹 " + getName() + " uses " + type.toUpperCase() + " ATTACK!");
    }
}

public class RPGGame {
    public static void main(String[] args) {
        System.out.println("╔═══════════════════════════════════╗");
        System.out.println("║     ⚔️  EPIC RPG BATTLE  ⚔️      ║");
        System.out.println("╚═══════════════════════════════════╝\n");

        // Create heroes (POLYMORPHISM - different types in same array)
        GameCharacter[] heroes = {
            new Warrior("Conan"),
            new Mage("Gandalf"),
            new Archer("Legolas")
        };

        // Create monster
        Monster dragon = new Monster("Dragon", "fire", 200, 25, 15);

        // Show all stats
        System.out.println("=== YOUR PARTY ===");
        for (GameCharacter hero : heroes) {
            hero.showStats();
        }

        System.out.println("\n=== ENEMY ===");
        dragon.showStats();

        System.out.println("\n=== BATTLE BEGIN! ===\n");

        // Battle simulation
        int round = 1;
        while (dragon.isAlive() && anyHeroAlive(heroes)) {
            System.out.println("--- Round " + round + " ---\n");

            // Heroes attack
            for (GameCharacter hero : heroes) {
                if (hero.isAlive() && dragon.isAlive()) {
                    hero.specialAttack(dragon);
                    System.out.println();
                }
            }

            // Dragon attacks random hero
            if (dragon.isAlive()) {
                GameCharacter target = getRandomAliveHero(heroes);
                if (target != null) {
                    dragon.specialAttack(target);
                    System.out.println();
                }
            }

            round++;

            if (round > 10) break; // Safety limit
        }

        // Battle result
        System.out.println("\n=== BATTLE END ===");
        if (!dragon.isAlive()) {
            System.out.println("🎉 VICTORY! The dragon has been slain!");
            for (GameCharacter hero : heroes) {
                if (hero.isAlive()) {
                    hero.gainExp(150);
                }
            }
        } else {
            System.out.println("💀 DEFEAT... The party has fallen...");
        }
    }

    static boolean anyHeroAlive(GameCharacter[] heroes) {
        for (GameCharacter hero : heroes) {
            if (hero.isAlive()) return true;
        }
        return false;
    }

    static GameCharacter getRandomAliveHero(GameCharacter[] heroes) {
        java.util.List<GameCharacter> alive = new java.util.ArrayList<>();
        for (GameCharacter hero : heroes) {
            if (hero.isAlive()) alive.add(hero);
        }
        if (alive.isEmpty()) return null;
        return alive.get((int)(Math.random() * alive.size()));
    }
}

Quick Reference Cheat Sheet

OOP Keywords

KeywordPurposeExample
classDefine a blueprintclass Dog { }
newCreate an objectDog buddy = new Dog();
extendsInherit from classclass Puppy extends Dog
implementsImplement interfaceclass Dog implements Pet
abstractCan’t instantiate directlyabstract class Animal
interfacePure contractinterface Runnable
@OverrideReplace parent method@Override void bark()
superCall parentsuper.bark();
thisCurrent objectthis.name = name;
staticBelongs to class, not objectstatic int count;
finalCan’t change/overridefinal int MAX = 100;

When to Use What?

ScenarioUse
Share code between related classesInheritance
Define a contract without implementationInterface
Share some code, force some implementationAbstract class
Protect data from direct accessEncapsulation
Same method, different behaviorPolymorphism
Hide complex implementationAbstraction

Practice Exercises

Try building these on your own:

  1. Zoo System: Create Animal class with Lion, Elephant, Penguin children
  2. Vehicle System: VehicleCar, Motorcycle, Bicycle
  3. Social Media: Post class with TextPost, ImagePost, VideoPost
  4. E-commerce: ProductElectronics, Clothing, Food
  5. Music App: InstrumentGuitar, Piano, Drums

Conclusion

Congratulations! You’ve learned the 4 pillars of OOP:

  • Encapsulation - Protect your data like a bank protects your money
  • Inheritance - Share traits like family members share genes
  • Polymorphism - Same command, different actions (like “play” on different instruments)
  • Abstraction - Hide complexity (like driving without knowing engine internals)

Now go build something awesome! Start with simple classes, then add complexity gradually. Practice makes perfect!

Happy coding! 🚀


Further Reading