Abstraction with abstract classes

What is the area of "a shape"? There's no sensible answer until you know it's a circle or a rectangle. An abstract class lets you say "every shape has an area" without pretending to know how, while still sharing the code that is common. You'll see abstract methods, why new Shape() doesn't compile, how constructors in abstract classes run, the template method pattern, and then practise with 10 problems.

Module 7 · OOP Pillars⏱ 23 minIntermediate10 practice problems
1 The story

The hexagon with zero area

Riya's poster-design tool has a Shape parent with double area() { return 0; } as a default. A teammate adds Hexagon, forgets to override area(), and the paint estimate for every hexagon is Rs 0. Nobody notices for a week, because the code compiled and ran without a single error.

Fake default: silent bug class Shape area() { return 0; } Hexagon forgot area() → 0.0 compiles, runs, gives wrong answers Abstract: javac enforces it abstract class Shape abstract double area(); Hexagon must write area() to compile the mistake can't reach production myphatshala.in
Riya

Sir, the hexagon bug compiled perfectly. How do I force every new shape to write its own area?

Arjun Sir

Stop giving Shape a fake answer. Declare abstract double area(); with no body, and mark the class abstract. Now any concrete shape that forgets area() won't compile.

Riya

But then can I still create a Shape?

Arjun Sir

No, and you shouldn't: a "shape" with no form isn't real. You create circles and hexagons; Shape holds what they share. Showing what matters and hiding the how is abstraction, the fourth pillar.

2 Definition

What is abstraction, and what is an abstract class?

Formal definition (write this in exams)

Abstraction is the OOP principle of exposing only the essential behaviour of an object and hiding its implementation details. In Java it is achieved with abstract classes and interfaces. An abstract class is declared with the abstract keyword and cannot be instantiated. It may contain abstract methods (declared without a body, ending in a semicolon) as well as concrete methods, fields and constructors. A concrete (non-abstract) subclass must implement every inherited abstract method; otherwise it must itself be declared abstract.

In simple words

An abstract class is a half-filled form. It says "every shape has a name (filled in here) and an area (you fill this in)". You can't submit a half-filled form; a child class must complete it.

Words you'll meet in this lesson

Abstract methodA method with no body: abstract double area();
Abstract classA class marked abstract; can't be created with new.
Concrete classA normal class with every method implemented; can be instantiated.
Partial implementationShared fields and methods written once in the abstract parent.
Template methodA (usually final) parent method that fixes the steps and calls abstract steps.
HookA concrete method with a default that children may override if they want.

Abstract classes at a glance

Create one?new Shape() → error: Shape is abstract; cannot be instantiated
Use as a type?Yes: Shape s = new Circle(7);, Shape[] canvas, method parameters
ConstructorsAllowed, and they run when a child is created, through super(...)
Abstract methodsZero or more. A class with even one abstract method must be declared abstract.
Forgot to implement?Hexagon is not abstract and does not override abstract method area() in Shape
Illegal with abstractfinal, private, static ("illegal combination of modifiers"), because they all prevent overriding

Exam tip: "Can an abstract class have a constructor?" Yes, and it runs whenever a concrete subclass is instantiated. "Can an abstract class have no abstract methods?" Yes; it then simply can't be instantiated. "Can an abstract method be final?" No: final forbids the overriding that abstract demands.

3 Types and ways to use it

Three ways abstract classes are used

A required contract

Abstract methods force every child to supply the varying part: area(), calculatePay().

must implement

Shared partial code

Fields, a constructor and helper methods written once: name, describe(), validation.

reuse

Template method

The parent fixes the order of steps; children fill in individual steps.

fixed skeleton

Contract and shared code together

abstract class Shape {
    private final String name;

    Shape(String name) {                      // constructors are allowed
        this.name = name;
    }

    abstract double area();                   // no body: each shape decides

    String describe() {                       // shared, uses the abstract method
        return String.format("%s: area %.2f", name, area());
    }
}

class Circle extends Shape {
    private final double r;
    Circle(String name, double r) { super(name); this.r = r; }

    @Override
    double area() { return Math.PI * r * r; }
}

class Rect extends Shape {
    private final double w, h;
    Rect(String name, double w, double h) { super(name); this.w = w; this.h = h; }

    @Override
    double area() { return w * h; }
}

public class Main {
    public static void main(String[] args) {
        Shape[] canvas = { new Circle("logo", 7), new Rect("banner", 40, 15) };
        for (Shape s : canvas) {
            System.out.println(s.describe());
        }
    }
}
logo: area 153.94 banner: area 600.00
Concrete classAbstract class
new allowed?YesNo (compile error)
Abstract methods?None allowedZero or more
Fields, constructors, concrete methodsYesYes
Can be a variable / parameter typeYesYes
PurposeModel a real, complete thingModel a general idea and share code with its children

Constructor trap: an abstract class's constructor runs before the child's fields are initialised (Lesson 7.3). If it calls an abstract method, the child's version runs early and sees zeros and nulls. Call abstract methods from ordinary methods like describe(), never from the constructor.

4 Step by step

Building an abstract hierarchy

  1. Mark the class abstract

    abstract class Shape { ... }
    javac sets the ACC_ABSTRACT flag in the class file. Both javac and the JVM refuse to create instances of such a class.
  2. Declare what every child must provide

    abstract double area();     // semicolon, no braces
    An abstract method is a promise with no code. In the class file it has a signature but no bytecode. Writing braces gives abstract methods cannot have a body.
  3. Write the shared parts once

    Shape(String name) { this.name = name; }
    String describe() { return name + ": " + area(); }
    describe() can call area() even though Shape has no body for it: at run time this is always a concrete child, so dynamic dispatch finds a real implementation.
  4. Complete it in a concrete child

    class Circle extends Shape {
        Circle(String name, double r) { super(name); this.r = r; }
        @Override double area() { return Math.PI * r * r; }
    }
    The child must call a Shape constructor (here super(name)) and implement every abstract method. Miss one and javac names it in the error.
  5. Program to the abstract type

    Shape s = new Circle("logo", 7);
    System.out.println(s.describe());
    Code that uses Shape never needs to know which concrete class it has. New shapes plug in without changing it.
5 See it happen

From compile errors to a working canvas: both views

Left: the code and what javac or the console says. Right: the class hierarchy and the object built from it.

An abstract Shape 6 steps
What you see Shape s = new Shape("logo"); class Hexagon extends Shape { } Shape s = new Circle("logo", 7); System.out.println(s.area()); System.out.println(s.describe()); for (Shape s : canvas) describe() error: Shape is abstract; cannot be instantiated error: Hexagon is not abstract and does not override abstract method area() in Shape (Shape's constructor ran first) 153.93804002589985 logo: area 153.94 logo: area 153.94 banner: area 600.00 You can't create a "shape" with no form. Forgetting area() is now a compile error. Abstract type, concrete object. The child's area() runs. Shared code calls the abstract method. Any new shape works with the same loop. myphatshala.in
Behind the screen abstract Shape name; Shape(name) area(); describe() {...} new Shape(): no object is ever made Hexagon area() missing ✗ Circle area() = πr² Rect area() = w × h #1 Circle name = "logo" r = 7.0 javac checks every abstract method is implemented. One object: Shape's part (name) + Circle's part (r). invokevirtual area → found in Circle. describe() in Shape → this.area() → Circle's. Each concrete child completes the contract. myphatshala.in
6 Real-world use

Abstract classes in real systems

Abstract classes appear where several classes share real code but differ in a few steps.

Watch it with an example: a bank's loan products

Every loan uses the same EMI formula, but each loan type has its own interest rate. An abstract Loan class holds the formula and leaves the rate to its children.

abstract Loan, concrete loan types 6 steps
Animated example Loan l = new Loan(500000, 36); class GoldLoan extends Loan { } Loan l = new HomeLoan(3000000, 240); l.emi(); // final, in Loan l = new CarLoan(600000, 60); for (Loan x : loans) x.emi(); abstract class Loan abstract double rate(); final double emi() {…} HomeLoan rate() 8.5 CarLoan rate() 9.5 GoldLoan rate() missing error: Loan is abstract; cannot be instantiated error: GoldLoan is not abstract and does not override abstract method rate() in Loan Loan's constructor runs first (super) principal 30,00,000 240 months emi() calls rate() → HomeLoan: 8.5% EMI Rs 26,035 Rs 26,035 same emi() code → CarLoan: 9.5% EMI Rs 12,601 Rs 12,601 Home Rs 26,035 Car Rs 12,601 one formula, many rates An abstract class can't be created on its own. Forgetting the abstract method is a compile error. A concrete loan; the abstract parent's part is built first. Shared final formula, child-supplied rate. Same formula, different rate: a different EMI. New loan type = one class with one rate(). myphatshala.in
validate (fixed)charge (varies)receipt (fixed)

1. Fixed workflows with varying steps

A payment flow always validates, charges, then sends a receipt. An abstract PaymentProcessor makes that order final; card, UPI and net-banking processors only implement charge().

process()→validate → charge() → receipt→same order always
abstract classInputStreamabstract read()

2. The Java library itself

java.io.InputStream is abstract: subclasses implement read() for one byte, and inherit ready-made methods like read(byte[]) built on top of it. AbstractList does the same for collections.

FileInputStream→implements read()→inherits the rest
Report (abstract)GSTSales

3. Reports and exports

Every report has a header, body and footer. The abstract parent formats header and footer (logo, date, page numbers); each report type implements only body().

generate()→header + body() + footer→consistent PDFs
abstract classBaseTestsetUp() shared

4. Shared test setup

Test suites often use an abstract base class that starts a test database and loads sample data; each concrete test class adds its own test methods.

BaseTest→setUp() once→many test classes

Examples are simplified. When there's no shared code or state, an interface (Lesson 7.7) is usually preferred over an abstract class.

7 Run the code

A template method for payslips

abstract class Employee {
    private final String name;

    Employee(String name) {
        this.name = name;
    }

    abstract double gross();                 // each type decides

    double deductions() {                    // hook: a default children may change
        return gross() * 0.10;
    }

    final String payslip() {                 // template method: fixed steps
        double g = gross();
        double d = deductions();
        return String.format("%-6s gross %9.2f  less %8.2f  net %9.2f", name, g, d, g - d);
    }
}

class FullTime extends Employee {
    private final double basic;
    FullTime(String name, double basic) { super(name); this.basic = basic; }

    @Override
    double gross() { return basic * 1.4; }
}

class Contractor extends Employee {
    private final double rate;
    private final int hours;
    Contractor(String name, double rate, int hours) { super(name); this.rate = rate; this.hours = hours; }

    @Override
    double gross() { return rate * hours; }

    @Override
    double deductions() { return 0; }         // no PF for contractors in this example
}

public class Main {
    public static void main(String[] args) {
        Employee[] staff = { new FullTime("Anil", 40000), new Contractor("Sara", 800, 120) };
        for (Employee e : staff) {
            System.out.println(e.payslip());
        }
    }
}
Anil gross 56000.00 less 5600.00 net 50400.00 Sara gross 96000.00 less 0.00 net 96000.00
  • abstract double gross();Every concrete employee type must say how gross pay is computed.
  • double deductions()A hook: a sensible default that Contractor overrides.
  • final String payslip()The template method: children can't change the order of steps or the format.
  • super(name)The abstract class's constructor runs for every concrete employee.
8 Line by line

Shared code calling the child's method

Watch describe(), written in the abstract class, call area(), which only the child implements.

Worked examples: real problems, solved and traced in memory

Three business problems solved with abstract classes. Run each solution, then step through it: watch the abstract class's constructor run for a concrete child, and watch shared code in the abstract class call methods that only the child implements.

Example 1Design tool · print estimate

Shared describe(), child-specific area()

Problem statement

A sticker printer prices every shape by its area. Shape stores the sticker's name and has a shared describe(); each concrete shape must supply area().

Input
Circle "logo" r=1; Rect "tag" 4 × 2
Expected output
logo 3.14
tag 8.0

Approach

  1. Make Shape abstract with a constructor for the name and an abstract area().
  2. describe() lives in Shape and calls area().
  3. Create one circle and one rectangle through Shape variables and describe both.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2HR · payroll

A final payslip template with a hook

Problem statement

Every payslip shows gross, deductions and net in the same order. Full-time staff get 10% deducted; contractors have no deductions. Write it so no employee type can change the payslip's structure.

Input
Contractor rate Rs 500 × 100 hours
Expected output
Net pay: 50000.0

Approach

  1. Abstract Employee with abstract gross() and a hook deductions() (default 10%).
  2. A final net() combines them in a fixed way.
  3. Contractor implements gross() and overrides the hook to return 0.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3Logistics · trip pricing

Partial implementation of a trip cost

Problem statement

A delivery company prices trips as a base charge plus a per-km rate. The formula is the same for all vehicles; base charge and rate differ. A van has base Rs 100 and Rs 18/km. Price a 10 km trip.

Input
Van, 10 km
Expected output
Trip cost: Rs 280

Approach

  1. Abstract Vehicle implements tripCost(km) once.
  2. It calls two abstract methods, base() and perKm().
  3. Van fills in just those two numbers.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 7.6 in one screen

  • Abstraction: show what an object does, hide how. Tools: abstract classes and interfaces.
  • abstract class: can't be instantiated; can still be a variable, parameter or array type.
  • abstract method: no body; any class containing one must be abstract.
  • Concrete subclasses must implement every abstract method, or be abstract themselves.
  • Abstract classes can have fields, constructors (run via super) and concrete methods.
  • Template method: a final parent method fixes the steps; children fill in abstract steps; hooks give defaults.
  • abstract can't combine with final, private or static. Don't call abstract methods from constructors.

Interview and viva questions

What is abstraction, and how is it different from encapsulation?

Abstraction is about what to show: exposing essential operations (area(), send()) and hiding implementation. Encapsulation is about protecting data: keeping fields private and controlling changes. They work together: an abstract type defines the operations; each concrete class encapsulates its own data.

Why can't we create an object of an abstract class?

Because it may have methods with no implementation. If new Shape() were allowed, calling area() on it would have no code to run. The class represents a general idea, completed only by concrete subclasses.

Why does an abstract class have a constructor if it can't be instantiated?

To initialise its own fields when a concrete subclass is created. Every subclass constructor calls a superclass constructor, so the abstract class's constructor runs as part of building the child object, for example to validate and set a name field.

Can an abstract class have no abstract methods? Why would you do that?

Yes. You'd do it to prevent direct instantiation of a class that only makes sense as a base, while still providing complete shared code; HttpServlet-style base classes are a known example of this idea.

What is the template method pattern?

A design where a parent class defines an algorithm's skeleton in a method (often final), calling abstract "step" methods and optional hook methods that subclasses implement or override. It keeps the order of steps consistent while letting each subclass vary individual steps.

Practice problems

For each problem, decide what is common (goes in the abstract class as fields and concrete methods) and what varies (becomes an abstract method). The last three are business problems.

P1Easy

Abstract Animal

Make Animal abstract with an abstract sound() and a concrete speak() that prints the animal's name and sound. Create a Cow and a Parrot.

Input
Cow "Gauri", Parrot "Mithu"
Expected output
Gauri says Moo
Mithu says Ram Ram
Show solution and explanation
abstract class Animal {
    private final String name;

    Animal(String name) {
        this.name = name;
    }

    abstract String sound();

    void speak() {
        System.out.println(name + " says " + sound());
    }
}

class Cow extends Animal {
    Cow(String name) { super(name); }

    @Override
    String sound() { return "Moo"; }
}

class Parrot extends Animal {
    Parrot(String name) { super(name); }

    @Override
    String sound() { return "Ram Ram"; }
}

public class Main {
    public static void main(String[] args) {
        Animal[] zoo = { new Cow("Gauri"), new Parrot("Mithu") };
        for (Animal a : zoo) {
            a.speak();
        }
    }
}
Gauri says Moo Mithu says Ram Ram

How it works: speak() is written once in the abstract class and calls the abstract sound(); each concrete animal supplies its own. new Animal("x") would not compile.

P2Easy

Constructor of an abstract class

Show that an abstract class's constructor runs: Account (abstract) prints "Account opened: <no>" in its constructor; SavingsAccount prints its own message. Create one savings account.

Input
SavingsAccount("SB-2041")
Expected output
Account opened: SB-2041
Savings account ready
SB-2041 earns 3.5%
Show solution and explanation
abstract class Account {
    protected final String no;

    Account(String no) {
        this.no = no;
        System.out.println("Account opened: " + no);
    }

    abstract double interestRate();
}

class SavingsAccount extends Account {
    SavingsAccount(String no) {
        super(no);
        System.out.println("Savings account ready");
    }

    @Override
    double interestRate() { return 3.5; }
}

public class Main {
    public static void main(String[] args) {
        Account a = new SavingsAccount("SB-2041");
        System.out.println(a.no + " earns " + a.interestRate() + "%");
    }
}
Account opened: SB-2041 Savings account ready SB-2041 earns 3.5%

How it works: you can't call new Account(...), but super(no) in the child runs Account's constructor as part of building the SavingsAccount, so its message prints first.

P3Easy

Shapes with an abstract area

Write abstract Shape with abstract area() and perimeter(). Implement Rectangle and Circle and print both values for each, to 2 decimals.

Input
Rectangle 8×5, Circle r=4
Expected output
Rectangle: area 40.00, perimeter 26.00
Circle: area 50.27, perimeter 25.13
Show solution and explanation
abstract class Shape {
    abstract double area();
    abstract double perimeter();

    void print() {
        System.out.printf("%s: area %.2f, perimeter %.2f%n",
                getClass().getSimpleName(), area(), perimeter());
    }
}

class Rectangle extends Shape {
    private final double l, w;
    Rectangle(double l, double w) { this.l = l; this.w = w; }

    @Override double area() { return l * w; }
    @Override double perimeter() { return 2 * (l + w); }
}

class Circle extends Shape {
    private final double r;
    Circle(double r) { this.r = r; }

    @Override double area() { return Math.PI * r * r; }
    @Override double perimeter() { return 2 * Math.PI * r; }
}

public class Main {
    public static void main(String[] args) {
        new Rectangle(8, 5).print();
        new Circle(4).print();
    }
}
Rectangle: area 40.00, perimeter 26.00 Circle: area 50.27, perimeter 25.13

How it works: both abstract methods must be implemented in each concrete shape; leaving out either one is a compile error. print() is shared, and getClass() reports the real class name.

P4Medium

Template method: making chai and coffee

Every hot drink is made in the same order: boil water, brew, pour, add extras. Write abstract HotDrink with a final make() and abstract brew() and extras(). Implement Chai and Coffee.

Input
make Chai, then Coffee
Expected output
Boil water
Boil tea leaves with milk, ginger, elaichi
Pour into cup
Add sugar
---
Boil water
Add filter coffee decoction
Pour into cup
Add hot milk and froth
---
Show solution and explanation
abstract class HotDrink {
    final void make() {
        System.out.println("Boil water");
        brew();
        System.out.println("Pour into cup");
        extras();
        System.out.println("---");
    }

    abstract void brew();
    abstract void extras();
}

class Chai extends HotDrink {
    @Override void brew() { System.out.println("Boil tea leaves with milk, ginger, elaichi"); }
    @Override void extras() { System.out.println("Add sugar"); }
}

class Coffee extends HotDrink {
    @Override void brew() { System.out.println("Add filter coffee decoction"); }
    @Override void extras() { System.out.println("Add hot milk and froth"); }
}

public class Main {
    public static void main(String[] args) {
        HotDrink[] orders = { new Chai(), new Coffee() };
        for (HotDrink d : orders) {
            d.make();
        }
    }
}
Boil water Boil tea leaves with milk, ginger, elaichi Pour into cup Add sugar --- Boil water Add filter coffee decoction Pour into cup Add hot milk and froth ---

How it works: make() is the template method. It's final, so no drink can skip boiling water or change the order. Each subclass fills in only the steps that differ.

P5Medium

A hook with a default

Extend the template idea: HotDrink gets a hook boolean wantsSugar() returning true by default; make() adds sugar only if it returns true. A GreenTea overrides the hook to return false.

Input
make Chai, then GreenTea
Expected output
Chai brewed
  + sugar
Green tea steeped
Show solution and explanation
abstract class HotDrink {
    final void make() {
        brew();
        if (wantsSugar()) {
            System.out.println("  + sugar");
        }
    }

    abstract void brew();

    boolean wantsSugar() {          // hook: optional to override
        return true;
    }
}

class Chai extends HotDrink {
    @Override void brew() { System.out.println("Chai brewed"); }
}

class GreenTea extends HotDrink {
    @Override void brew() { System.out.println("Green tea steeped"); }

    @Override boolean wantsSugar() { return false; }
}

public class Main {
    public static void main(String[] args) {
        new Chai().make();
        new GreenTea().make();
    }
}
Chai brewed + sugar Green tea steeped

How it works: an abstract method must be implemented; a hook has a sensible default and is overridden only when needed. Chai uses the default; GreenTea turns sugar off.

P6Medium

Abstract class with no abstract methods

A BaseEntity gives every record an auto-incremented id and a label(), but should never be created on its own. Make it abstract (with no abstract methods) and create two Product records.

Input
Product "Rice 5kg", Product "Dal 1kg"
Expected output
Product#1 Rice 5kg
Product#2 Dal 1kg
Show solution and explanation
abstract class BaseEntity {
    private static int nextId = 1;
    protected final int id;

    BaseEntity() {
        id = nextId++;
    }

    String label() {
        return getClass().getSimpleName() + "#" + id;
    }
}

class Product extends BaseEntity {
    private final String name;

    Product(String name) {
        this.name = name;
    }

    String show() {
        return label() + " " + name;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new Product("Rice 5kg").show());
        System.out.println(new Product("Dal 1kg").show());
    }
}
Product#1 Rice 5kg Product#2 Dal 1kg

How it works: abstract here only prevents new BaseEntity(); everything else is complete. Product's constructor implicitly calls super(), which assigns the next id.

P7Medium

An abstract middle layer

Vehicle is abstract with wheels() and fuel(). TwoWheeler (also abstract) implements only wheels(). Scooter and EBike extend TwoWheeler and implement fuel(). Print both.

Input
Scooter, EBike
Expected output
Scooter: 2 wheels, petrol
EBike: 2 wheels, battery
Show solution and explanation
abstract class Vehicle {
    abstract int wheels();
    abstract String fuel();

    String info() {
        return getClass().getSimpleName() + ": " + wheels() + " wheels, " + fuel();
    }
}

abstract class TwoWheeler extends Vehicle {
    @Override
    int wheels() { return 2; }
}

class Scooter extends TwoWheeler {
    @Override
    String fuel() { return "petrol"; }
}

class EBike extends TwoWheeler {
    @Override
    String fuel() { return "battery"; }
}

public class Main {
    public static void main(String[] args) {
        Vehicle[] list = { new Scooter(), new EBike() };
        for (Vehicle v : list) {
            System.out.println(v.info());
        }
    }
}
Scooter: 2 wheels, petrol EBike: 2 wheels, battery

How it works: a subclass that implements only some abstract methods must stay abstract. TwoWheeler fixes wheels() once for all two-wheelers; the concrete classes finish the contract with fuel().

P8Business problem

Bank accounts with a monthly charge

Every account deducts a monthly charge, but the rule differs: savings charge Rs 50 if balance is below Rs 5,000, else nothing; current accounts always charge Rs 200. Write abstract Account with a final monthEnd() that applies charge() and prints a statement line.

Input
Savings SB-1 balance 3200; Savings SB-2 balance 12000; Current CA-9 balance 85000
Expected output
SB-1 charge Rs 50.00, balance Rs 3150.00
SB-2 charge Rs 0.00, balance Rs 12000.00
CA-9 charge Rs 200.00, balance Rs 84800.00
Show solution and explanation
abstract class Account {
    private final String no;
    protected double balance;

    Account(String no, double balance) {
        this.no = no;
        this.balance = balance;
    }

    abstract double charge();

    final void monthEnd() {
        double c = charge();
        balance -= c;
        System.out.printf("%s charge Rs %.2f, balance Rs %.2f%n", no, c, balance);
    }
}

class Savings extends Account {
    Savings(String no, double balance) { super(no, balance); }

    @Override
    double charge() { return balance < 5000 ? 50 : 0; }
}

class Current extends Account {
    Current(String no, double balance) { super(no, balance); }

    @Override
    double charge() { return 200; }
}

public class Main {
    public static void main(String[] args) {
        Account[] accounts = {
            new Savings("SB-1", 3200), new Savings("SB-2", 12000), new Current("CA-9", 85000)
        };
        for (Account a : accounts) {
            a.monthEnd();
        }
    }
}
SB-1 charge Rs 50.00, balance Rs 3150.00 SB-2 charge Rs 0.00, balance Rs 12000.00 CA-9 charge Rs 200.00, balance Rs 84800.00

How it works:

  1. The month-end job is a template method: compute the charge, deduct it, print. It's final, so no account type can skip the statement.
  2. Only the charge rule varies, so only that is abstract.
  3. SB-1 is below 5,000 and pays Rs 50; SB-2 pays nothing; CA-9 pays Rs 200.
P9Business problem

Loan EMI for different loan types

All loans compute EMI with the standard formula EMI = P·r·(1+r)n / ((1+r)n − 1), r = monthly rate. The yearly rate differs: home loan 8.5%, car loan 9.5%, and personal loan 14% (or 12.5% if the customer is salaried). Write abstract Loan with the formula and an abstract yearlyRate().

Input
Home Rs 30,00,000 / 240 months; Car Rs 6,00,000 / 60 months; Personal Rs 2,00,000 / 24 months (salaried)
Expected output
HomeLoan      8.50%  EMI Rs 26,035
CarLoan       9.50%  EMI Rs 12,601
PersonalLoan 12.50%  EMI Rs 9,461
Show solution and explanation
abstract class Loan {
    private final double principal;
    private final int months;

    Loan(double principal, int months) {
        this.principal = principal;
        this.months = months;
    }

    abstract double yearlyRate();

    final double emi() {
        double r = yearlyRate() / 12 / 100;
        double f = Math.pow(1 + r, months);
        return principal * r * f / (f - 1);
    }
}

class HomeLoan extends Loan {
    HomeLoan(double p, int m) { super(p, m); }
    @Override double yearlyRate() { return 8.5; }
}

class CarLoan extends Loan {
    CarLoan(double p, int m) { super(p, m); }
    @Override double yearlyRate() { return 9.5; }
}

class PersonalLoan extends Loan {
    private final boolean salaried;
    PersonalLoan(double p, int m, boolean salaried) { super(p, m); this.salaried = salaried; }
    @Override double yearlyRate() { return salaried ? 12.5 : 14.0; }
}

public class Main {
    public static void main(String[] args) {
        Loan[] loans = {
            new HomeLoan(3000000, 240), new CarLoan(600000, 60), new PersonalLoan(200000, 24, true)
        };
        for (Loan l : loans) {
            System.out.printf("%-12s %5.2f%%  EMI Rs %,.0f%n",
                    l.getClass().getSimpleName(), l.yearlyRate(), l.emi());
        }
    }
}
HomeLoan 8.50% EMI Rs 26,035 CarLoan 9.50% EMI Rs 12,601 PersonalLoan 12.50% EMI Rs 9,461

How it works:

  1. The EMI formula is identical for every loan, so it's a final concrete method in the abstract class.
  2. Only the rate varies, so yearlyRate() is abstract. PersonalLoan's rate depends on its own field.
  3. %,.0f prints rupees rounded, with commas. These are teaching figures, not real bank offers.
P10Business problem

Notification template with a mandatory footer

Every customer message must have a greeting, a body and a fixed footer "- Team Kirana Mart". SMS bodies are cut to 40 characters; email bodies are sent in full with a subject line. Use an abstract class with a final compose().

Input
customer "Meera"; text "Your order #812 has been shipped and will arrive by Friday."
Expected output
Hi Meera,
Your order #812 has been shipped and ...
- Team Kirana Mart
=====
Hi Meera,
Subject: Order update
Your order #812 has been shipped and will arrive by Friday.
- Team Kirana Mart
=====
Show solution and explanation
abstract class Message {
    protected final String customer;
    protected final String text;

    Message(String customer, String text) {
        this.customer = customer;
        this.text = text;
    }

    abstract String body();

    final String compose() {
        return "Hi " + customer + ",\n" + body() + "\n- Team Kirana Mart";
    }
}

class SmsMessage extends Message {
    SmsMessage(String c, String t) { super(c, t); }

    @Override
    String body() {
        return text.length() > 40 ? text.substring(0, 37) + "..." : text;
    }
}

class EmailMessage extends Message {
    EmailMessage(String c, String t) { super(c, t); }

    @Override
    String body() {
        return "Subject: Order update\n" + text;
    }
}

public class Main {
    public static void main(String[] args) {
        String text = "Your order #812 has been shipped and will arrive by Friday.";
        Message[] out = { new SmsMessage("Meera", text), new EmailMessage("Meera", text) };
        for (Message m : out) {
            System.out.println(m.compose());
            System.out.println("=====");
        }
    }
}
Hi Meera, Your order #812 has been shipped and ... - Team Kirana Mart ===== Hi Meera, Subject: Order update Your order #812 has been shipped and will arrive by Friday. - Team Kirana Mart =====

How it works:

  1. The greeting and footer are fixed in the final compose(): no channel can drop the footer.
  2. Each channel implements only body(). The SMS body is cut to 37 characters plus "..." to fit 40.
  3. Compare with the Lesson 7.3 scenario where an override forgot the footer: the template method makes that mistake impossible.

Real-world scenario questions (asked in company interviews)

Abstract classes turn "remember to do X" into "the compiler makes you do X". Decide your answer first, then open the model answer.

Design tool · billingFresher

The shape that cost nothing

In a print-shop app, Shape.area() returns 0 by default, and the price is area × rate. A new Star shape was added without overriding area(), so star stickers were printed free for a month. How do you prevent this class of bug?

The interviewer is checking: do you know how abstract methods enforce a contract?

Show a strong answer
  • Make Shape abstract and area() abstract. A concrete Star without area() then fails to compile.
  • Keep the shared code (pricing, name, colour) as concrete methods in Shape.
  • Add a sanity check in the pricing template (area must be > 0) and a unit test per shape.

Avoid: "default" implementations that return a fake value for something every child must define.

Payments · compliance1–3 years

A new gateway skipped fraud checks

Each payment gateway class has its own pay() method that is supposed to call fraudCheck() first, then charge, then write an audit log. A newly added gateway skipped the fraud check. How would you redesign so this can't happen again?

The interviewer is checking: can you apply the template method pattern?

Show a strong answer
  • Create an abstract PaymentGateway with public final Receipt pay(Order o) that always runs fraudCheck, then the abstract charge(o), then audit.
  • Gateways implement only charge() (and optional hooks such as supportsRefund()).
  • Because pay is final, no gateway can reorder or skip the mandatory steps.

Avoid: relying on a code-review checklist to remember the order.

Reports · production bug1–3 years

"null" in every report title

An abstract Report constructor calls the abstract title() to set up the page header. SalesReport.title() returns "Sales - " + region, where region is set in SalesReport's constructor. Every sales report header reads "Sales - null". Explain and fix.

The interviewer is checking: do you know the constructor-order trap with abstract methods?

Show a strong answer
  • Report's constructor runs first (via super(...)). Its call to title() dispatches to SalesReport's version before region has been assigned, so it's still null.
  • Fix: don't call abstract or overridable methods from constructors. Build the header lazily in a method like render(), or pass the title up as a constructor argument: super("Sales - " + region).

Avoid: making region static to "fix" it. That breaks reports for different regions.

SaaS · architecture3+ years

Abstract class or interface for plugins?

Your platform lets partner companies write import plugins (CSV, Excel, a bank's custom format). Some plugin authors already extend their own base classes. Should the plugin type be an abstract class or an interface?

The interviewer is checking: do you know the trade-offs between abstract classes and interfaces?

Show a strong answer
  • Because a Java class can extend only one class, an abstract-class plugin type would block authors who already extend something. An interface Importer is the public contract (Lesson 7.7).
  • Offer an optional abstract helper AbstractImporter implements Importer with shared code (row counting, error collection). Authors who can extend it get the help; others implement the interface directly.
  • Java's own collections follow this pattern: List (interface) and AbstractList (helper).

Avoid: forcing a single base class on third-party code.