This collection of 40 Java OOP exercises covers every core object-oriented concept, from basic encapsulation to abstract classes, interfaces, and composition versus aggregation.
What You’ll Practice
- Encapsulation & Constructors: Private fields, constructor overloading and chaining, and the copy constructor pattern.
- Inheritance & Polymorphism:
super(), multilevel and hierarchical inheritance, method overriding, variable shadowing, and runtime polymorphism through dynamic method dispatch. - Abstraction: Abstract classes and methods, interfaces, multiple interface implementation, default methods, and combining
extendswithimplements. - Class Design: Method overloading,
toString()overriding, static fields and counters,finalvariables and methods, and enums used as a state machine. - Object Relationships: Aggregation versus composition, illustrated with a university department and a computer’s internal components.
Each exercise includes a Problem Statement, Purpose, Hint, and a Solution with Explanation, so you understand exactly which OOP principle each piece of code demonstrates.
- Also, See: Java Exercises with over 20+ topic-wise sets and 575+ coding questions to practice.
- Practice questions using our Online Java Compiler
+ Table of Contents (40 Exercises)
Table of contents
- Exercise 1: Book Repository (Encapsulation)
- Exercise 2: Rectangle Dimensions (Object and Class)
- Exercise 3: Secure Bank Account
- Exercise 4: Employee Payroll Basics
- Exercise 5: Student Grading System
- Exercise 6: Car Dashboard
- Exercise 7: Inventory Item
- Exercise 8: Time Validation
- Exercise 9: Circle Initialization
- Exercise 10: Patient Admission (Constructor Chaining)
- Exercise 11: Point Duplicator (Copy Constructor)
- Exercise 12: Complex Number Addition
- Exercise 13: Smartphone Configurator (Method Chaining)
- Exercise 14: Flight Tracker
- Exercise 15: Vehicle Customization (Basic Inheritance)
- Exercise 16: Multilevel Living (Multilevel Inheritance)
- Exercise 17: Shape Coloring (Hierarchical Inheritance)
- Exercise 18: Polite Person (Method Overriding with super)
- Exercise 19: Variable Shadowing Resolution
- Exercise 20: Manager Salary Calculation
- Exercise 21: Grocery Stock (Perishable Goods)
- Exercise 22: Tiered Banking
- Exercise 23: Universal Calculator (Method Overloading)
- Exercise 24: Search Directory (Method Overloading)
- Exercise 25: Animal Chorus (Runtime Polymorphism)
- Exercise 26: Dynamic Method Dispatch
- Exercise 27: Payroll Calculation with Overriding
- Exercise 28: String Representation Override
- Exercise 29: Abstract Classes
- Exercise 30: Appliance Control
- Exercise 31: Flyable Contract
- Exercise 32: Hybrid Machines (Multiple Interfaces)
- Exercise 33: Tax Compliance (Abstract + Interface)
- Exercise 34: Default Methods in Interfaces
- Exercise 35: E-Commerce Payment Gateway
- Exercise 36: Instance Counter (Static Keyword)
- Exercise 37: Immutable Configuration (Final Keyword)
- Exercise 38: The University Setup (Aggregation)
- Exercise 39: The Desktop System (Composition)
- Exercise 40: State Machine (Enums with OOP)
Exercise 1: Book Repository (Encapsulation)
Problem Statement: Create a Book class with private attributes for title, author, and price. Implement getters and setters, ensuring that the price cannot be set to a negative value.
Purpose: This exercise helps you practice encapsulation by keeping fields private and controlling access through getters and setters, while also validating input inside a setter to protect object state.
Given Input: Book book = new Book("Java Basics", "James Gosling", 45.0); book.setPrice(-10);
Expected Output: Invalid price. Price not changed. Current price: 45.0
▼ Hint
- Declare
title,author, andpriceasprivatefields. - Write a constructor that accepts and assigns all three values.
- In
setPrice(), check if the incoming value is negative before assigning it. - If the value is negative, print a message and leave the existing price unchanged.
▼ Solution & Explanation
Explanation:
private String title, author; private double price;: These fields are hidden from outside classes, forcing all access to go through getters and setters.public Book(...): The constructor initializes a newBookobject with the given title, author, and price.if (price < 0): Validates the input before assignment. If the check fails, the method rejects the update and keeps the current price.this.price = price: Assigns the parameter value to the instance field only when the value passes validation.
Exercise 2: Rectangle Dimensions (Object and Class)
Problem Statement: Design a Rectangle class with length and width attributes. Include methods to calculate and return the area and perimeter.
Purpose: This exercise reinforces how to model a real-world object as a class, storing its properties as fields and exposing behavior through methods that operate on those fields.
Given Input: Rectangle rect = new Rectangle(10, 5);
Area = 50.0 Perimeter = 30.0
▼ Hint
Area is length * width, and perimeter is 2 * (length + width). Write each formula inside its own method and return the result.
▼ Solution & Explanation
Explanation:
private double length, width: Stores the rectangle’s dimensions as instance fields.getArea(): Multiplieslengthandwidthand returns the result as adouble.getPerimeter(): Addslengthandwidth, then multiplies the sum by 2 to get the total boundary length.
Exercise 3: Secure Bank Account
Problem Statement: Create a BankAccount class with a private balance. Implement safe deposit(double amount) and withdraw(double amount) methods that validate inputs and prevent overdrafts.
Purpose: This exercise builds on encapsulation by protecting a sensitive field, balance, from invalid changes and teaches how to guard against illegal state transitions such as negative deposits or overdrafts.
Given Input: BankAccount acc = new BankAccount(100); acc.deposit(50); acc.withdraw(200);
Deposited: 50.0 New balance: 150.0 Insufficient funds. Withdrawal denied.
▼ Hint
- In
deposit(), reject the amount if it is less than or equal to zero. - In
withdraw(), reject the amount if it exceeds the current balance. - Only update
balanceafter the validation check passes.
▼ Solution & Explanation
Explanation:
private double balance: Keeps the account balance hidden so it can only change through controlled methods.if (amount <= 0): Blocks deposits of zero or negative amounts before they can affect the balance.if (amount > balance): Prevents withdrawing more money than is available, avoiding a negative balance.balance += amount / balance -= amount: Updates the balance only after the corresponding validation check succeeds.
Exercise 4: Employee Payroll Basics
Problem Statement: Create an Employee class with an id, name, and salary. Add a method raiseSalary(double percentage) that modifies the salary attribute.
Purpose: This exercise practices writing a method that mutates an object’s own state based on a calculation, a common pattern in business and payroll style applications.
Given Input: Employee emp = new Employee(101, "Priya", 50000); emp.raiseSalary(10);
Expected Output: Updated salary for Priya: 55000.0
▼ Hint
Calculate the raise amount as salary * (percentage / 100), then add it to the existing salary before printing the updated value.
▼ Solution & Explanation
Explanation:
private int id; private String name; private double salary;: Stores the employee’s core details as private fields.salary * (percentage / 100): Calculates the raise amount as a percentage of the current salary.salary += ...: Adds the calculated raise directly to the existing salary field, updating the object’s state.
Exercise 5: Student Grading System
Problem Statement: Create a Student class containing an array of numerical marks. Implement a method to calculate the average mark and another to return a pass or fail status string.
Purpose: This exercise combines arrays with class design, showing how a class can hold a collection of values and expose calculated results and derived status through its methods.
Given Input: Student s = new Student(new int[]{80, 45, 60, 90, 35});
Average = 62.0 Status = Pass
▼ Hint
- Loop through the marks array and sum all the values.
- Divide the sum by
marks.lengthto get the average. - Decide on a pass threshold (for example, average of 40 or above) and return “Pass” or “Fail” accordingly.
▼ Solution & Explanation
Explanation:
private int[] marks: Stores all of the student’s marks as a single array field.for (int mark : marks): Uses an enhanced for loop to add every mark in the array tototal.(double) total / marks.length: Casts the sum todoublebefore dividing so the average is not truncated to an integer.getAverage() >= 40 ? "Pass" : "Fail": Uses a ternary operator to return a status string based on the calculated average.
Exercise 6: Car Dashboard
Problem Statement: Design a Car class with attributes make, model, and currentSpeed. Implement accelerate() and brake() methods that change the speed safely.
Purpose: This exercise practices controlling how a field changes over time by keeping the speed within safe bounds, similar to how real dashboard controls prevent invalid states.
Given Input: Car car = new Car("Toyota", "Corolla"); car.accelerate(20); car.brake(30);
Speed increased to 20 Speed cannot go below 0. Speed set to 0
▼ Hint
- In
accelerate(), add the given amount tocurrentSpeed. - In
brake(), subtract the given amount, but check if the result would go below zero. - If braking would make the speed negative, set
currentSpeedto 0 instead.
▼ Solution & Explanation
Explanation:
this.currentSpeed = 0: Initializes the car at a standstill inside the constructor.currentSpeed += amount: Increases the speed field directly when accelerating.if (currentSpeed - amount < 0): Checks whether braking would push the speed below zero before applying the change.currentSpeed = 0: Clamps the speed to zero instead of allowing a negative value when the check fails.
Exercise 7: Inventory Item
Problem Statement: Create a Product class with productID, name, and quantityInStock. Implement methods to addStock(int quantity) and sellProduct(int quantity) while verifying stock availability.
Purpose: This exercise practices guarding a numeric field against invalid operations, a pattern used in inventory and stock management systems to prevent overselling.
Given Input: Product p = new Product(1, "Notebook", 10); p.sellProduct(15);
Expected Output: Not enough stock. Only 10 units available.
▼ Hint
- In
sellProduct(), compare the requested quantity againstquantityInStockfirst. - If there is not enough stock, print a message and do not change
quantityInStock. - In
addStock(), simply add the quantity to the existing stock.
▼ Solution & Explanation
Explanation:
private int quantityInStock: Tracks how many units of the product are currently available.if (quantity > quantityInStock): Checks whether the requested sale exceeds available stock before making any change.quantityInStock -= quantity: Reduces the stock only after the availability check passes.quantityInStock += quantity: Increases stock directly insideaddStock(), since restocking has no upper limit to validate.
Exercise 8: Time Validation
Problem Statement: Create a Time class that stores hours, minutes, and seconds. Use setters to validate that inputs fall within correct time constraints, for example minutes and seconds must be between 0 and 59.
Purpose: This exercise practices validating multiple related fields independently in their own setters, ensuring an object can never hold an invalid combination of values.
Given Input: Time t = new Time(); t.setHours(23); t.setMinutes(75); t.setSeconds(30);
Invalid minutes. Must be between 0 and 59. Time = 23:0:30
▼ Hint
- Write a separate setter for each field:
setHours(),setMinutes(), andsetSeconds(). - Validate
hoursto be between 0 and 23. - Validate
minutesandsecondsto be between 0 and 59. - Print a message and skip the assignment when a value fails validation.
▼ Solution & Explanation
Explanation:
if (hours < 0 || hours > 23): Rejects any hour value outside the valid 24 hour range.if (minutes < 0 || minutes > 59): Rejects an out of range minute value, sominuteskeeps its previous default of 0.return;: Exits the setter early when validation fails, leaving the field unchanged.displayTime(): Prints the current values of all three fields together in a readable format.
Exercise 9: Circle Initialization
Problem Statement: Create a Circle class with a radius attribute. Provide a default constructor that sets radius to 1.0, and a parameterized constructor. Use this.radius to resolve naming conflicts.
Purpose: This exercise practices constructor overloading and the use of the this keyword to distinguish between a field and a parameter that share the same name.
Given Input: Circle c1 = new Circle(); Circle c2 = new Circle(5.0);
Radius of c1 = 1.0 Radius of c2 = 5.0
▼ Hint
- Write a no-argument constructor that assigns
1.0toradius. - Write a second constructor that accepts a
radiusparameter with the same name as the field. - Use
this.radius = radius;inside the parameterized constructor so Java knows which one is the field.
▼ Solution & Explanation
Explanation:
public Circle(): The default constructor takes no arguments and setsradiusto1.0.public Circle(double radius): The parameterized constructor has a parameter that shares its name with the field.this.radius = radius: Thethiskeyword refers to the instance field, distinguishing it from the parameter of the same name on the right side.
Exercise 10: Patient Admission (Constructor Chaining)
Problem Statement: Create a Patient class with name, age, and illness. Write three overloaded constructors and use this(...) to chain them so default values are assigned if some data is missing.
Purpose: This exercise practices constructor chaining with this(...), showing how one constructor can call another to avoid duplicating initialization logic while supplying sensible defaults.
Given Input: Patient p1 = new Patient(); Patient p2 = new Patient("Rahul"); Patient p3 = new Patient("Anita", 34, "Fever");
Patient: Unknown, Age: 0, Illness: Not specified Patient: Rahul, Age: 0, Illness: Not specified Patient: Anita, Age: 34, Illness: Fever
▼ Hint
- Write the full constructor first, taking
name,age, andillness. - Write a constructor that takes only
name, and have it callthis(name, 0, "Not specified"). - Write a no-argument constructor that calls
this("Unknown")so it chains through the second constructor.
▼ Solution & Explanation
Explanation:
public Patient(String name, int age, String illness): The full constructor assigns all three fields directly and is the base that the others chain into.this(name, 0, "Not specified"): The single-argument constructor calls the full constructor, supplying default values forageandillness.this("Unknown"): The no-argument constructor chains into the single-argument constructor, which in turn chains into the full constructor.this(...)must be the first statement: Java requires a constructor call usingthis(...)to be the very first line inside another constructor.
Exercise 11: Point Duplicator (Copy Constructor)
Problem Statement: Create a Point class representing (x, y) coordinates. Implement a copy constructor that takes another Point object as a parameter and duplicates its coordinates.
Purpose: This exercise introduces the copy constructor pattern, where a new object is built by reading the field values of an existing object of the same class rather than from raw parameters.
Given Input: Point p1 = new Point(3, 4); Point p2 = new Point(p1);
Expected Output: p2 = (3, 4)
▼ Hint
- Write a regular constructor that accepts
xandyvalues directly. - Write a second constructor that accepts a
Pointobject instead of raw values. - Inside the copy constructor, read
other.xandother.yand assign them tothis.xandthis.y.
▼ Solution & Explanation
Explanation:
public Point(Point other): This is the copy constructor. Its parameter is an existing object of the same class instead of separate primitive values.other.x: Because the code is inside thePointclass itself, it can directly read the private fields of anotherPointobject.this.x = other.x: Copies each field from the source object into the new object being constructed.
Exercise 12: Complex Number Addition
Problem Statement: Design a ComplexNumber class with real and imaginary parts. Use a constructor to initialize them, and create a method that accepts another ComplexNumber object to add them together.
Purpose: This exercise practices writing a method that takes an object of its own class as a parameter and returns a new object, a pattern common in mathematical and immutable data classes.
Given Input: ComplexNumber c1 = new ComplexNumber(2, 3); ComplexNumber c2 = new ComplexNumber(4, 5); ComplexNumber sum = c1.add(c2);
Expected Output: Sum = 6 + 8i
▼ Hint
- Store
realandimaginaryas fields set in the constructor. - Write an
add(ComplexNumber other)method that adds the corresponding fields separately. - Return a brand new
ComplexNumberbuilt from the two sums instead of modifying either original object.
▼ Solution & Explanation
Explanation:
add(ComplexNumber other): Accepts anotherComplexNumberobject so its fields can be read and combined with the current object’s fields.this.real + other.real: Adds the real parts of both numbers, keeping the two components separate as complex addition requires.return new ComplexNumber(...): Builds and returns a fresh object holding the result, leavingc1andc2unchanged.
Exercise 13: Smartphone Configurator (Method Chaining)
Problem Statement: Create a Smartphone class. Use the this keyword to return the current object instance from your setter methods, allowing you to chain calls like myPhone.setBrand("X").setRAM(8);.
Purpose: This exercise introduces the fluent interface pattern, where setter methods return the object itself so multiple configuration calls can be linked together in a single statement.
Given Input: Smartphone myPhone = new Smartphone(); myPhone.setBrand("Pixel").setRAM(8).setStorage(128);
Expected Output: Brand: Pixel, RAM: 8GB, Storage: 128GB
▼ Hint
- Give each setter a return type of
Smartphoneinstead ofvoid. - At the end of each setter, after assigning the field, add
return this;. - Because each call returns the same object, the next method in the chain can be called directly on that return value.
▼ Solution & Explanation
Explanation:
public Smartphone setBrand(String brand): The return type isSmartphoneinstead ofvoid, which is what makes chaining possible.return this;: Returns a reference to the current object, so the result of one setter call can immediately have another setter called on it.setBrand("Pixel").setRAM(8).setStorage(128): Each method call passes the same object reference along the chain, updating one field at a time.
Exercise 14: Flight Tracker
Problem Statement: Create a Flight class where the constructor automatically generates a unique, sequential tracking ID string for every flight instance created.
Purpose: This exercise introduces static fields, showing how a value can be shared and incremented across every instance of a class rather than belonging to just one object.
Given Input: Flight f1 = new Flight(); Flight f2 = new Flight(); Flight f3 = new Flight();
Tracking ID: FL-1 Tracking ID: FL-2 Tracking ID: FL-3
▼ Hint
- Declare a
private static int counterfield that is shared by everyFlightobject. - Inside the constructor, increment
counterbefore using it to build the tracking ID. - Build the ID string as
"FL-" + counterand store it in an instance field.
▼ Solution & Explanation
Explanation:
private static int counter: A static field belongs to the class itself rather than to any single object, so allFlightinstances share and update the same value.counter++: Runs every time a newFlightis constructed, ensuring each instance receives the next number in sequence.this.trackingId = "FL-" + counter: Stores the generated ID as an instance field so it stays fixed for that particular flight after creation.
Exercise 15: Vehicle Customization (Basic Inheritance)
Problem Statement: Create a base class Vehicle with attributes brand and year. Create a subclass Bike that adds a handlebarType attribute. Use super() to initialize the parent attributes.
Purpose: This exercise introduces basic inheritance, showing how a subclass can extend a parent class with its own fields while delegating the initialization of inherited fields to the parent’s constructor.
Given Input: Bike b = new Bike("Yamaha", 2023, "Sport");
Expected Output: Brand: Yamaha, Year: 2023, Handlebar: Sport
▼ Hint
- Declare
Bikeusingclass Bike extends Vehicle. - Give
Vehiclea constructor that acceptsbrandandyear. - In the
Bikeconstructor, callsuper(brand, year)as the very first line before settinghandlebarType.
▼ Solution & Explanation
Explanation:
class Bike extends Vehicle: MakesBikea subclass ofVehicle, inheriting itsbrandandyearfields.protected String brand; protected int year;: Declared asprotectedso the subclass can access them directly when needed.super(brand, year): Calls the parent class constructor to initialize the inherited fields before the subclass sets up its ownhandlebarTypefield.
Exercise 16: Multilevel Living (Multilevel Inheritance)
Problem Statement: Design an inheritance chain: Animal to Mammal to Dog. Introduce specific behaviors and attributes at each level, showing how properties inherit down the line.
Purpose: This exercise demonstrates multilevel inheritance, where a class inherits from a class that itself inherits from another class, and behavior from every level becomes available at the bottom.
Given Input: Dog d = new Dog(); d.eat(); d.walk(); d.bark();
This animal eats food. This mammal walks on land. The dog barks.
▼ Hint
- Give
Animalaneat()method. - Make
Mammal extends Animaland add awalk()method to it. - Make
Dog extends Mammaland add abark()method to it. - A
Dogobject will then have access to all three methods through the inheritance chain.
▼ Solution & Explanation
Explanation:
class Mammal extends Animal:Mammalinheritseat()fromAnimalwhile adding its ownwalk()method.class Dog extends Mammal:Doginherits botheat()andwalk()from the levels above it, then addsbark()of its own.d.eat(); d.walk(); d.bark();: A singleDogobject can call methods defined at every level of the chain, since each level builds on the one before it.
Exercise 17: Shape Coloring (Hierarchical Inheritance)
Problem Statement: Create a base class Shape with a color attribute. Create two independent subclasses, Triangle and Square, that inherit the color attribute but define their own distinct geometric dimensions.
Purpose: This exercise demonstrates hierarchical inheritance, where multiple subclasses independently extend the same parent class and each adds its own distinct fields and behavior.
Given Input: Triangle t = new Triangle("Red", 4, 6); Square s = new Square("Blue", 5);
Triangle color: Red, Area: 12.0 Square color: Blue, Area: 25.0
▼ Hint
- Give
Shapeacolorfield and a constructor that sets it. - In
Triangle, addbaseandheightfields and calculate area as0.5 * base * height. - In
Square, add asidefield and calculate area asside * side. - Both subclasses should call
super(color)in their own constructors.
▼ Solution & Explanation
Explanation:
class Triangle extends Shapeandclass Square extends Shape: Both subclasses inherit independently from the same parent, each gaining thecolorfield without knowing about the other.super(color): Each subclass constructor passes itscolorparameter up to theShapeconstructor to initialize the inherited field.getArea(): Each subclass defines its own version of this method using the dimensions relevant to its own shape.
Exercise 18: Polite Person (Method Overriding with super)
Problem Statement: Create a parent class Person with a displayDetails() method. Create a subclass Student that overrides this method but calls super.displayDetails() inside it to print the parent data first.
Purpose: This exercise practices method overriding combined with super, showing how a subclass can extend rather than completely replace the behavior of an inherited method.
Given Input: Student s = new Student("Amit", "10th Grade"); s.displayDetails();
Name: Amit Grade: 10th Grade
▼ Hint
- Give
Personanamefield and adisplayDetails()method that prints it. - In
Student, add agradefield and overridedisplayDetails(). - As the first line of the overridden method, call
super.displayDetails()before printing the grade.
▼ Solution & Explanation
Explanation:
@Override: Marks thatStudentis providing its own version of a method already defined inPerson.super.displayDetails(): Explicitly calls the parent class’s version of the method, printing the name before the subclass adds its own output.- Combined output: Because the parent method runs first, the final output shows both the inherited name and the subclass grade in one call.
Exercise 19: Variable Shadowing Resolution
Problem Statement: Create a class Parent with an instance variable value. Create a subclass Child that also defines an instance variable named value. Write a method in Child that accesses both variables using super.
Purpose: This exercise demonstrates variable shadowing, where a subclass field with the same name as a parent field hides it, and shows how super can still reach the hidden parent version.
Given Input: Child c = new Child(); c.showValues();
Parent value: 10 Child value: 20
▼ Hint
- Give both
ParentandChilda field namedvalue, set to different numbers. - Inside a method in
Child, usethis.valueto refer to theChildfield. - Use
super.valuein the same method to reach the hiddenParentfield.
▼ Solution & Explanation
Explanation:
protected int value = 10;inParentand again inChild: Both classes define a field with the identical name, so theChildversion shadows, but does not replace, theParentversion.super.value: Bypasses the shadowing and reaches directly into theParentclass’s copy ofvalue.this.value: Refers to theChildclass’s own copy, confirming that both versions of the field exist independently in memory.
Exercise 20: Manager Salary Calculation
Problem Statement: Create an Employee base class and a Manager subclass. The manager’s salary should be calculated as a base salary plus a performance bonus. Pass the base details up via super.
Purpose: This exercise practices combining inheritance with method overriding, where a subclass reuses inherited fields but recalculates a derived value, such as total salary, using its own additional data.
Given Input: Manager m = new Manager("Sunita", 60000, 15000);
Expected Output: Manager: Sunita, Total Salary: 75000.0
▼ Hint
- Give
Employeea constructor acceptingnameandbaseSalary. - In
Manager, add abonusfield and callsuper(name, baseSalary)in its constructor. - Add a method in
Managerthat returnsbaseSalary + bonusas the total salary.
▼ Solution & Explanation
Explanation:
super(name, baseSalary): Passes the shared employee details up to theEmployeeconstructor soManagerdoes not duplicate that assignment logic.private double bonus: Stores the manager-specific value thatEmployeeobjects do not have.baseSalary + bonus: Combines the inherited field with the subclass’s own field to compute a value that only makes sense for aManager.
Exercise 21: Grocery Stock (Perishable Goods)
Problem Statement: Create a base class Item with name and price. Create a subclass PerishableItem that adds an expirationDate attribute and handles specific storage alerts.
Purpose: This exercise practices extending a general class with a more specialized one, adding both a new field and new behavior that only makes sense for that specific subclass.
Given Input: PerishableItem milk = new PerishableItem("Milk", 3.5, "2026-07-05");
Item: Milk, Price: 3.5 Store in refrigerator. Expires on 2026-07-05
▼ Hint
- Give
Itema constructor fornameandprice, plus a method to display them. - In
PerishableItem, callsuper(name, price)and storeexpirationDateseparately. - Add a method such as
storageAlert()that prints a refrigeration reminder along with the expiration date.
▼ Solution & Explanation
Explanation:
protected String name; protected double price;: Declared asprotectedinItemso the subclass can rely on them without needing separate getters.super(name, price): Initializes the inherited fields beforePerishableItemsets its ownexpirationDate.storageAlert(): A method that exists only onPerishableItem, since regularItemobjects have no concept of expiration.
Exercise 22: Tiered Banking
Problem Statement: Create a SavingsAccount class that inherits from your original BankAccount class. Add an interestRate attribute and a method applyInterest() that updates the balance using parent-level access.
Purpose: This exercise practices extending a class whose main field is protected rather than public, showing how a subclass can read and modify an inherited field directly while still respecting encapsulation from outside classes.
Given Input: SavingsAccount acc = new SavingsAccount(1000, 5); acc.applyInterest();
Expected Output: New balance after interest: 1050.0
▼ Hint
- Declare
balanceasprotectedinBankAccountso subclasses can access it directly. - In
SavingsAccount, callsuper(balance)and store the additionalinterestRate. - In
applyInterest(), calculatebalance * (interestRate / 100)and add it directly to the inheritedbalancefield.
▼ Solution & Explanation
Explanation:
protected double balance;: Usingprotectedinstead ofprivateallowsSavingsAccountto reach the field directly, while classes outside the hierarchy still cannot access it.super(balance): Passes the initial balance up toBankAccountso both classes stay in sync from the moment of construction.balance += balance * (interestRate / 100): Modifies the inherited field directly within the subclass, using data that onlySavingsAccountknows about, its interest rate.
Exercise 23: Universal Calculator (Method Overloading)
Problem Statement: Create a MathUtils class featuring overloaded versions of an add() method that can process two integers, three integers, or two double values.
Purpose: This exercise practices method overloading, where multiple methods share the same name but differ in the number or type of their parameters, and the compiler picks the correct one automatically.
Given Input: MathUtils m = new MathUtils(); m.add(2, 3); m.add(2, 3, 4); m.add(2.5, 3.5);
Sum of two ints: 5 Sum of three ints: 9 Sum of two doubles: 6.0
▼ Hint
- Write three separate
addmethods, each with a different parameter list. - Two methods can share the same number of parameters as long as the parameter types differ, as with
add(int, int)versusadd(double, double). - Java automatically chooses the correct method based on the arguments used at the call site.
▼ Solution & Explanation
Explanation:
add(int a, int b)andadd(int a, int b, int c): These two methods differ in the number of parameters, which is enough for Java to treat them as separate overloads.add(double a, double b): Has the same parameter count as the first overload but a different parameter type, making it a distinct method.m.add(2, 3)versusm.add(2.5, 3.5): The compiler matches each call to the overload whose parameter types fit the arguments provided.
Exercise 24: Search Directory (Method Overloading)
Problem Statement: Create a UserDirectory class with overloaded findUser() methods: one that searches by an int id, and another that searches by a String username.
Purpose: This exercise practices overloading based on parameter type alone, showing how the same method name can offer two different search strategies depending on what kind of value is passed in.
Given Input: UserDirectory dir = new UserDirectory(); dir.findUser(101); dir.findUser("neha_k");
Searching by ID: 101 Searching by username: neha_k
▼ Hint
- Write one
findUser(int id)method and onefindUser(String username)method. - Both methods have exactly one parameter, but the parameter types differ, which is enough to overload them.
- Java decides which method to call based on whether an
intor aStringis passed as the argument.
▼ Solution & Explanation
Explanation:
findUser(int id): Handles calls where the argument is a numeric identifier.findUser(String username): Handles calls where the argument is text, even though the method name is identical to the other overload.dir.findUser(101)versusdir.findUser("neha_k"): The compiler resolves which method to run purely by matching the argument’s type against the available overloads.
Exercise 25: Animal Chorus (Runtime Polymorphism)
Problem Statement: Create a base class Animal with a makeSound() method. Override it in Cat (“Meow”) and Dog (“Bark”) subclasses. Create an array of Animal references holding different subclasses and loop through them to trigger their unique sounds.
Purpose: This exercise demonstrates runtime polymorphism, where the specific overridden method that runs is determined by the actual object type, not the reference type used to call it.
Given Input: Animal[] animals = { new Cat(), new Dog() }; for (Animal a : animals) { a.makeSound(); }
Meow Bark
▼ Hint
- Give
AnimalamakeSound()method with generic or empty behavior. - Override
makeSound()separately inCatandDog. - Declare the array as
Animal[]even though it storesCatandDogobjects, then callmakeSound()on each element inside a loop.
▼ Solution & Explanation
Explanation:
Animal[] animals = { new Cat(), new Dog() }: The array type is the parent class, but each element is actually a different subclass object underneath.a.makeSound(): Even thoughais declared as typeAnimal, Java calls the overridden version that matches the object’s real class at runtime.- Result: The same line of code inside the loop produces different output for each element, which is the essence of runtime polymorphism.
Exercise 26: Dynamic Method Dispatch
Problem Statement: Create a base class Printer with a printDocument() method. Create subclasses LaserPrinter and InkjetPrinter. Demonstrate dynamic method dispatch by assigning subclass objects to a base Printer variable at runtime.
Purpose: This exercise reinforces dynamic method dispatch, showing that the version of an overridden method that gets executed is decided while the program runs, based on the object a reference variable currently points to.
Given Input: Printer p = new LaserPrinter(); p.printDocument(); p = new InkjetPrinter(); p.printDocument();
Printing with laser technology. Printing with inkjet technology.
▼ Hint
- Declare a single variable of type
Printer. - Assign a
LaserPrinterobject to it first, callprintDocument(), then reassign it to a newInkjetPrinterobject and callprintDocument()again. - Each call should run the version defined in whichever subclass the variable currently references.
▼ Solution & Explanation
Explanation:
Printer p = new LaserPrinter();: The reference type isPrinter, but the object it points to is aLaserPrinter.p = new InkjetPrinter();: The same variable is reassigned to a different subclass object without changing its declared type.p.printDocument(): Java looks at the actual objectprefers to at the moment of the call, not the variable’s declared type, to decide which overridden method runs.
Exercise 27: Payroll Calculation with Overriding
Problem Statement: Create an abstract-like setup where a base class Worker has a calculatePay() method. Subclasses SalariedWorker and HourlyWorker override this method to calculate pay using entirely different formulas.
Purpose: This exercise practices overriding a method where each subclass implements completely different logic, showing that overriding is about replacing behavior, not just extending it.
Given Input: Worker w1 = new SalariedWorker(50000); Worker w2 = new HourlyWorker(25, 160);
Salaried pay: 50000.0 Hourly pay: 4000.0
▼ Hint
- In
SalariedWorker, store a fixedmonthlySalaryand havecalculatePay()simply return it. - In
HourlyWorker, storehourlyRateandhoursWorked, then havecalculatePay()multiply them together. - Both classes override the same method name from
Worker, but the internal formula is completely unrelated between them.
▼ Solution & Explanation
Explanation:
calculatePay()inWorker: Acts as a placeholder that both subclasses are expected to override with real logic.SalariedWorker.calculatePay(): Simply returns the fixedmonthlySalary, ignoring hours entirely.HourlyWorker.calculatePay(): MultiplieshourlyRatebyhoursWorked, a formula with no relation to the salaried version.
Exercise 28: String Representation Override
Problem Statement: Create a User class with fields like username and email. Override the built-in Object class toString() method to print a clean, human-readable summary of the user instance instead of its memory address.
Purpose: This exercise introduces overriding a method inherited from Object, the implicit parent of every Java class, and shows how it changes what gets printed when an object is used directly in output.
Given Input: User u = new User("neha_k", "neha@example.com"); System.out.println(u);
Expected Output: User[username=neha_k, email=neha@example.com]
▼ Hint
- Every Java class implicitly extends
Object, which already defines a defaulttoString(). - Add
@Override public String toString()toUserand build a formatted string from its fields. - When an object is passed to
System.out.println(), Java automatically calls itstoString()method.
▼ Solution & Explanation
Explanation:
@Override public String toString(): Replaces the defaultObjectversion, which would otherwise print something like a class name followed by a hash code.return "User[username=" + username + ...: Builds a single formatted string that summarizes the object’s key fields.System.out.println(u): Implicitly callsu.toString()behind the scenes, so the overridden version’s output is what actually appears.
Exercise 29: Abstract Classes
Problem Statement: Create an abstract class Shape with an abstract method calculateArea(). Implement concrete subclasses Circle and Rectangle that fill in the specific mathematical formulas.
Purpose: This exercise introduces abstract classes, which define a method signature without any implementation and force every concrete subclass to provide its own version before it can be instantiated.
Given Input: Shape c = new Circle(3); Shape r = new Rectangle(4, 5);
Circle area: 28.26 Rectangle area: 20.0
▼ Hint
- Mark the class as
abstract class Shapeand declarepublic abstract double calculateArea();with no method body. - In
Circle, calculate area asMath.PI * radius * radius. - In
Rectangle, calculate area aslength * width. Java will not compile either subclass unless it fully implementscalculateArea().
▼ Solution & Explanation
Explanation:
abstract class Shape: An abstract class cannot be instantiated directly withnew Shape(), it exists only to be extended.public abstract double calculateArea();: Declares a method signature with no body, meaning every concrete subclass must supply its own implementation.Shape c = new Circle(3);: Even though the reference type is the abstractShape, the object created is a concreteCircle, which is the only kind of object that can actually exist.
Exercise 30: Appliance Control
Problem Statement: Create an abstract class Appliance with abstract methods turnOn() and turnOff(). Create a concrete subclass AirConditioner that implements these methods and tracks current temperature settings.
Purpose: This exercise extends the abstract class pattern to a case with multiple abstract methods, showing that a concrete subclass must implement every one of them, not just some.
Given Input: Appliance ac = new AirConditioner(22); ac.turnOn(); ac.turnOff();
AC turned on. Temperature set to 22 degrees. AC turned off.
▼ Hint
- Declare
Applianceas an abstract class with two abstract methods,turnOn()andturnOff(), both without bodies. - In
AirConditioner, store atemperaturefield set through the constructor. - Implement both abstract methods in
AirConditioner, printing a message that referencestemperatureinturnOn().
▼ Solution & Explanation
Explanation:
public abstract void turnOn(); public abstract void turnOff();: Both methods must be implemented by any concrete subclass, since neither has a body inAppliance.private int temperature: A field specific toAirConditionerthat has no equivalent in the abstract parent class.turnOn()andturnOff()implementations: Together they satisfy the contract set byAppliance, allowingAirConditionerto be instantiated.
Exercise 31: Flyable Contract
Problem Statement: Create an interface called Flyable with a method fly(). Implement this interface in two completely unrelated classes: Bird and Airplane.
Purpose: This exercise introduces interfaces, showing how two classes with no shared parent class can still guarantee the same behavior by agreeing to a common contract.
Given Input: Flyable b = new Bird(); Flyable a = new Airplane(); b.fly(); a.fly();
The bird flaps its wings to fly. The airplane uses jet engines to fly.
▼ Hint
- Declare
interface Flyablewith a single method signature,void fly();, and no body. - Have
BirdandAirplaneeach useimplements Flyableinstead ofextends. - Each class must provide its own complete implementation of
fly(), since interfaces have no default logic here.
▼ Solution & Explanation
Explanation:
interface Flyable { void fly(); }: Defines a contract that any implementing class must fulfill by providing its own version offly().class Bird implements Flyable:BirdandAirplaneshare no class hierarchy at all, yet both satisfy the same interface.Flyable b = new Bird();: A variable of the interface type can reference any object that implements it, regardless of the object’s actual class.
Exercise 32: Hybrid Machines (Multiple Interfaces)
Problem Statement: Create two interfaces: Drivable (startEngine(), stopEngine()) and Watercraft (dock()). Create an AmphibiousVehicle class that implements both interfaces.
Purpose: This exercise demonstrates that a single Java class can implement multiple interfaces at once, something not possible with class inheritance, allowing one class to combine several unrelated behavior contracts.
Given Input: AmphibiousVehicle v = new AmphibiousVehicle(); v.startEngine(); v.dock(); v.stopEngine();
Engine started. Docking at the harbor. Engine stopped.
▼ Hint
- Declare both interfaces separately, each with their own method signatures.
- In
AmphibiousVehicle, useimplements Drivable, Watercraftwith a comma between the two interface names. - Provide implementations for all three methods,
startEngine(),stopEngine(), anddock(), inside the single class.
▼ Solution & Explanation
Explanation:
implements Drivable, Watercraft: A class can implement more than one interface by listing them separated by commas, something Java does not allow withextendsfor classes.startEngine(),stopEngine(),dock(): All three come from two different interfaces, yetAmphibiousVehiclemust implement each one to compile.- Combined capability: The single class now satisfies two separate behavior contracts, letting it act as both a drivable vehicle and a watercraft.
Exercise 33: Tax Compliance (Abstract + Interface)
Problem Statement: Create an abstract class Employee and an interface called Taxable. Create a concrete subclass FullTimeEmployee that extends the abstract class and implements the interface.
Purpose: This exercise practices combining extends and implements on the same class, showing how a subclass can inherit shared state from an abstract parent while also fulfilling a separate interface contract.
Given Input: FullTimeEmployee emp = new FullTimeEmployee("Karan", 80000); emp.displayName(); emp.calculateTax();
Employee: Karan Tax owed: 16000.0
▼ Hint
- Give the abstract class
Employeeanamefield and a concrete method likedisplayName(). - Declare
interface Taxablewith a method such ascalculateTax(). - Write
class FullTimeEmployee extends Employee implements Taxableand implementcalculateTax()using a fixed percentage ofsalary.
▼ Solution & Explanation
Explanation:
class FullTimeEmployee extends Employee implements Taxable: Combines a single class inheritance with an interface contract, both allowed at once in Java.super(name): Initializes the field inherited from the abstractEmployeeclass.calculateTax(): Fulfills theTaxableinterface’s contract, using thesalaryfield that belongs only toFullTimeEmployee.
Exercise 34: Default Methods in Interfaces
Problem Statement: Create an interface Vehicle with an abstract method accelerate(). Add a default method inside the interface called soundHorn() so implementing classes automatically inherit a default horn behavior without being forced to write it.
Purpose: This exercise introduces default methods in interfaces, which provide a ready-made implementation that implementing classes can use as is or override if they need different behavior.
Given Input: Vehicle v = new Motorcycle(); v.accelerate(); v.soundHorn();
Motorcycle accelerating. Beep beep!
▼ Hint
- Declare
accelerate()as a normal abstract method with no body. - Declare
soundHorn()using thedefaultkeyword and give it a complete method body directly inside the interface. - A class implementing
Vehiclemust implementaccelerate(), but it can callsoundHorn()immediately without writing any code for it.
▼ Solution & Explanation
Explanation:
void accelerate();: Remains a plain abstract method, soMotorcycleis still required to implement it.default void soundHorn() { ... }: Thedefaultkeyword allows the interface to supply a working method body, which is unusual for interfaces before this feature existed.v.soundHorn(): Works immediately even thoughMotorcyclenever wrote asoundHorn()method itself, since it inherits the interface’s default implementation.
Exercise 35: E-Commerce Payment Gateway
Problem Statement: Design a PaymentProcessor interface with a processPayment(double amount) method. Implement it across three distinct payment classes: CreditCardProcessor, PayPalProcessor, and CryptoProcessor.
Purpose: This exercise shows a practical use of interfaces for building interchangeable components, where any payment method can be swapped in as long as it follows the same contract.
Given Input: PaymentProcessor[] processors = { new CreditCardProcessor(), new PayPalProcessor(), new CryptoProcessor() }; for (PaymentProcessor p : processors) { p.processPayment(100); }
Processing 100.0 via Credit Card. Processing 100.0 via PayPal. Processing 100.0 via Cryptocurrency.
▼ Hint
- Declare a single method,
void processPayment(double amount);, insidePaymentProcessor. - Have all three classes implement the interface, each printing a message naming its own payment method.
- Store all three objects in a single
PaymentProcessor[]array so they can be looped through identically despite being different underlying classes.
▼ Solution & Explanation
Explanation:
PaymentProcessor[] processors: The array type is the interface, so it can hold any object from any class that implementsPaymentProcessor.for (PaymentProcessor p : processors): The same loop body works for all three payment types since each guarantees aprocessPayment()method.- Interchangeability: A new payment method could be added later by writing one more class that implements the interface, with no changes needed to the loop itself.
Exercise 36: Instance Counter (Static Keyword)
Problem Statement: Create a Car class that contains a static variable called totalCarsCreated. Increment this counter inside every constructor so you can track how many cars exist globally across your application.
Purpose: This exercise reinforces static fields as class-level state that persists and accumulates across every object created, independent of any single instance.
Given Input: Car c1 = new Car("Honda"); Car c2 = new Car("Ford"); Car c3 = new Car("Kia");
Expected Output: Total cars created: 3
▼ Hint
- Declare
private static int totalCarsCreated = 0;outside of any constructor. - Add
totalCarsCreated++;as a line inside the constructor. - Access the count using a
staticmethod or field, since it belongs to the class itself rather than to any oneCarobject.
▼ Solution & Explanation
Explanation:
private static int totalCarsCreated = 0;: A single copy of this variable is shared by the class itself, not duplicated for every object.totalCarsCreated++;: Runs once per constructor call, so the shared counter increases every time a newCarobject is created.Car.getTotalCarsCreated(): Called on the class itself rather than on any particular object, which is howstaticmembers are typically accessed.
Exercise 37: Immutable Configuration (Final Keyword)
Problem Statement: Create a SystemConfig class with a final variable for DATABASE_URL and a final method showVersion(). Attempt to extend this class or modify these components to observe how Java enforces immutability.
Purpose: This exercise demonstrates the final keyword’s role in preventing change, either by locking a variable’s value after assignment or by blocking a method from being overridden in a subclass.
Given Input: SystemConfig config = new SystemConfig(); config.showVersion();
Expected Output: Version 1.0, DB: jdbc:mysql://localhost:3306/app
▼ Hint
- Declare
public static final String DATABASE_URL = "...";and assign it once at declaration. - Mark
showVersion()with thefinalkeyword in its signature. - If you try
DATABASE_URL = "newValue";later, or attempt to overrideshowVersion()in a subclass, the code will fail to compile, confirming both are locked.
▼ Solution & Explanation
Explanation:
public static final String DATABASE_URL = "...";: Once assigned, this value can never be reassigned anywhere else in the program, and any attempt to do so is a compile-time error.public final void showVersion(): Thefinalkeyword on a method blocks any subclass from overriding it, keeping its behavior fixed across the entire hierarchy.- Immutability guarantee: Both uses of
finalprotect different things, one a value and one a behavior, but both rely on the compiler to enforce the restriction rather than a runtime check.
Exercise 38: The University Setup (Aggregation)
Problem Statement: Create a Professor class. Then, create a Department class that holds a list of Professor objects. Ensure that if the Department is deleted, the Professor objects can still exist independently.
Purpose: This exercise introduces aggregation, a “has-a” relationship where one class holds references to objects that were created outside of it and can outlive it.
Given Input: Professor p1 = new Professor("Dr. Rao"); Professor p2 = new Professor("Dr. Iyer"); Department cs = new Department("Computer Science"); cs.addProfessor(p1); cs.addProfessor(p2); cs.listProfessors();
Computer Science professors: Dr. Rao Dr. Iyer
▼ Hint
- Create
Professorobjects independently, outside of theDepartmentconstructor. - Give
DepartmentaList<Professor>field and anaddProfessor(Professor p)method that simply adds an existing reference to the list. - Because the
Professorobjects are created outsideDepartmentand only referenced inside it, they can keep existing even if theDepartmentobject is discarded.
▼ Solution & Explanation
Explanation:
Professor p1 = new Professor("Dr. Rao");: The professor objects are created independently inmain(), before anyDepartmentexists.addProfessor(Professor p):Departmentonly stores a reference to an already existingProfessorobject, it does not create the object itself.- Independent lifecycle: Because
p1andp2exist outsidecs, they would remain valid objects even ifcswere discarded, which is the defining trait of aggregation.
Exercise 39: The Desktop System (Composition)
Problem Statement: Create a Computer class that instantiates its own Processor and GraphicsCard objects inside its constructor. Ensure that if the Computer object is destroyed, its internal component objects are destroyed along with it.
Purpose: This exercise introduces composition, a stricter “has-a” relationship where the contained objects are created and owned entirely by the containing object and cannot exist independently of it.
Given Input: Computer pc = new Computer(); pc.showSpecs();
Processor: Octa-core CPU Graphics Card: RTX Series
▼ Hint
- Inside the
Computerconstructor, directly writethis.processor = new Processor();rather than accepting one as a parameter. - Do the same for
GraphicsCard. - Since both component objects are created entirely inside
Computerwith no reference kept anywhere else, they only exist as long as their owningComputerobject does.
▼ Solution & Explanation
Explanation:
this.processor = new Processor();: TheComputerconstructor creates its ownProcessorobject internally, rather than receiving one from outside.this.graphicsCard = new GraphicsCard();: The same pattern applies toGraphicsCard, so both components are fully owned by theComputerinstance.- Ownership: No other part of the program holds a reference to these
ProcessororGraphicsCardobjects, so they become eligible for garbage collection as soon as the owningComputerobject is no longer used.
Exercise 40: State Machine (Enums with OOP)
Problem Statement: Create an enum OrderStatus with states like PENDING, SHIPPED, and DELIVERED. Create an Order class that utilizes this enum, and write a method canCancel() that returns true or false depending on the current enum state.
Purpose: This exercise shows how an enum can represent a fixed, well-defined set of states for an object, and how ordinary logic such as if or switch can branch based on which state is currently active.
Given Input: Order o1 = new Order(OrderStatus.PENDING); Order o2 = new Order(OrderStatus.SHIPPED); System.out.println(o1.canCancel()); System.out.println(o2.canCancel());
true false
▼ Hint
- Declare
enum OrderStatus { PENDING, SHIPPED, DELIVERED }outside theOrderclass. - Give
Ordera field of typeOrderStatus, set through its constructor. - In
canCancel(), use aswitchor anifcomparison to returntrueonly when the status equalsOrderStatus.PENDING.
▼ Solution & Explanation
Explanation:
enum OrderStatus { PENDING, SHIPPED, DELIVERED }: Defines a fixed set of named constants that represent every valid state an order can be in.private OrderStatus status;: Stores the order’s current state using the enum type instead of a plain string or number, preventing invalid values.status == OrderStatus.PENDING: Enum constants can be compared directly with==, returningtrueonly when the order has not yet moved past the pending state.

Leave a Reply