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.
Sir, the hexagon bug compiled perfectly. How do I force every new shape to write its own area?
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.
But then can I still create a Shape?
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.
What is abstraction, and what is an abstract class?
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.
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 double area();abstract; can't be created with new.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 |
| Constructors | Allowed, and they run when a child is created, through super(...) |
| Abstract methods | Zero 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 abstract | final, 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.
Three ways abstract classes are used
A required contract
Abstract methods force every child to supply the varying part: area(), calculatePay().
Shared partial code
Fields, a constructor and helper methods written once: name, describe(), validation.
Template method
The parent fixes the order of steps; children fill in individual steps.
fixed skeletonContract 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());
}
}
}| Concrete class | Abstract class | |
|---|---|---|
new allowed? | Yes | No (compile error) |
| Abstract methods? | None allowed | Zero or more |
| Fields, constructors, concrete methods | Yes | Yes |
| Can be a variable / parameter type | Yes | Yes |
| Purpose | Model a real, complete thing | Model 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.
Building an abstract hierarchy
Mark the class abstract
javac sets theabstract class Shape { ... }ACC_ABSTRACTflag in the class file. Both javac and the JVM refuse to create instances of such a class.Declare what every child must provide
An abstract method is a promise with no code. In the class file it has a signature but no bytecode. Writing braces givesabstract double area(); // semicolon, no bracesabstract methods cannot have a body.Write the shared parts once
Shape(String name) { this.name = name; } String describe() { return name + ": " + area(); }describe()can callarea()even though Shape has no body for it: at run timethisis always a concrete child, so dynamic dispatch finds a real implementation.Complete it in a concrete child
The child must call a Shape constructor (hereclass Circle extends Shape { Circle(String name, double r) { super(name); this.r = r; } @Override double area() { return Math.PI * r * r; } }super(name)) and implement every abstract method. Miss one and javac names it in the error.Program to the abstract type
Code that usesShape s = new Circle("logo", 7); System.out.println(s.describe());Shapenever needs to know which concrete class it has. New shapes plug in without changing it.
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.
Shape 6 steps- No instances:
new Shape(...)is rejected by javac because Shape is abstract. - Contract enforced: a concrete Hexagon without
area()doesn't compile. The silent zero-area bug is gone. - Abstract type, real object: a Circle is created and stored in a Shape variable. Shape's constructor sets the name.
- Dispatch:
s.area()runs Circle's implementation: π × 7². - Partial implementation:
describe()lives in Shape and callsarea(), which is supplied by the child. - Payoff: the canvas loop handles circles and rectangles alike, and future shapes too.
- The abstract class exists as a class (its code is loaded), but no object of exactly that class can ever be created.
- When compiling a concrete subclass, javac checks that every inherited abstract method has a body somewhere in the chain.
new Circle(...)creates one object. Shape's constructor (called throughsuper(name)) fills the Shape part.- Shape has no code for
area(); the JVM's lookup on the Circle object finds Circle's. describe()runs Shape's code withthis= the Circle, so its call toarea()also dispatches to Circle.- Rect completes the same contract differently. The abstract parent is the common type for both.
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.
- No plain loans:
Loanis abstract. A loan with no rate makes no sense, sonew Loan(...)doesn't compile. - Contract enforced: a new
GoldLoanthat forgetsrate()is rejected by javac, so it can never reach customers with a missing rate. - Concrete child:
new HomeLoan(...)works. Its constructor callssuper(...), so Loan's constructor stores the principal and tenure. - Shared formula:
emi()is written once in Loan (and is final). It calls the abstractrate(), which dispatches to HomeLoan's 8.5%: EMI ≈ Rs 26,035 for Rs 30 lakh over 20 years. - Same code, different child: for a CarLoan at 9.5%, Rs 6 lakh over 5 years, the same formula gives ≈ Rs 12,601.
- The payoff: the bank's EMI calculator loops over
Loanobjects. Each product supplies only its rate. (Teaching figures, not real offers.)
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().
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.
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().
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.
Examples are simplified. When there's no shared code or state, an interface (Lesson 7.7) is usually preferred over an abstract class.
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());
}
}
}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.
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.
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().
Circle "logo" r=1; Rect "tag" 4 × 2
logo 3.14 tag 8.0
Approach
- Make
Shapeabstract with a constructor for the name and an abstractarea(). describe()lives in Shape and callsarea().- 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.
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.
Contractor rate Rs 500 × 100 hours
Net pay: 50000.0
Approach
- Abstract
Employeewith abstractgross()and a hookdeductions()(default 10%). - A final
net()combines them in a fixed way. Contractorimplementsgross()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.
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.
Van, 10 km
Trip cost: Rs 280
Approach
- Abstract
VehicleimplementstripCost(km)once. - It calls two abstract methods,
base()andperKm(). Vanfills 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.
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.abstractmethod: 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.
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.
Cow "Gauri", Parrot "Mithu"
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();
}
}
}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.
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.
SavingsAccount("SB-2041")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() + "%");
}
}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.
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.
Rectangle 8×5, Circle r=4
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();
}
}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.
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.
make Chai, then Coffee
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();
}
}
}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.
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.
make Chai, then GreenTea
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();
}
}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.
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.
Product "Rice 5kg", Product "Dal 1kg"
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());
}
}How it works: abstract here only prevents new BaseEntity(); everything else is complete. Product's constructor implicitly calls super(), which assigns the next id.
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.
Scooter, EBike
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());
}
}
}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().
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.
Savings SB-1 balance 3200; Savings SB-2 balance 12000; Current CA-9 balance 85000
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();
}
}
}How it works:
- 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.
- Only the charge rule varies, so only that is abstract.
- SB-1 is below 5,000 and pays Rs 50; SB-2 pays nothing; CA-9 pays Rs 200.
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().
Home Rs 30,00,000 / 240 months; Car Rs 6,00,000 / 60 months; Personal Rs 2,00,000 / 24 months (salaried)
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());
}
}
}How it works:
- The EMI formula is identical for every loan, so it's a final concrete method in the abstract class.
- Only the rate varies, so
yearlyRate()is abstract. PersonalLoan's rate depends on its own field. %,.0fprints rupees rounded, with commas. These are teaching figures, not real bank offers.
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().
customer "Meera"; text "Your order #812 has been shipped and will arrive by Friday."
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("=====");
}
}
}How it works:
- The greeting and footer are fixed in the final
compose(): no channel can drop the footer. - Each channel implements only
body(). The SMS body is cut to 37 characters plus "..." to fit 40. - 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.
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
Shapeabstract andarea()abstract. A concrete Star withoutarea()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.
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
PaymentGatewaywithpublic final Receipt pay(Order o)that always runsfraudCheck, then the abstractcharge(o), thenaudit. - Gateways implement only
charge()(and optional hooks such assupportsRefund()). - Because
payis final, no gateway can reorder or skip the mandatory steps.
Avoid: relying on a code-review checklist to remember the order.
"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 totitle()dispatches to SalesReport's version beforeregionhas 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.
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
Importeris the public contract (Lesson 7.7). - Offer an optional abstract helper
AbstractImporter implements Importerwith 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) andAbstractList(helper).
Avoid: forcing a single base class on third-party code.