Java Question Bank-3
Java Question Bank-3
Ch Topic
2 Method Overloading
3 Method Overriding
4 Constructors
5 Exception Handling
7 Abstract Classes
8 Interfaces
Chapter 1
■ Quick Concept
A CLASS is a template/blueprint. An OBJECT is a real-world instance of that class.
Syntax: class ClassName { fields; methods; } | ClassName obj = new ClassName();
Key keywords: class, new, this, static, void, public, private, protected
Memory: objects live on the Heap; references live on the Stack.
Q1.
Define a class 'Car' with fields brand, model, and year. Create an object and display its
details.
■ Solution
public class Car {
String brand;
String model;
int year;
void displayDetails() {
[Link]("Brand: " + brand);
[Link]("Model: " + model);
[Link]("Year: " + year);
}
public static void main(String[] args) {
Car myCar = new Car(); // object creation with 'new'
[Link] = "Toyota";
[Link] = "Corolla";
[Link] = 2022;
[Link]();
}
}
We declare three instance fields (brand, model, year). The 'new' keyword allocates memory on the heap and
returns a reference stored in myCar. Dot-notation accesses each field.
Q2.
Create a class 'BankAccount' with a private balance field. Add deposit() and withdraw()
methods to safely modify it. Demonstrate encapsulation.
■ Solution
public class BankAccount {
private double balance; // private = encapsulated
public void deposit(double amount) {
if (amount > 0) balance += amount;
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance)
balance -= amount;
else
[Link]("Insufficient funds!");
}
public double getBalance() { return balance; }
public static void main(String[] args) {
BankAccount acc = new BankAccount();
[Link](5000);
[Link](1200);
[Link]("Balance: " + [Link]());
}
}
'private' hides the field from outside access — this is encapsulation. Public getter/setter methods act as
controlled gateways. Guards inside the methods prevent negative deposits or overdrafts.
Q3.
Create a 'Student' class with a static field 'schoolName' shared by all students, and an
instance field 'name'. Show the difference between static and instance members.
■ Solution
public class Student {
static String schoolName = "Greenwood High"; // shared by ALL objects
String name; // each object has its own copy
void display() {
[Link](name + " studies at " + schoolName);
}
public static void main(String[] args) {
Student s1 = new Student(); [Link] = "Alice";
Student s2 = new Student(); [Link] = "Bob";
[Link] = "Springfield Academy"; // change affects ALL
[Link]();
[Link]();
}
}
Static members belong to the class, not to any object. Changing schoolName through one reference changes
it for every Student because they all point to the same memory location in the method area.
Q4.
Write a class 'Circle' with a method area() using 'this' keyword to disambiguate field names
from parameter names.
■ Solution
public class Circle {
double radius;
void setRadius(double radius) {
[Link] = radius; // '[Link]' = field; 'radius' = parameter
}
double area() {
return [Link] * [Link] * [Link];
}
public static void main(String[] args) {
Circle c = new Circle();
[Link](7.0);
[Link]("Area = %.2f%n", [Link]());
}
}
'this' is a reference to the current object. It's essential when a parameter has the same name as a field —
without 'this', Java uses the local parameter for both, leaving the field unchanged.
Q5.
Create a class 'Rectangle' and overload the toString() method so printing the object gives
meaningful output.
■ Solution
public class Rectangle {
double length, width;
Rectangle(double length, double width) {
[Link] = length;
[Link] = width;
}
@Override
public String toString() {
return "Rectangle[length=" + length + ", width=" + width +
", area=" + (length * width) + "]";
}
public static void main(String[] args) {
Rectangle r = new Rectangle(5, 3);
[Link](r); // calls toString() automatically
}
}
Every Java class extends Object implicitly. Overriding toString() lets [Link](object) produce
readable output instead of a memory-address hash.
Q6.
Demonstrate object reference vs object copy. Show that two references to the same object
reflect the same change.
■ Solution
public class Box {
int size;
public static void main(String[] args) {
Box b1 = new Box();
[Link] = 10;
Box b2 = b1; // b2 points to the SAME object, not a copy
[Link] = 99;
[Link]("[Link] = " + [Link]); // 99
[Link]("[Link] = " + [Link]); // 99
[Link]("Same? " + (b1 == b2)); // true
}
}
In Java, objects are accessed via references. Assignment copies the reference (memory address), not the
object itself. Both b1 and b2 point to the same heap location, so modifying via b2 is visible through b1.
Q7.
Create a class 'Counter' with a static method to track how many objects have been created.
■ Solution
public class Counter {
private static int count = 0;
private int id;
Counter() {
count++;
[Link] = count;
}
public static int getCount() { return count; }
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter c3 = new Counter();
[Link]("Objects created: " + [Link]()); // 3
}
}
The static field 'count' is shared across all instances. Every time the constructor runs, count increments.
Static methods can only access static fields directly.
Q8.
Illustrate the concept of 'null' and NullPointerException with a safe check.
■ Solution
public class NullDemo {
String name;
public static void main(String[] args) {
NullDemo obj = null;
// Safe null check before accessing
if (obj != null) {
[Link]([Link]);
} else {
[Link]("Object is null — cannot access fields!");
}
// This would throw NullPointerException:
// [Link]([Link]);
}
}
Uninitialized object references hold 'null'. Attempting to call a method or access a field on null causes
NullPointerException at runtime. Always guard with a null-check.
Q9.
Create a 'Product' class with a method that returns 'this' (method chaining / fluent API).
■ Solution
public class Product {
private String name;
private double price;
private int quantity;
public Product setName(String name) { [Link] = name; return this; }
public Product setPrice(double price) { [Link] = price; return this; }
public Product setQuantity(int quantity) { [Link] = quantity; return this; }
public void display() {
[Link](name + " | $" + price + " | Qty: " + quantity);
}
public static void main(String[] args) {
new Product()
.setName("Laptop")
.setPrice(999.99)
.setQuantity(5)
.display(); // method chaining
}
}
Returning 'this' from each setter enables method chaining — a fluent API style where calls are chained
without storing intermediate results. This is common in builders and frameworks like StringBuilder.
Q10.
Compare two objects logically using equals() vs == operator.
■ Solution
public class Person {
String name;
int age;
Person(String name, int age) { [Link] = name; [Link] = age; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
Person other = (Person) obj;
return [Link]([Link]) && [Link] == [Link];
}
public static void main(String[] args) {
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
[Link]("== : " + (p1 == p2)); // false (diff refs)
[Link]("equals: " + [Link](p2)); // true (same content)
}
}
'==' compares memory addresses (are they the exact same object?). equals() can be overridden to compare
content/state. Always override equals() (and hashCode()) for value-based classes.
Chapter 2
Method Overloading
Same name, different signatures — resolved at compile time
■ Quick Concept
Overloading = multiple methods with the SAME name but DIFFERENT parameter lists (type, number, or
order).
Return type alone cannot distinguish overloaded methods.
The compiler resolves which method to call at COMPILE TIME (static/early binding).
Common use: constructors, [Link](), [Link](), [Link]().
Q1.
Write a class 'Calculator' with overloaded add() methods for int, double, and three integers.
■ Solution
public class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) { // overloaded: different param types
return a + b;
}
int add(int a, int b, int c) { // overloaded: different param count
return a + b + c;
}
public static void main(String[] args) {
Calculator calc = new Calculator();
[Link]([Link](2, 3)); // 5
[Link]([Link](2.5, 3.1)); // 5.6
[Link]([Link](1, 2, 3)); // 6
}
}
Java selects the matching method based on argument types and count at compile time. Each add() has a
unique signature. The return type (int vs double) alone would not make them distinct.
Q2.
Demonstrate overloading by parameter ORDER (different types swapped).
■ Solution
public class Display {
void show(String s, int n) {
[Link]("String then int: " + s + ", " + n);
}
void show(int n, String s) { // order swapped
[Link]("Int then String: " + n + ", " + s);
}
public static void main(String[] args) {
Display d = new Display();
[Link]("Hello", 10);
[Link](10, "Hello");
}
}
Changing the ORDER of parameter types creates a new signature. The compiler matches call arguments
left-to-right against each overloaded version.
Q3.
Overload a printArea() method for a circle, rectangle, and triangle.
■ Solution
public class AreaPrinter {
void printArea(double radius) {
[Link]("Circle area: %.2f%n", [Link] * radius * radius);
}
void printArea(double length, double width) {
[Link]("Rectangle area: %.2f%n", length * width);
}
void printArea(double base, double height, boolean isTriangle) {
[Link]("Triangle area: %.2f%n", 0.5 * base * height);
}
public static void main(String[] args) {
AreaPrinter ap = new AreaPrinter();
[Link](7.0);
[Link](5.0, 3.0);
[Link](6.0, 4.0, true);
}
}
One name (printArea) serves three shapes. The boolean flag in the third version distinguishes it from the
two-double rectangle version — a common but slightly hacky trick; generally prefer the approach of clearly
different parameter counts.
Q4.
Show how type promotion works during overload resolution: passing an int where a long or
double is expected.
■ Solution
public class Promotion {
void test(long x) {
[Link]("long version called: " + x);
}
void test(double x) {
[Link]("double version called: " + x);
}
public static void main(String[] args) {
Promotion p = new Promotion();
[Link](10); // int is promoted to long (closer match)
[Link](10L); // exact match: long
[Link](10.0); // exact match: double
}
}
When no exact match exists, Java promotes the argument to the next wider type:
byte→short→int→long→float→double. Here, the int literal 10 is widened to long because long is the first
compatible overload.
Q5.
Overload a 'concat' method for String+String, int+String, and varargs.
■ Solution
public class Concat {
String concat(String a, String b) { return a + b; }
String concat(int n, String s) { return n + s; }
String concat(String... parts) { // varargs
StringBuilder sb = new StringBuilder();
for (String p : parts) [Link](p);
return [Link]();
}
public static void main(String[] args) {
Concat c = new Concat();
[Link]([Link]("Hello", " World"));
[Link]([Link](42, " is the answer"));
[Link]([Link]("A", "B", "C", "D"));
}
}
Varargs (String... parts) allows zero or more arguments of the same type and is treated as an array internally.
It must be the last parameter and matches when no more-specific overload is found.
Q6.
Show that overloading CANNOT be done by return type alone — and why it causes a
compile error.
■ Solution
// This code WON'T compile — shown to illustrate the rule:
// public class BadOverload {
// int getValue() { return 1; }
// double getValue() { return 1.0; } // COMPILE ERROR
// }
// CORRECT: differentiate by parameter
public class GoodOverload {
int getValue(int x) { return x; }
double getValue(double x) { return x; }
public static void main(String[] args) {
GoodOverload g = new GoodOverload();
[Link]([Link](5)); // int version
[Link]([Link](5.0)); // double version
}
}
The compiler resolves method calls based on argument types, not the expected return type at the call site.
Two methods identical in name and parameters but differing only in return type are ambiguous to the
compiler.
Q7.
Create a 'Logger' class with overloaded log() methods: one with just a message, one with a
level (INFO/WARN/ERROR), and one with level + timestamp.
■ Solution
import [Link];
public class Logger {
void log(String message) {
[Link]("[LOG] " + message);
}
void log(String level, String message) {
[Link]("[" + level + "] " + message);
}
void log(String level, String message, LocalDateTime time) {
[Link]("[" + level + "] " + time + " - " + message);
}
public static void main(String[] args) {
Logger logger = new Logger();
[Link]("Application started");
[Link]("WARN", "Low memory");
[Link]("ERROR", "Crash!", [Link]());
}
}
A real-world overloading use-case: the simplest form needs just a message; more detailed forms accept
optional metadata. This avoids having separate logInfo(), logWarn(), logError() methods.
Q8.
Overload a 'power' method to compute integer powers and double powers, and also one that
uses a default exponent of 2.
■ Solution
public class MathUtils {
long power(int base, int exp) {
long result = 1;
for (int i = 0; i < exp; i++) result *= base;
return result;
}
double power(double base, int exp) {
return [Link](base, exp);
}
long power(int base) { // default exponent = 2 (square)
return power(base, 2);
}
public static void main(String[] args) {
MathUtils m = new MathUtils();
[Link]([Link](3, 4)); // 81
[Link]([Link](2.5, 3)); // 15.625
[Link]([Link](7)); // 49
}
}
The one-argument version delegates to the two-argument version — a common pattern to avoid code
duplication while still providing a convenient shorthand.
Q9.
Write a 'Converter' class with overloaded convert() methods: miles-to-km,
Celsius-to-Fahrenheit, and kg-to-pounds.
■ Solution
public class Converter {
double convert(double miles, String fromUnit) {
if ([Link]("miles")) return miles * 1.60934;
if ([Link]("km")) return miles / 1.60934;
return -1;
}
double convert(double celsius, boolean toFahrenheit) {
return toFahrenheit ? (celsius * 9/5) + 32 : (celsius - 32) * 5/9;
}
double convert(int kg) { // kg to pounds
return kg * 2.20462;
}
public static void main(String[] args) {
Converter cv = new Converter();
[Link]("5 miles = %.2f km%n", [Link](5, "miles"));
[Link]("100C = %.1fF%n", [Link](100, true));
[Link]("70 kg = %.1f lbs%n", [Link](70));
}
}
A practical converter: each convert() handles a different unit. The boolean flag differentiates the temperature
direction. Choosing distinctive parameter combinations keeps signatures unambiguous.
Q10.
Demonstrate method overloading with null argument — understand which overloaded
method Java calls.
■ Solution
public class NullOverload {
void display(String s) {
[Link]("String: " + s);
}
void display(Object o) {
[Link]("Object: " + o);
}
public static void main(String[] args) {
NullOverload no = new NullOverload();
[Link]((String) null); // explicit cast → String version
[Link]((Object) null); // explicit cast → Object version
// [Link](null); // would be AMBIGUOUS — compile error
}
}
When null is passed without a cast, the compiler cannot decide between String and Object (both can hold
null). Casting resolves the ambiguity by explicitly choosing the overload. Java always tries the most-specific
(narrowest) type match first.
Chapter 3
Method Overriding
Redefining parent behaviour in a child class — resolved at runtime
■ Quick Concept
Overriding = child class provides its OWN implementation of a method already defined in the parent.
Rules: same name + same parameter list + same (or covariant) return type. Must use @Override annotation.
Access modifier cannot be MORE restrictive in the child (e.g., public→protected is illegal).
Static methods are NOT overridden — they are HIDDEN. Private methods cannot be overridden.
Resolved at RUNTIME via dynamic dispatch (polymorphism / late binding).
Q1.
Create a parent class 'Animal' with a speak() method and override it in 'Dog' and 'Cat'.
■ Solution
class Animal {
void speak() {
[Link]("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void speak() {
[Link]("Dog says: Woof!");
}
}
class Cat extends Animal {
@Override
void speak() {
[Link]("Cat says: Meow!");
}
}
public class TestOverride {
public static void main(String[] args) {
Animal a = new Animal();
Animal d = new Dog(); // parent reference, child object
Animal c = new Cat();
[Link](); // Animal makes a sound
[Link](); // Dog says: Woof! ← runtime dispatch
[Link](); // Cat says: Meow!
}
}
Even though d is declared as Animal, the JVM checks the actual object type at runtime and calls Dog's
speak(). This is runtime polymorphism — the cornerstone of OOP.
Q2.
Use [Link]() inside an overriding method to call the parent version.
■ Solution
class Vehicle {
void start() {
[Link]("Vehicle engine started");
}
}
class ElectricCar extends Vehicle {
@Override
void start() {
[Link](); // call parent's start() first
[Link]("Electric motor engaged silently");
}
}
public class TestSuper {
public static void main(String[] args) {
ElectricCar ec = new ElectricCar();
[Link]();
}
}
[Link]() invokes the parent class method from within the overriding child method. This is useful when you
want to extend (not completely replace) parent behaviour — a common pattern in GUI toolkits and
frameworks.
Q3.
Demonstrate that static methods are HIDDEN, not overridden — the reference type decides
which version runs.
■ Solution
class Parent {
static void greet() {
[Link]("Hello from Parent");
}
}
class Child extends Parent {
static void greet() { // method HIDING, not overriding
[Link]("Hello from Child");
}
}
public class StaticHiding {
public static void main(String[] args) {
Parent p = new Child();
[Link](); // prints Parent! (no runtime dispatch for static)
[Link](); // prints Child
}
}
Static methods are resolved at compile time using the REFERENCE type, not the object type. That's why
[Link]() calls Parent's version even though p holds a Child object. Only instance methods are
polymorphically dispatched.
Q4.
Show that a private method in the parent cannot be overridden — the child creates a NEW
method.
■ Solution
class Base {
private void secret() {
[Link]("Base secret");
}
void callSecret() { secret(); } // only Base can call this
}
class Derived extends Base {
// This is NOT an override — it's a brand new method
private void secret() {
[Link]("Derived secret");
}
}
public class PrivateTest {
public static void main(String[] args) {
Derived d = new Derived();
[Link](); // prints 'Base secret' (Base's secret() is called)
}
}
Private methods are invisible to subclasses. What looks like an override is actually a completely separate
method in Derived. callSecret() in Base always calls Base's own private secret(), never Derived's.
Q5.
Override the equals() and hashCode() methods in a 'Point' class for logical equality.
■ Solution
import [Link];
public class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Point)) return false;
Point p = (Point) obj;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return [Link](x, y); // consistent with equals
}
public static void main(String[] args) {
Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);
[Link]([Link](p2)); // true
[Link]([Link]() == [Link]()); // true
}
}
Java's contract: if two objects are equal (equals() returns true), they MUST have the same hashCode().
Breaking this contract causes bugs in HashMaps and HashSets. [Link]() is a convenient way to
combine fields.
Q6.
Show covariant return types — the overriding method may return a subtype of the parent's
return type.
■ Solution
class Fruit {
Fruit get() { return new Fruit(); }
public String toString() { return "Fruit"; }
}
class Mango extends Fruit {
@Override
Mango get() { // return type narrowed to Mango (covariant)
return new Mango();
}
public String toString() { return "Mango"; }
}
public class CovariantTest {
public static void main(String[] args) {
Fruit f = new Mango();
[Link]([Link]()); // Mango (runtime dispatch)
}
}
Since Java 5, an overriding method may declare a return type that is a subclass of the parent's return type.
This is called covariant return type and is useful in builder/factory patterns.
Q7.
Demonstrate runtime polymorphism using an array of Animal references.
■ Solution
class Shape {
double area() { return 0; }
}
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
@Override double area() { return [Link] * r * r; }
}
class Rect extends Shape {
double l, w;
Rect(double l, double w) { this.l = l; this.w = w; }
@Override double area() { return l * w; }
}
public class Poly {
public static void main(String[] args) {
Shape[] shapes = { new Circle(5), new Rect(4, 6), new Circle(3) };
for (Shape s : shapes)
[Link]("Area = %.2f%n", [Link]());
}
}
A single loop works for all shape types. Adding a new shape (Triangle) requires zero changes to the loop —
just create a new subclass. This is the Open/Closed Principle enabled by runtime polymorphism.
Q8.
Show that you cannot reduce visibility when overriding (public→protected is a compile
error).
■ Solution
class Base {
public void show() {
[Link]("Base show");
}
}
class Child extends Base {
// ILLEGAL: cannot reduce access from public to protected
// protected void show() { } // COMPILE ERROR
@Override
public void show() { // must be same or MORE visible
[Link]("Child show");
}
}
public class AccessTest {
public static void main(String[] args) {
Base b = new Child();
[Link](); // Child show
}
}
Subclasses can WIDEN visibility (protected→public) but never NARROW it. If callers have a Base reference
and expect to call a public method, the override must remain at least as accessible.
Q9.
Override toString() and compareTo() in a 'Temperature' class to enable natural ordering.
■ Solution
public class Temperature implements Comparable<Temperature> {
private double celsius;
Temperature(double celsius) { [Link] = celsius; }
@Override
public String toString() {
return [Link]("%.1f°C", celsius);
}
@Override
public int compareTo(Temperature other) {
return [Link]([Link], [Link]);
}
public static void main(String[] args) {
Temperature t1 = new Temperature(100);
Temperature t2 = new Temperature(37);
[Link](t1); // 100.0°C
[Link]([Link](t2) > 0 ? t1 + " is hotter" : t2 + " is hotter");
}
}
Implementing Comparable and overriding compareTo() lets Java sort Temperature objects with
[Link](). The convention: return negative if this < other, 0 if equal, positive if this > other.
Q10.
Override finalize() (deprecated but educational) — and show the modern try-with-resources
alternative.
■ Solution
// Old approach (Java < 9) — do NOT rely on this
class OldResource {
@Override
@Deprecated
protected void finalize() throws Throwable {
[Link]("finalize() called (unreliable)");
[Link]();
}
}
// Modern approach: implement AutoCloseable
class ModernResource implements AutoCloseable {
public ModernResource() {
[Link]("Resource opened");
}
@Override
public void close() {
[Link]("Resource closed (guaranteed)");
}
}
public class ResourceDemo {
public static void main(String[] args) {
try (ModernResource r = new ModernResource()) {
[Link]("Using resource");
} // close() called automatically here
}
}
finalize() is called by GC before object collection — timing is unreliable and it was deprecated in Java 9,
removed in 18. The modern alternative: implement AutoCloseable and use try-with-resources for guaranteed,
deterministic cleanup.
Chapter 4
Constructors
Initialising objects — default, parameterised, copy, and chaining
■ Quick Concept
A constructor has the SAME name as the class, NO return type (not even void), called via 'new'.
Types: Default (no-arg, compiler-generated if none written), Parameterised, Copy Constructor.
this() calls another constructor in the same class (must be first statement).
super() calls parent's constructor (must be first statement; implicit if omitted).
If you write ANY constructor, the compiler no longer provides the default one.
Q1.
Show the default (no-arg) constructor — the one Java provides when you write none.
■ Solution
public class Dog {
String name;
String breed;
// No constructor written — Java provides: public Dog() {}
public static void main(String[] args) {
Dog d = new Dog(); // uses compiler-generated default constructor
[Link] = "Buddy";
[Link] = "Labrador";
[Link]([Link] + " - " + [Link]);
}
}
When no constructor is defined, Java inserts a public no-arg constructor that calls super() implicitly. Fields get
default values: 0 for numbers, false for boolean, null for objects.
Q2.
Write a parameterised constructor and a no-arg constructor for the same class.
■ Solution
public class Book {
String title;
String author;
double price;
Book() { // no-arg constructor
this("Unknown", "Unknown", 0.0);// calls parameterised via this()
}
Book(String title, String author, double price) { // parameterised
[Link] = title;
[Link] = author;
[Link] = price;
}
void display() {
[Link](title + " by " + author + " - $" + price);
}
public static void main(String[] args) {
Book b1 = new Book();
Book b2 = new Book("Clean Code", "R. Martin", 35.99);
[Link]();
[Link]();
}
}
this('...') inside the no-arg constructor chains to the parameterised one — avoiding code duplication. Note:
this() must be the very first statement of the constructor body.
Q3.
Write a copy constructor that creates a deep copy of an object.
■ Solution
public class Address {
String street;
String city;
Address(String street, String city) {
[Link] = street;
[Link] = city;
}
// Copy constructor
Address(Address other) {
[Link] = [Link];
[Link] = [Link];
}
public static void main(String[] args) {
Address a1 = new Address("123 Main St", "Springfield");
Address a2 = new Address(a1); // copy constructor
[Link] = "Shelbyville"; // change a2 — a1 unaffected
[Link]("a1: " + [Link]); // Springfield
[Link]("a2: " + [Link]); // Shelbyville
}
}
A copy constructor accepts an object of the same class and copies its fields. This gives a logically
independent object (same data, different memory). For nested objects you'd recursively copy those too (deep
copy).
Q4.
Demonstrate constructor chaining with this() — three constructors building on each other.
■ Solution
public class Pizza {
String size;
String crust;
String topping;
Pizza() {
this("Medium"); // chain 1 → 2
}
Pizza(String size) {
this(size, "Thin"); // chain 2 → 3
}
Pizza(String size, String crust) {
this(size, crust, "Cheese"); // chain 3 → full
}
Pizza(String size, String crust, String topping) {
[Link] = size;
[Link] = crust;
[Link] = topping;
}
void describe() {
[Link](size + " " + crust + " pizza with " + topping);
}
public static void main(String[] args) {
new Pizza().describe();
new Pizza("Large").describe();
new Pizza("Small","Thick","Pepperoni").describe();
}
}
Constructor chaining via this() avoids repeating initialisation logic. Each simpler constructor delegates to the
fuller one. Ultimately only the most parameterised constructor actually sets the fields.
Q5.
Show that if you define a parameterised constructor, the default no-arg constructor is LOST
unless you explicitly write it.
■ Solution
class Robot {
String model;
Robot(String model) { // parameterised — default is now gone
[Link] = model;
}
}
public class RobotTest {
public static void main(String[] args) {
Robot r1 = new Robot("R2D2"); // OK
// Robot r2 = new Robot(); // COMPILE ERROR: no default constructor
[Link]([Link]);
}
}
This is one of the most common beginner mistakes. The moment you declare any constructor, Java stops
generating the default one. If you need both, explicitly write the no-arg version.
Q6.
Create an immutable class using a constructor — fields final, no setters.
■ Solution
public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
// Instead of modify, return a new object
public ImmutablePoint translate(int dx, int dy) {
return new ImmutablePoint(x + dx, y + dy);
}
public static void main(String[] args) {
ImmutablePoint p = new ImmutablePoint(3, 4);
ImmutablePoint q = [Link](1, 1);
[Link]("p: " + [Link]() + "," + [Link]());
[Link]("q: " + [Link]() + "," + [Link]());
}
}
Immutability recipe: final class (no subclassing), private final fields, only getters, constructor sets all fields
once. Java's String and Integer follow this pattern. Immutable objects are inherently thread-safe.
Q7.
Use a private constructor to implement the Singleton pattern.
■ Solution
public class Database {
private static Database instance; // single instance
private String url;
private Database() { // private: no one outside can call new
[Link] = "jdbc:mysql://localhost/mydb";
[Link]("DB connection established");
}
public static Database getInstance() {
if (instance == null)
instance = new Database(); // created only once
return instance;
}
public String getUrl() { return url; }
public static void main(String[] args) {
Database d1 = [Link]();
Database d2 = [Link]();
[Link](d1 == d2); // true — same object
[Link]([Link]());
}
}
The Singleton pattern ensures only one instance of a class exists. The private constructor prevents external
instantiation. getInstance() acts as the single access point. Used for database pools, loggers, configuration
managers.
Q8.
Demonstrate constructor with an array field — show shallow vs deep initialisation.
■ Solution
public class GradeBook {
String student;
int[] marks;
GradeBook(String student, int[] marks) {
[Link] = student;
[Link] = [Link](); // defensive copy (deep)
}
void printMarks() {
[Link](student + ": ");
for (int m : marks) [Link](m + " ");
[Link]();
}
public static void main(String[] args) {
int[] scores = {90, 85, 78};
GradeBook gb = new GradeBook("Alice", scores);
scores[0] = 0; // original array changed
[Link](); // gb still shows 90 (defensive copy)
}
}
Without clone(), both scores and [Link] would point to the same array — modifying scores would corrupt
the GradeBook. Defensive copying in the constructor protects immutability.
Q9.
Show the order of execution: static block → instance initialiser → constructor.
■ Solution
public class InitOrder {
static int staticCount;
int instanceId;
static {
staticCount = 100;
[Link]("1. Static block runs once: " + staticCount);
}
{
instanceId = ++staticCount;
[Link]("2. Instance initialiser: " + instanceId);
}
InitOrder() {
[Link]("3. Constructor: id=" + instanceId);
}
public static void main(String[] args) {
[Link]("--- Creating first object ---");
new InitOrder();
[Link]("--- Creating second object ---");
new InitOrder();
}
}
Order: Static blocks (once, when class loads) → Instance initialisers (each time new is called) → Constructor
body. Understanding this order prevents subtle bugs with complex initialisations.
Q10.
Implement a Factory Method using constructors — returning different subtypes based on
input.
■ Solution
class Shape {
String type;
Shape(String type) { [Link] = type; }
void draw() { [Link]("Drawing " + type); }
}
class Circle extends Shape { Circle() { super("Circle"); } }
class Square extends Shape { Square() { super("Square"); } }
class Triangle extends Shape { Triangle() { super("Triangle"); } }
public class ShapeFactory {
public static Shape create(String type) {
switch ([Link]()) {
case "circle": return new Circle();
case "square": return new Square();
case "triangle": return new Triangle();
default: throw new IllegalArgumentException("Unknown: " + type);
}
}
public static void main(String[] args) {
Shape s1 = [Link]("circle");
Shape s2 = [Link]("triangle");
[Link]();
[Link]();
}
}
The Factory Method pattern encapsulates object creation. Callers don't use 'new' directly — they ask the
factory. This decouples the client from concrete types and makes adding new shapes easy (Open/Closed
Principle).
Chapter 5
Exception Handling
Graceful error management — try, catch, finally, throw, throws, custom exceptions
■ Quick Concept
Exception hierarchy: Throwable → Error (JVM errors, don't catch) / Exception.
Exception → RuntimeException (unchecked) | Checked Exceptions (must handle).
Checked: IOException, SQLException — compiler forces you to handle them.
Unchecked (RuntimeException): NullPointerException, ArrayIndexOutOfBoundsException, etc.
Keywords: try, catch, finally, throw (throw an exception), throws (declare in signature).
finally always runs (even after return) unless [Link]() is called.
Q1.
Basic try-catch — handle an ArithmeticException (divide by zero).
■ Solution
public class DivisionDemo {
public static void main(String[] args) {
int a = 10, b = 0;
try {
int result = a / b; // throws ArithmeticException
[Link]("Result: " + result);
} catch (ArithmeticException e) {
[Link]("Error: " + [Link]()); // / by zero
}
[Link]("Program continues after exception");
}
}
Without the try-catch, the program crashes. The catch block intercepts the exception, runs the handler, and
the program continues normally after the try-catch block.
Q2.
Use finally to ensure a resource (simulated) is always closed.
■ Solution
public class FinallyDemo {
static void readFile(String name) {
[Link]("Opening file: " + name);
try {
if (name == null) throw new NullPointerException("No file name");
[Link]("Reading file...");
} catch (NullPointerException e) {
[Link]("Caught: " + [Link]());
} finally {
[Link]("Closing file (always runs)"); // always executes
}
}
public static void main(String[] args) {
readFile("[Link]");
readFile(null);
}
}
finally runs whether or not an exception occurred — it's the right place for cleanup (closing streams, DB
connections). In real code, prefer try-with-resources for AutoCloseable resources.
Q3.
Catch multiple exceptions in separate catch blocks and use multi-catch (|) syntax.
■ Solution
public class MultiCatch {
public static void main(String[] args) {
String[] data = {"42", null, "abc"};
for (String s : data) {
try {
int value = [Link](s); // may throw NumberFormatException
int result = 100 / value; // may throw ArithmeticException
[Link]("Result: " + result);
} catch (NumberFormatException | ArithmeticException e) {
// multi-catch: handle both the same way
[Link]("Handled: " + [Link]().getSimpleName() + ": " +
[Link]());
} catch (NullPointerException e) {
[Link]("Null input provided");
}
}
}
}
Multi-catch (Java 7+) with | combines handlers for exceptions that need identical treatment. Order matters:
catch more specific exceptions BEFORE general ones (catching Exception first would swallow everything).
Q4.
Create and throw a custom checked exception for invalid age input.
■ Solution
class InvalidAgeException extends Exception { // checked — must be declared/caught
int age;
InvalidAgeException(int age) {
super("Invalid age: " + age + ". Must be 0-150.");
[Link] = age;
}
}
public class AgeValidator {
static void validateAge(int age) throws InvalidAgeException {
if (age < 0 || age > 150)
throw new InvalidAgeException(age);
[Link]("Age " + age + " is valid.");
}
public static void main(String[] args) {
try {
validateAge(25);
validateAge(-5); // throws
} catch (InvalidAgeException e) {
[Link]("Caught: " + [Link]());
[Link]("Bad age value was: " + [Link]);
}
}
}
Custom exceptions extend Exception (checked) or RuntimeException (unchecked). Extend Exception when
the caller MUST handle the error. Adding fields (like 'age') lets callers access error-specific data.
Q5.
Create a custom unchecked exception and use it in a method chain.
■ Solution
class InsufficientFundsException extends RuntimeException {
double amount;
InsufficientFundsException(double amount) {
super("Need $" + amount + " more");
[Link] = amount;
}
}
public class Wallet {
private double balance;
Wallet(double balance) { [Link] = balance; }
void spend(double amount) {
if (amount > balance)
throw new InsufficientFundsException(amount - balance);
balance -= amount;
[Link]("Spent $" + amount + ". Remaining: $" + balance);
}
public static void main(String[] args) {
Wallet w = new Wallet(100);
try {
[Link](60);
[Link](70); // throws
} catch (InsufficientFundsException e) {
[Link]([Link]());
}
}
}
Unchecked exceptions (extending RuntimeException) don't require 'throws' declarations. They propagate
silently up the call stack until caught or the program crashes. Use for programming errors; use checked for
recoverable situations.
Q6.
Demonstrate exception chaining — wrapping a low-level exception in a higher-level one.
■ Solution
class ServiceException extends Exception {
ServiceException(String msg, Throwable cause) {
super(msg, cause); // cause = original exception
}
}
public class ExceptionChaining {
static void connectDB() throws Exception {
throw new [Link]("Connection refused");
}
static void loadUser() throws ServiceException {
try {
connectDB();
} catch (Exception e) {
throw new ServiceException("Failed to load user", e); // wrap
}
}
public static void main(String[] args) {
try {
loadUser();
} catch (ServiceException e) {
[Link]("High-level: " + [Link]());
[Link]("Root cause: " + [Link]().getMessage());
}
}
}
Exception chaining preserves the original cause while adding higher-level context. getCause() retrieves the
original. This is essential for debugging layered architectures (DAO → Service → Controller).
Q7.
Use try-with-resources to auto-close a custom resource.
■ Solution
class FileResource implements AutoCloseable {
String name;
FileResource(String name) {
[Link] = name;
[Link]("Opened: " + name);
}
void read() {
[Link]("Reading: " + name);
}
@Override
public void close() {
[Link]("Closed: " + name);
}
}
public class TryWithRes {
public static void main(String[] args) {
try (FileResource f1 = new FileResource("[Link]");
FileResource f2 = new FileResource("[Link]")) {
[Link]();
[Link]();
} // close() called in REVERSE order: B then A
}
}
Any class implementing AutoCloseable can be used in try-with-resources. close() is guaranteed to run even if
an exception occurs inside. Multiple resources are closed in reverse declaration order.
Q8.
Show re-throwing an exception and the difference between 'throw e' and 'throw new ...'.
■ Solution
public class Rethrow {
static void process(int n) throws ArithmeticException {
try {
int r = 100 / n;
[Link]("Result: " + r);
} catch (ArithmeticException e) {
[Link]("Logging in process(): " + [Link]());
throw e; // rethrow the SAME exception (preserves stack trace)
}
}
public static void main(String[] args) {
try {
process(0);
} catch (ArithmeticException e) {
[Link]("Caught in main(): " + [Link]());
}
}
}
Re-throwing (throw e) passes the same exception object up the stack — the original stack trace is preserved.
Creating a new exception (throw new ...) starts a fresh stack trace, hiding the origin. Prefer re-throw for
debugging clarity.
Q9.
Demonstrate the exception hierarchy and catch order — specific before general.
■ Solution
public class HierarchyDemo {
public static void main(String[] args) {
try {
String s = null;
[Link](); // NullPointerException
}
catch (NullPointerException e) { // most specific first
[Link]("NPE: " + [Link]().getName());
}
catch (RuntimeException e) { // less specific
[Link]("RuntimeException: " + [Link]());
}
catch (Exception e) { // least specific
[Link]("Exception: " + [Link]());
}
}
}
Java matches catch blocks top-to-bottom. Placing 'Exception' first would catch everything — making all
subsequent blocks unreachable (compile error). Always order from most specific to most general.
Q10.
Build a robust input-parsing utility with proper exception handling and user-friendly
messages.
■ Solution
import [Link];
public class SafeInput {
static int readPositiveInt(Scanner sc) {
while (true) {
try {
[Link]("Enter a positive integer: ");
int n = [Link]([Link]().trim());
if (n <= 0) throw new IllegalArgumentException("Must be positive");
return n;
} catch (NumberFormatException e) {
[Link]("Not a number. Try again.");
} catch (IllegalArgumentException e) {
[Link]([Link]() + ". Try again.");
}
}
}
public static void main(String[] args) {
try (Scanner sc = new Scanner([Link])) {
int n = readPositiveInt(sc);
[Link]("You entered: " + n);
}
}
}
A production-quality input loop: the while(true) loop retries on bad input instead of crashing. Scanner is
wrapped in try-with-resources. Each distinct error type gets a specific, helpful message.
Chapter 6
■ Quick Concept
Inheritance: class Child extends Parent — Child inherits non-private members.
Java supports Single, Multilevel, Hierarchical inheritance. NOT multiple class inheritance.
Constructor chaining: child constructor ALWAYS calls a parent constructor first (super()).
If you don't call super() explicitly, Java inserts super() — which requires a no-arg parent constructor.
Types: Single (A→B), Multilevel (A→B→C), Hierarchical (A→B, A→C), Hybrid (via interfaces).
Method Resolution Order: child → parent → grandparent (up the chain).
Q1.
Single Inheritance — Employee extends Person.
■ Solution
class Person {
String name;
int age;
Person(String name, int age) {
[Link] = name;
[Link] = age;
}
void introduce() {
[Link]("I am " + name + ", age " + age);
}
}
class Employee extends Person {
String department;
double salary;
Employee(String name, int age, String dept, double salary) {
super(name, age); // must call parent constructor
[Link] = dept;
[Link] = salary;
}
void showDetails() {
introduce(); // inherited method
[Link]("Dept: " + department + ", Salary: " + salary);
}
}
public class SingleInheritance {
public static void main(String[] args) {
Employee e = new Employee("Alice", 30, "IT", 75000);
[Link]();
}
}
super(name, age) must be the FIRST statement. It calls Person's constructor, ensuring the parent part of the
object is fully initialised before the child adds its own fields.
Q2.
Multilevel Inheritance — Animal → Mammal → Dog.
■ Solution
class Animal {
String type;
Animal(String type) {
[Link] = type;
[Link]("Animal created: " + type);
}
void breathe() { [Link](type + " breathes"); }
}
class Mammal extends Animal {
boolean warmBlooded;
Mammal(String type) {
super(type);
[Link] = true;
[Link]("Mammal init");
}
void feedMilk() { [Link](type + " feeds milk"); }
}
class Dog extends Mammal {
String name;
Dog(String name) {
super("Dog");
[Link] = name;
[Link]("Dog named " + name + " created");
}
void bark() { [Link](name + " says Woof!"); }
}
public class MultiLevel {
public static void main(String[] args) {
Dog d = new Dog("Rex");
[Link](); // inherited from Animal
[Link](); // inherited from Mammal
[Link](); // own method
}
}
Constructor output shows the chain: Animal → Mammal → Dog. Each level calls super() first, so initialisations
happen top-down. Methods are available from all ancestors via the chain.
Q3.
Hierarchical Inheritance — Shape as parent, Circle and Rectangle as siblings.
■ Solution
class Shape {
String color;
Shape(String color) {
[Link] = color;
}
void displayColor() {
[Link]("Color: " + color);
}
}
class Circle extends Shape {
double radius;
Circle(String color, double radius) {
super(color);
[Link] = radius;
}
double area() { return [Link] * radius * radius; }
}
class Rectangle extends Shape {
double l, w;
Rectangle(String color, double l, double w) {
super(color);
this.l = l; this.w = w;
}
double area() { return l * w; }
}
public class HierarchicalDemo {
public static void main(String[] args) {
Circle c = new Circle("Red", 5);
Rectangle r = new Rectangle("Blue", 4, 6);
[Link]();
[Link]("Circle area: %.2f%n", [Link]());
[Link]();
[Link]("Rect area: %.2f%n", [Link]());
}
}
Both Circle and Rectangle independently inherit from Shape. They share displayColor() but each has its own
area(). This is hierarchical inheritance — one parent, multiple children.
Q4.
Show that Java DOES NOT support multiple class inheritance — and why interfaces solve
this.
■ Solution
// ILLEGAL in Java:
// class C extends A, B { } // compile error
// Solution: use interfaces
interface Flyable { default void fly() { [Link]("Flying!"); } }
interface Swimmable { default void swim() { [Link]("Swimming!"); } }
class Duck implements Flyable, Swimmable {
void quack() { [Link]("Quack!"); }
}
public class MultipleInterface {
public static void main(String[] args) {
Duck d = new Duck();
[Link]();
[Link]();
[Link]();
}
}
Java avoids the 'Diamond Problem' by forbidding multiple class inheritance. Interfaces (with default methods
since Java 8) provide a safe alternative — a class can implement many interfaces.
Q5.
Demonstrate the IS-A test and 'instanceof' keyword in an inheritance hierarchy.
■ Solution
class Vehicle {}
class Car extends Vehicle {}
class Sedan extends Car {}
public class InstanceofDemo {
public static void main(String[] args) {
Sedan s = new Sedan();
[Link](s instanceof Sedan); // true
[Link](s instanceof Car); // true (IS-A Car)
[Link](s instanceof Vehicle); // true (IS-A Vehicle)
[Link](s instanceof Object); // true (everything IS-A Object)
Vehicle v = new Car();
[Link](v instanceof Sedan); // false (not a Sedan)
}
}
instanceof checks the actual runtime type, not the reference type. A Sedan object is simultaneously a Sedan,
Car, Vehicle, and Object. This is the Liskov Substitution Principle in action.
Q6.
Use super to access a parent field that is shadowed by a child field.
■ Solution
class ParentClass {
int value = 10;
void display() {
[Link]("Parent value: " + value);
}
}
class ChildClass extends ParentClass {
int value = 20; // shadows parent's value
void display() {
[Link]("Child value: " + value);
[Link]("Parent value via super: " + [Link]);
[Link]();
}
}
public class SuperFieldDemo {
public static void main(String[] args) {
new ChildClass().display();
}
}
Fields are NOT polymorphically dispatched — shadowing (not overriding) applies. [Link] explicitly
accesses the parent's version. [Link]() calls the parent's method. Avoid field shadowing in practice —
it causes confusion.
Q7.
Multilevel constructor chaining — see the full super() call sequence printed.
■ Solution
class A {
A() { [Link]("A no-arg"); }
A(int x) { [Link]("A(int): " + x); }
}
class B extends A {
B() { super(42); [Link]("B no-arg"); }
B(int x) { [Link]("B(int): " + x); }
}
class C extends B {
C() { [Link]("C no-arg"); }
}
public class ConstructorChain {
public static void main(String[] args) {
[Link]("--- new C() ---");
new C(); // C() → B() → A(42)
[Link]("--- new B(5) ---");
new B(5); // B(int) → implicit super() → A()
}
}
'new C()': C() doesn't call super explicitly → Java inserts super() → B() → which calls super(42) → A(int). The
chain flows upward before returning downward. Understanding this order is critical for debugging initialisation
issues.
Q8.
Prevent inheritance using the 'final' keyword on a class.
■ Solution
final class ImmutableConfig {
final String host;
final int port;
ImmutableConfig(String host, int port) {
[Link] = host;
[Link] = port;
}
void print() {
[Link]("Connecting to " + host + ":" + port);
}
}
// class ExtendedConfig extends ImmutableConfig { } // COMPILE ERROR
public class FinalClassDemo {
public static void main(String[] args) {
ImmutableConfig cfg = new ImmutableConfig("localhost", 8080);
[Link]();
}
}
'final' on a class locks it — no subclass can extend it. Java's String, Integer, and all primitive wrapper classes
are final. This guarantees behaviour cannot be altered by subclassing.
Q9.
Demonstrate upcasting and downcasting with instanceof safety check.
■ Solution
class Animal { void sound() { [Link]("..."); } }
class Dog extends Animal {
@Override void sound() { [Link]("Woof"); }
void fetch() { [Link]("Fetching!"); }
}
public class CastingDemo {
public static void main(String[] args) {
// Upcasting (automatic, safe)
Animal a = new Dog();
[Link](); // Woof (polymorphism)
// [Link](); // compile error — Animal ref can't see fetch()
// Downcasting (manual, may throw ClassCastException)
if (a instanceof Dog) {
Dog d = (Dog) a;
[Link](); // now accessible
}
// Java 16+ pattern matching instanceof
if (a instanceof Dog dog) {
[Link](); // no explicit cast needed
}
}
}
Upcasting widens the reference — safe and automatic. Downcasting narrows it — unsafe without instanceof
check. Java 16+ pattern-matching instanceof combines the check and cast into one clean expression.
Q10.
Build a complete inheritance hierarchy: Employee → Manager → Director with salary
progression.
■ Solution
class Employee {
String name; double baseSalary;
Employee(String name, double base) { [Link]=name; [Link]=base; }
double totalSalary() { return baseSalary; }
void print() { [Link]("%s: $%.0f%n", name, totalSalary()); }
}
class Manager extends Employee {
double teamBonus;
Manager(String name, double base, double bonus) {
super(name, base);
[Link] = bonus;
}
@Override double totalSalary() { return baseSalary + teamBonus; }
}
class Director extends Manager {
double stockOptions;
Director(String name, double base, double bonus, double stock) {
super(name, base, bonus);
[Link] = stock;
}
@Override double totalSalary() { return [Link]() + stockOptions; }
}
public class OrgChart {
public static void main(String[] args) {
Employee[] staff = {
new Employee("Alice", 50000),
new Manager("Bob", 70000, 15000),
new Director("Carol", 100000, 30000, 50000)
};
for (Employee e : staff) [Link](); // polymorphic dispatch
}
}
Each level adds its own salary components and calls [Link]() to include the parent's calculation.
The polymorphic loop calls the correct overridden version for each runtime type.
Chapter 7
Abstract Classes
Partial blueprints — define the skeleton, leave the details to subclasses
■ Quick Concept
An abstract class: declared with 'abstract' keyword. Cannot be instantiated directly.
May contain abstract methods (no body) and concrete methods (with body).
A subclass MUST override ALL abstract methods — or itself be declared abstract.
Abstract classes CAN have constructors, fields, and static methods.
Use abstract class when: sharing code (concrete methods) + enforcing a contract (abstract methods).
Vs Interface: abstract class allows state (fields) + partial implementation; supports single inheritance.
Q1.
Define an abstract class 'Shape' with an abstract area() and a concrete describe() method.
■ Solution
abstract class Shape {
String color;
Shape(String color) { [Link] = color; }
abstract double area(); // subclasses MUST implement
abstract double perimeter(); // subclasses MUST implement
void describe() { // concrete — shared by all
[Link]("%s | color=%s | area=%.2f | perimeter=%.2f%n",
getClass().getSimpleName(), color, area(), perimeter());
}
}
class Circle extends Shape {
double r;
Circle(String c, double r) { super(c); this.r = r; }
@Override double area() { return [Link] * r * r; }
@Override double perimeter() { return 2 * [Link] * r; }
}
class Rectangle extends Shape {
double l, w;
Rectangle(String c, double l, double w) { super(c); this.l=l; this.w=w; }
@Override double area() { return l * w; }
@Override double perimeter() { return 2 * (l + w); }
}
public class AbstractDemo {
public static void main(String[] args) {
Shape[] shapes = { new Circle("Red", 5), new Rectangle("Blue", 4, 6) };
for (Shape s : shapes) [Link]();
}
}
describe() is implemented once in Shape and reuses the polymorphic area() and perimeter() calls — the
Template Method pattern in action. Subclasses fill in the specifics; the parent orchestrates the sequence.
Q2.
Show that you cannot instantiate an abstract class directly.
■ Solution
abstract class Vehicle {
abstract void start();
void stop() { [Link]("Vehicle stopped"); }
}
class Car extends Vehicle {
@Override
public void start() { [Link]("Car engine started"); }
}
public class InstantiationDemo {
public static void main(String[] args) {
// Vehicle v = new Vehicle(); // COMPILE ERROR: abstract class
Vehicle v = new Car(); // OK: reference type = abstract, object = concrete
[Link]();
[Link]();
}
}
Abstract classes exist as reference types but not as concrete objects. Attempting 'new Vehicle()' is a
compile-time error. You can (and should) use the abstract type as the reference for polymorphism.
Q3.
Abstract class with a constructor — show how subclasses invoke it.
■ Solution
abstract class Animal {
String name;
int age;
Animal(String name, int age) { // abstract class CAN have constructors
[Link] = name;
[Link] = age;
[Link]("Animal initialised: " + name);
}
abstract String sound();
void info() {
[Link](name + " (age " + age + ") says: " + sound());
}
}
class Lion extends Animal {
Lion(String name, int age) {
super(name, age); // must call abstract class constructor
}
@Override String sound() { return "ROAR"; }
}
public class AbstractConstructor {
public static void main(String[] args) {
new Lion("Simba", 3).info();
}
}
Abstract class constructors initialise the abstract class's own fields. They cannot be called with 'new
AbstractClass()' directly, but ARE called automatically when a subclass constructor uses super().
Q4.
Use the Template Method Pattern — abstract class defines the algorithm skeleton.
■ Solution
abstract class DataProcessor {
// Template method — final so subclasses can't break the order
final void process() {
readData();
processData();
writeData();
}
abstract void readData();
abstract void processData();
void writeData() { // concrete default — can be overridden
[Link]("Writing to output");
}
}
class CSVProcessor extends DataProcessor {
@Override void readData() { [Link]("Reading CSV file"); }
@Override void processData() { [Link]("Parsing CSV rows"); }
}
class JSONProcessor extends DataProcessor {
@Override void readData() { [Link]("Reading JSON file"); }
@Override void processData() { [Link]("Parsing JSON nodes"); }
@Override void writeData() { [Link]("Writing JSON output"); }
}
public class TemplateMethod {
public static void main(String[] args) {
DataProcessor csv = new CSVProcessor();
DataProcessor json = new JSONProcessor();
[Link]();
[Link]("---");
[Link]();
}
}
Template Method is one of the most important GoF patterns. The algorithm's STRUCTURE
(read→process→write) is fixed in the parent. The DETAILS are deferred to subclasses. 'final' on process()
locks the sequence.
Q5.
Demonstrate an abstract class with a static factory method.
■ Solution
abstract class Notification {
String message;
Notification(String message) { [Link] = message; }
abstract void send();
// Static factory inside abstract class
static Notification of(String type, String message) {
if ([Link]("email")) return new EmailNotification(message);
if ([Link]("sms")) return new SMSNotification(message);
throw new IllegalArgumentException("Unknown type: " + type);
}
}
class EmailNotification extends Notification {
EmailNotification(String msg) { super(msg); }
@Override public void send() { [Link]("[EMAIL] " + message); }
}
class SMSNotification extends Notification {
SMSNotification(String msg) { super(msg); }
@Override public void send() { [Link]("[SMS] " + message); }
}
public class AbstractFactory {
public static void main(String[] args) {
[Link]("email", "Meeting at 3PM").send();
[Link]("sms", "Your OTP is 4829").send();
}
}
Static methods in abstract classes are allowed and useful for factory patterns. The caller uses [Link]()
— they get the right subtype without knowing the concrete class names.
Q6.
A subclass that is also abstract — extends an abstract class without implementing all
methods.
■ Solution
abstract class Vehicle {
abstract void start();
abstract void fuel();
void stop() { [Link]("Stopped"); }
}
abstract class ElectricVehicle extends Vehicle {
@Override
public void fuel() { [Link]("Charging battery"); }
// start() still abstract — subclass must implement
}
class Tesla extends ElectricVehicle {
@Override
public void start() { [Link]("Tesla: silent electric start"); }
}
public class PartialAbstract {
public static void main(String[] args) {
Tesla t = new Tesla();
[Link]();
[Link]();
[Link]();
}
}
An abstract class can extend another abstract class and implement SOME of its abstract methods.
Remaining abstract methods must be completed by the first concrete subclass in the chain.
Q7.
Abstract class vs Interface decision: use abstract class when sharing state.
■ Solution
// Abstract class version — can share state (fields)
abstract class Logger {
private String prefix; // state shared by all loggers
Logger(String prefix) { [Link] = prefix; }
void log(String msg) { // concrete: uses shared field
[Link]("[" + prefix + "] " + format(msg));
}
abstract String format(String msg); // each logger formats differently
}
class TimestampLogger extends Logger {
TimestampLogger() { super("LOG"); }
@Override String format(String msg) { return [Link]() + ": " + msg; }
}
class UpperLogger extends Logger {
UpperLogger() { super("UPPER"); }
@Override String format(String msg) { return [Link](); }
}
public class LoggerDemo {
public static void main(String[] args) {
new TimestampLogger().log("Application started");
new UpperLogger().log("warning issued");
}
}
The abstract class holds a 'prefix' field — something interfaces cannot do (they only hold public static final
constants). Both loggers share the log() logic and customise only format(). This is a clean abstract-class
use-case.
Q8.
Demonstrate abstract class with concrete fields and partial implementation in a banking
system.
■ Solution
abstract class Account {
protected String owner;
protected double balance;
Account(String owner, double initialBalance) {
[Link] = owner;
[Link] = initialBalance;
}
void deposit(double amount) {
if (amount > 0) { balance += amount; [Link]("Deposited $" + amount); }
}
abstract void withdraw(double amount); // rules differ per account type
abstract double interestRate();
void printStatement() {
[Link]("%s [%s] Balance: $%.2f | Rate: %.1f%%%n",
getClass().getSimpleName(), owner, balance, interestRate()*100);
}
}
class SavingsAccount extends Account {
SavingsAccount(String o, double b) { super(o, b); }
@Override public void withdraw(double a) {
if (a > balance*0.5) { [Link]("Cannot withdraw >50% of savings");
return; }
balance -= a;
}
@Override public double interestRate() { return 0.04; }
}
class CurrentAccount extends Account {
CurrentAccount(String o, double b) { super(o, b); }
@Override public void withdraw(double a) { balance -= a; } // allows overdraft
@Override public double interestRate() { return 0.01; }
}
public class BankDemo {
public static void main(String[] args) {
Account sa = new SavingsAccount("Alice", 10000);
Account ca = new CurrentAccount("Bob", 5000);
[Link](2000); [Link](7000); [Link]();
[Link](1000); [Link](8000); [Link]();
}
}
deposit() is shared logic — defined once in Account. withdraw() and interestRate() are abstract because each
account type has different rules. This design is extensible: adding a FixedDepositAccount requires no
changes to existing code.
Chapter 8
Interfaces
Pure contracts — defining what, not how (with default & static methods since Java 8)
■ Quick Concept
Interface: 100% abstract blueprint (before Java 8) — all methods implicitly public abstract.
Fields: implicitly public static final (constants). Cannot hold instance state.
Java 8+: default methods (with body, can be overridden) and static methods.
Java 9+: private methods inside interfaces (helper for default methods).
A class implements multiple interfaces. Interfaces can extend multiple interfaces.
Marker interface: empty interface used to 'tag' a class (Serializable, Cloneable).
Functional interface: exactly ONE abstract method — used with lambdas (@FunctionalInterface).
Q1.
Basic interface — Drawable — implemented by Circle and Square.
■ Solution
interface Drawable {
void draw(); // implicitly public abstract
void resize(int factor);
}
class Circle implements Drawable {
@Override public void draw() { [Link]("Drawing Circle"); }
@Override public void resize(int f) { [Link]("Circle resized by " + f); }
}
class Square implements Drawable {
@Override public void draw() { [Link]("Drawing Square"); }
@Override public void resize(int f) { [Link]("Square resized by " + f); }
}
public class InterfaceDemo {
static void render(Drawable d) { [Link](); [Link](2); }
public static void main(String[] args) {
render(new Circle());
render(new Square());
}
}
The render() method accepts ANY Drawable — current or future. New shapes need only implement the
interface; render() requires zero modification. This is the Open/Closed Principle via interfaces.
Q2.
Implement multiple interfaces in a single class.
■ Solution
interface Printable { void print(); }
interface Saveable { void save(); }
interface Shareable { void share(String recipient); }
class Document implements Printable, Saveable, Shareable {
private String content;
Document(String content) { [Link] = content; }
@Override public void print() { [Link]("Printing: " + content); }
@Override public void save() { [Link]("Saving: " + content); }
@Override public void share(String r) { [Link]("Sharing with " + r); }
}
public class MultiInterface {
public static void main(String[] args) {
Document doc = new Document("My Report");
[Link]();
[Link]();
[Link]("manager@[Link]");
// Polymorphism via interface reference
Printable p = doc;
[Link]();
}
}
A class can satisfy as many contracts (interfaces) as needed. Each interface reference type exposes only its
own methods. This is how Java achieves 'multiple type' relationships safely.
Q3.
Interface constants (public static final) and why they differ from class fields.
■ Solution
interface MathConstants {
double PI = 3.14159265358979; // implicitly public static final
double E = 2.71828182845904;
int MAX_N = 1000;
}
class Calculator implements MathConstants {
double circleArea(double r) { return PI * r * r; }
double expValue(double x) { return [Link](E, x); }
}
public class ConstantsDemo {
public static void main(String[] args) {
Calculator c = new Calculator();
[Link]("Area(r=5): %.4f%n", [Link](5));
[Link]("e^2: %.4f%n", [Link](2));
[Link]("Max N: " + MathConstants.MAX_N);
}
}
All interface fields are automatically public static final — they're constants, not instance variables. A class
implementing the interface inherits access to them, but they still belong to the interface.
Q4.
Default methods in an interface — provide a default implementation that subclasses may
override.
■ Solution
interface Greeting {
String greet(String name); // abstract
default void greetAll(String... names) { // default method (Java 8+)
for (String n : names)
[Link](greet(n));
}
static Greeting formal() { // static factory (Java 8+)
return name -> "Good day, " + name + ".";
}
}
class FriendlyGreeting implements Greeting {
@Override public String greet(String name) { return "Hey " + name + "! :)"; }
// inherits greetAll() default
}
public class DefaultMethodDemo {
public static void main(String[] args) {
Greeting fg = new FriendlyGreeting();
[Link]("Alice", "Bob", "Carol");
Greeting formal = [Link](); // lambda via static factory
[Link]([Link]("Dr. Smith"));
}
}
Default methods let interfaces evolve without breaking existing implementations. All existing classes that
implement the interface automatically get the new default behaviour. greetAll() is shared freely; each class
only customises greet().
Q5.
Functional interface and lambda expressions — @FunctionalInterface.
■ Solution
@FunctionalInterface
interface MathOperation {
int operate(int a, int b); // exactly ONE abstract method
}
public class LambdaDemo {
static int apply(int a, int b, MathOperation op) {
return [Link](a, b);
}
public static void main(String[] args) {
MathOperation add = (a, b) -> a + b;
MathOperation subtract = (a, b) -> a - b;
MathOperation multiply = (a, b) -> a * b;
MathOperation power = (a, b) -> (int) [Link](a, b);
[Link]("10 + 5 = " + apply(10, 5, add));
[Link]("10 - 5 = " + apply(10, 5, subtract));
[Link]("10 * 5 = " + apply(10, 5, multiply));
[Link]("2 ^ 8 = " + apply(2, 8, power));
}
}
@FunctionalInterface enforces the one-abstract-method rule at compile time. Lambda expressions (a, b) -> a
+ b provide inline implementations without creating named classes. This is the foundation of Java's Stream
API and functional programming.
Q6.
Interface extending multiple interfaces.
■ Solution
interface Flyable { void fly(); }
interface Swimmable { void swim(); }
interface Diveable { void dive(int depth); }
// Interface extends multiple interfaces
interface SeaBird extends Flyable, Swimmable, Diveable {
default void hunt() {
fly();
dive(10);
swim();
[Link]("Caught a fish!");
}
}
class Pelican implements SeaBird {
@Override public void fly() { [Link]("Pelican flying"); }
@Override public void swim() { [Link]("Pelican swimming"); }
@Override public void dive(int d) { [Link]("Pelican diving to " + d + "m");
}
}
public class InterfaceExtends {
public static void main(String[] args) {
Pelican p = new Pelican();
[Link]();
}
}
Interfaces can extend multiple interfaces, combining their contracts. Pelican must implement ALL three
interfaces' methods. The default hunt() in SeaBird orchestrates them into a behaviour.
Q7.
Resolve default method conflict when two interfaces provide the same default.
■ Solution
interface A {
default void hello() { [Link]("Hello from A"); }
}
interface B {
default void hello() { [Link]("Hello from B"); }
}
class C implements A, B {
@Override
public void hello() { // MUST override to resolve conflict
[Link](); // explicitly choose A's version
[Link]("Hello from C (resolved conflict)");
}
}
public class DiamondDefault {
public static void main(String[] args) {
new C().hello();
}
}
When two interfaces provide default methods with the same signature, the implementing class gets a compile
error until it overrides the method. [Link]() selects a specific interface's default. This is
Java's resolution of the Diamond Problem for default methods.
Q8.
Marker interface — implement Serializable-like tagging.
■ Solution
import [Link].*;
// Built-in marker interface — no methods
// [Link] is exactly: public interface Serializable {}
class Config implements Serializable {
private static final long serialVersionUID = 1L;
String host;
int port;
Config(String host, int port) { [Link]=host; [Link]=port; }
}
public class MarkerDemo {
public static void main(String[] args) throws Exception {
Config cfg = new Config("localhost", 8080);
// Serialize
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("[Link]"));
[Link](cfg); [Link]();
// Deserialize
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("[Link]"));
Config loaded = (Config) [Link](); [Link]();
[Link]([Link] + ":" + [Link]);
}
}
Serializable has no methods — it's a marker that tells the JVM 'this class may be serialized'. The JVM checks
'instanceof Serializable' at runtime. You can create custom markers for your own frameworks similarly.
Q9.
Use Comparable and Comparator interfaces to sort a list of objects.
■ Solution
import [Link].*;
class Student implements Comparable<Student> {
String name;
double gpa;
Student(String n, double g) { name=n; gpa=g; }
@Override
public int compareTo(Student other) {
return [Link]([Link], [Link]); // descending GPA
}
public String toString() { return name + "(" + gpa + ")"; }
}
public class SortDemo {
public static void main(String[] args) {
List<Student> students = new ArrayList<>([Link](
new Student("Alice", 3.8),
new Student("Bob", 3.5),
new Student("Carol", 3.9)
));
[Link](students); // uses compareTo
[Link]("By GPA desc: " + students);
// Comparator for alphabetical order
[Link]([Link](s -> [Link]));
[Link]("By name asc: " + students);
}
}
Comparable defines ONE natural ordering (compareTo). Comparator provides flexible, ad-hoc orderings
(compare). Using lambda (s -> [Link]) with [Link]() is the modern approach for sorting by a
field.
Q10.
Build a plugin system using interfaces — dynamically select implementations at runtime.
■ Solution
interface PaymentGateway {
boolean processPayment(double amount);
String gatewayName();
}
class StripeGateway implements PaymentGateway {
@Override public boolean processPayment(double amt) {
[Link]("Stripe: charging $%.2f%n", amt);
return true;
}
@Override public String gatewayName() { return "Stripe"; }
}
class PayPalGateway implements PaymentGateway {
@Override public boolean processPayment(double amt) {
[Link]("PayPal: transferring $%.2f%n", amt);
return true;
}
@Override public String gatewayName() { return "PayPal"; }
}
class CheckoutService {
private PaymentGateway gateway;
CheckoutService(PaymentGateway gateway) { [Link] = gateway; }
void checkout(double total) {
[Link]("Using: " + [Link]());
boolean ok = [Link](total);
[Link](ok ? "Payment successful" : "Payment failed");
}
}
public class PluginSystem {
public static void main(String[] args) {
CheckoutService cs1 = new CheckoutService(new StripeGateway());
CheckoutService cs2 = new CheckoutService(new PayPalGateway());
[Link](99.99);
[Link](49.50);
}
}
CheckoutService depends on the PaymentGateway INTERFACE, not any concrete class — Dependency
Inversion Principle. Switching payment providers requires no changes to CheckoutService. New gateways
(Apple Pay, Crypto) just implement the interface.
Chapter 0
Classes & Objects Class = blueprint; Object = instance via 'new'; 'this' = current object
Method Overloading Same name, diff params; compile-time; NOT by return type alone
Method Overriding @Override, same signature; runtime dispatch; widen access only
Constructors No return type; default lost if you write one; this()/super() first line
Practice Tip: After each chapter, close the solution and try to write the code from memory. Then compare —
focus on the parts you forgot. Repeat until you can write every pattern fluently.