Deep Answers — Tier 1 & 2
The other files tell you what to write. This one explains why it works that way — so that when the exam question is worded differently from what you practised, you can still answer it.
Read this when you have time. If the exam is tomorrow, read
07-WRITE-THIS.mdinstead.Java version: all code targets Java 25 LTS (2025) and also compiles on Java 21 and Java 17.
Part 1 — The six topics that carry the most marks
1 · Overloading vs Overriding
The short answer
Overloading = same name, different inputs, same class. Overriding = same name, same inputs, child class.
Why it works this way
Java has two completely different mechanisms, and the paper is really asking whether you know which is which.
- Overloading is decided by the compiler, before the program runs. The compiler looks at the arguments you typed and picks the method whose parameter list matches. It's a matching game with fixed rules.
- Overriding is decided by the JVM, while the program runs. The JVM looks at the actual object the variable is pointing at.
That's why one is called compile-time and the other run-time. They are not two versions of the same idea — they are two different ideas that happen to share the word "polymorphism".
The full picture
| Point | Overloading | Overriding |
|---|---|---|
| Classes involved | One | Two (parent + child) |
| Parameters | Must differ | Must be identical |
| Return type | Anything | Same or a subtype |
| Chosen by | Compiler, using the arguments | JVM, using the object |
| When decided | Before running | While running |
| Also called | Compile-time polymorphism | Run-time polymorphism |
| Inheritance needed | No | Yes |
static methods |
Can be overloaded | Cannot be overridden (hidden) |
final methods |
Can be overloaded | Cannot be overridden |
private methods |
Can be overloaded | Cannot be overridden |
The part people get wrong
class A {
void show() { System.out.println("A"); }
}
class B extends A {
void show() { System.out.println("B"); }
}
public class Main {
public static void main(String[] args) {
A ref = new B();
ref.show();
}
}
B
Why B? Because ref is just a label. The object is a B. Java asks the
object, not the label.
Now compare:
class A {
static void show() { System.out.println("A"); }
}
class B extends A {
static void show() { System.out.println("B"); }
}
public class Main {
public static void main(String[] args) {
A ref = new B();
ref.show();
}
}
A
Why A now? Because static methods belong to the class, not the object.
Java asks the label's class — and the label is A. This is hiding, not
overriding, and it is the single best proof that static breaks polymorphism.
If they ask "why can't static methods be overridden?" — because there is no object to dispatch to. The method doesn't belong to an object at all.
2 · Interfaces
The short answer
A contract that says what a class must do, not how.
Why they exist — the real reason
Java forbids class C extends A, B. Why? Because of the diamond problem:
Shape
/ \
Circle Square
\ /
??? (which draw()?)
If both Circle and Square define draw() differently and some class inherited
from both, Java would have two different method bodies with the same name. There
is no correct way to choose. So Java bans it outright.
Interfaces dodge the problem entirely: an interface has no method body. There is nothing to be ambiguous about, so a class may implement as many as it likes.
This is the answer to "why does Java not support multiple inheritance?" Almost everyone writes "because Java is simple" — that earns nothing. Write "diamond problem" and explain it.
The rules, and why each exists
| Rule | Why |
|---|---|
No new |
It's not a class — there's no complete implementation to build |
Methods are public abstract |
There's no body, so there's nothing to be private about |
Variables are public static final |
An interface can't hold state; only shared constants |
| No constructors | Nothing to construct |
| Can implement many | That's the entire point |
Common mistake
interface Shape {
void draw(); // implicitly public
}
class Circle implements Shape {
void draw() { } // ❌ COMPILE ERROR - cannot reduce visibility
}
class Square implements Shape {
public void draw() { } // ✅ must be public
}
You must write public when implementing. The interface promised a public method;
the class can't deliver a private one.
3 · Constructors
The short answer
Setup code that runs once, when the object is made. Same name as the class, no return type.
What actually happens when you write new
Step by step, in order:
Student s = new Student(101, "Aarav");
1. Class loading → the JVM loads Student.class (once, ever)
2. Memory allocated → space on the HEAP
every field gets a DEFAULT: 0, 0.0, false, null
3. super() runs → the parent's constructor finishes FIRST
4. Constructor body → this.id = 101; this.name = "Aarav";
5. Reference returned → the address is stored in s
Step 2 is why a constructor with an empty body still "works" — the fields were already zeroed by the JVM.
Why super() must be first
The child is built on top of the parent. You can't add a roof before the
foundation exists. Java enforces this by requiring super() (or this()) to be the
very first statement.
If you don't write it, Java inserts super() silently — which is why your code
compiles when the parent has a no-argument constructor.
But:
class Person {
Person(String name) { } // ONLY a 1-argument constructor
}
class Employee extends Person {
Employee() { } // ❌ COMPILE ERROR
// Java tried to insert super() with no arguments,
// but Person has no such constructor
}
this() vs super()
this() |
super() |
|
|---|---|---|
| Calls | Another constructor in this class | The parent's constructor |
| Position | Must be the first statement | Must be the first statement |
| Together? | Never — you can use one or the other, not both |
4 · Inheritance and the five types
The short answer
A child gets the parent's fields and methods, then adds its own.
Why "IS-A" matters
Inheritance models "is a kind of":
- A
Bikeis aVehicle✅ →extendsis correct - A
Carhas anEngine❌ → that's composition, not inheritance
Getting this wrong is the most common design mistake. If you write
class Car extends Engine, you're saying a car is an engine, which is false.
The five types, and why two need interfaces
| Type | Shape | Allowed with classes? |
|---|---|---|
| Single | A → B | ✅ |
| Multilevel | A → B → C | ✅ |
| Hierarchical | A → B, A → C | ✅ |
| Multiple | A + B → C | ❌ diamond problem |
| Hybrid | mixture | ❌ contains the diamond |
Only multiple and hybrid are blocked, and for the same reason.
What a subclass does NOT inherit
| Not inherited | Why |
|---|---|
| Constructors | A constructor is tied to a specific class name |
| Private members | Not visible outside the class that declared them |
static members (as overridable) |
They belong to the class, not the object |
People often say "the child inherits everything". It doesn't. Constructors and private members are the exceptions, and that's a good exam point.
The final keyword, all three uses
| Applied to | Effect |
|---|---|
| Variable | Cannot be reassigned — a constant |
| Method | Cannot be overridden |
| Class | Cannot be extended |
final class A { } // no class can extend A
class B {
final void show() { } // no subclass can override show()
}
final int MAX = 100; // MAX can never change
final is a non-access modifier — you can combine it with public, private etc.
5 · try-catch-finally
The short answer
try holds risky code, catch handles failure, finally always runs.
Why finally beats catch
catch only runs if that specific error happens. finally runs no matter what —
success, failure, or early return. That makes it the only place you can guarantee
cleanup.
The four cases, precisely
| Case | catch runs? |
finally runs? |
|---|---|---|
| No exception | ❌ | ✅ |
| Exception caught | ✅ | ✅ |
| Exception not caught | ❌ (program exits after) | ✅ |
try has a return |
❌ | ✅ before the method returns |
System.exit() called |
❌ | ❌ |
That last one is the only way to skip finally.
Why the catch order matters
try {
// ...
} catch (Exception e) { // ❌ COMPILE ERROR
// ...
} catch (ArithmeticException e) { // unreachable
// ...
}
ArithmeticException is an Exception. If you catch the parent first, the child's
handler can never be reached — and Java refuses to compile unreachable code.
Rule: most specific first, most general last.
The modern improvement: try-with-resources
// old way - verbose
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("f.txt"));
System.out.println(br.readLine());
} catch (IOException e) {
System.out.println("error");
} finally {
try { if (br != null) br.close(); } catch (IOException ignored) { }
}
// new way - the file closes itself
try (BufferedReader br = new BufferedReader(new FileReader("f.txt"))) {
System.out.println(br.readLine());
} catch (IOException e) {
System.out.println("error");
}
Anything inside try (...) must implement AutoCloseable, and it gets closed
automatically. Worth one sentence in an answer.
6 · Checked vs Unchecked exceptions
The short answer
Checked = the compiler forces you to handle it. Unchecked = it only appears at run time.
Why the compiler cares about one and not the other
This is the part nobody explains.
- A checked exception comes from outside your program — a missing file, a database that's down, a network that dropped. It is not your bug, and it is recoverable if you plan for it. So Java forces you to plan.
- An unchecked exception is your own mistake — dividing by zero, using a null,
going past the end of an array. If the compiler forced you to catch those, your code
would be buried in
tryblocks for bugs you should simply not write.
So the rule is really: external and recoverable → force handling. Internal and a bug → let it crash so you fix it.
The hierarchy, with the cut line
Throwable
├── Exception
│ ├── RuntimeException ← UNCHECKED, everything below it
│ │ ├── ArithmeticException
│ │ ├── NullPointerException
│ │ ├── ArrayIndexOutOfBoundsException
│ │ ├── NumberFormatException
│ │ └── ClassCastException
│ └── IOException ← CHECKED
│ ├── FileNotFoundException
│ ├── SQLException
│ └── ClassNotFoundException
└── Error ← do not catch
├── OutOfMemoryError
└── StackOverflowError
The cut line is RuntimeException. Anything under it is unchecked. Anything else
under Exception is checked.
Why catching Error is wrong
An OutOfMemoryError means the JVM has nowhere left to put objects. Your catch block
needs memory to run. There is none. Catching it doesn't help and hides a real problem.
Part 2 — The nine supporting topics
7 · The four pillars
The short answer: encapsulation, inheritance, polymorphism, abstraction.
Why they're grouped: each one solves a different problem in building large programs.
| Pillar | The problem it solves |
|---|---|
| Encapsulation | "Anyone can change my data and break it" |
| Inheritance | "I'm writing the same code again and again" |
| Polymorphism | "I need one loop to handle five different shapes" |
| Abstraction | "My caller shouldn't need to know how this works" |
Encapsulation vs abstraction — the pair people mix up:
- Encapsulation is about hiding data (fields private, methods public)
- Abstraction is about hiding complexity (an interface with no body)
Encapsulation is how you protect state. Abstraction is how you simplify design.
8 · JDK, JRE, JVM and bytecode
The short answer: JDK ⊃ JRE ⊃ JVM.
Why bytecode exists: because a .exe only runs on one kind of CPU. If Java
compiled straight to machine code, you'd need a separate download per operating
system — like every C program.
Bytecode solves that by targeting a made-up machine (the JVM) that every OS
implements. Same .class file, everywhere.
The JVM, in four parts
┌───────────────────────────────────────────┐
│ 1. CLASS LOADER │
│ finds and loads .class files │
├───────────────────────────────────────────┤
│ 2. MEMORY AREAS │
│ Method Area │ Heap │ Stack │ PC Reg │
├───────────────────────────────────────────┤
│ 3. EXECUTION ENGINE │
│ Interpreter → JIT Compiler → GC │
├───────────────────────────────────────────┤
│ 4. JNI + Native Method Libraries │
└───────────────────────────────────────────┘
▼
Native OS / hardware
Draw four boxes and label them. That's the "neat diagram" the question asks for.
Heap vs Stack — worth knowing, and nobody explains it:
| Heap | Stack |
|---|---|
| Where objects live | Where method calls live |
| Shared by all threads | One per thread |
| Cleaned by the garbage collector | Freed automatically when the method ends |
| Slower | Very fast |
Student s = new Student(); — the object is on the heap; the reference s is
on the stack.
9 · Primitive types and casting
The short answer: 8 basic types; small→big is automatic, big→small needs a cast.
Why the defaults are what they are: an int field you never assign is 0 because
the JVM zeroes heap memory before your constructor runs. A local variable has no
default — the compiler makes you assign it, because it can't prove you did.
The widening ladder:
byte → short → int → long → float → double
char ↗
Why narrowing can lose data: you're pouring a large jug into a small glass. Java
makes you say (int) so it's clear you accepted the spill.
double x = 9.99;
int y = (int) x; // 9, not 10 — it TRUNCATES
int z = (int) Math.round(x); // 10 — this rounds
Autoboxing, and the trap
Integer a = 100, b = 100;
System.out.println(a == b); // true — cached (-128 to 127)
Integer c = 1000, d = 1000;
System.out.println(c == d); // false — not cached, different objects
Java caches small Integer objects for speed. Beyond 127, every new Integer is a
fresh object, so == compares addresses and fails. Always use .equals().
10 · Operators
The short answer: symbols that do things to values.
Three facts that decide most questions:
- Integer division truncates.
10 / 3is3. +is overloaded for String. Once a String appears, everything after is joined.10 + 20 + "J" + 10 + 20→30J1020.&&and||short-circuit. They stop evaluating the moment the answer is known. This protects you from errors:
int b = 0;
if (b != 0 && 10 / b > 1) { } // safe — the division never runs
Prefix vs postfix:
int a = 5;
System.out.println(a++); // prints 5, then a becomes 6
System.out.println(++a); // a becomes 7, then prints 7
Read it as: postfix = use then change, prefix = change then use.
11 · Arrays
The short answer: fixed-size row of same-type values, indexed from 0.
Why arrays are fast: elements sit next to each other in memory. To reach index 5,
Java calculates start + 5 × size and jumps straight there — no searching. That's
O(1) access.
Why 2D arrays are "arrays of arrays": Java has no true 2D array. A 2D array is an
array whose elements are references to other arrays. That's why rows can differ in
length — and why m.length gives rows while m[0].length gives columns.
m ──► [ row0 ref | row1 ref | row2 ref ] m.length = 3
│
▼
[ 7 | 3 | -5 | 10 ] m[0].length = 4
Passing an array to a method: Java is always pass-by-value, but for an array the value is the reference. So the method can change the contents, not the variable.
12 · Classes, objects and memory
The short answer: class = plan, object = instance on the heap.
Why = doesn't copy an object:
Student s1 = new Student();
Student s2 = s1; // NOT a copy
s2.name = "Aarav";
System.out.println(s1.name); // Aarav — same object!
s1 ─┐
├──► [ the one Student object ]
s2 ─┘
Both labels point at one object. To actually copy you must either write a copy
constructor or use clone().
Shallow vs deep copy (Unit 6 topic, but the concept belongs here):
| Shallow | Deep |
|---|---|
| Copies the outer object | Copies everything, inside and out |
| Inner objects are shared | Inner objects are duplicated |
super.clone() |
super.clone() + clone each field |
Change an inner object in a shallow copy and it changes in the original too. In a deep copy it doesn't.
13 · static and final
The short answer: static = shared by the class. final = locked.
Why static exists: some values belong to the idea of a class rather than to any
one object. A Counter class tracking how many objects exist must keep one number,
not one per object.
class Counter {
static int total = 0; // ONE copy, shared
int own = 0; // one per object
Counter() { total++; own++; }
}
static and polymorphism don't mix — a static method is chosen by the
reference type, not the object. That's the point from section 1.
Don't confuse these three
| Keyword | Kind | Meaning |
|---|---|---|
final |
modifier | cannot change / override / extend |
finally |
block | always runs after try-catch |
finalize() |
method | called by the GC (deprecated since Java 9) |
They look alike and mean completely different things. Examiners love it.
14 · this and super
The short answer: this = me. super = my parent.
Why this is needed at all:
class Person {
String name;
Person(String name) {
name = name; // ❌ assigns the parameter to itself
this.name = name; // ✅ "this object's name = the parameter"
}
}
Without this, the parameter and the field have the same name and the parameter wins.
this.name says "the one that belongs to the object".
Why super is needed: when a child overrides a method but still wants the parent's
original behaviour, it calls super.method().
Three impossible combinations:
1. this() and super() in the same constructor — no
2. this() or super() anywhere except the first line — no
3. Using this in a static method — no (there's no object)
15 · String, StringBuffer, StringBuilder
The short answer: String is unchangeable; the other two are changeable.
Why String is immutable — and why that's a feature, not a bug:
String s = "Java";
s.concat(" Rocks");
System.out.println(s); // still "Java" — the result was thrown away
concat created a new String and returned it. s still points at the old one.
This surprises beginners but is deliberate:
| Benefit | Why |
|---|---|
| Safe to share | A string can never be changed behind your back |
| Safe across threads | Nothing to synchronise |
| Cacheable | Literals go in a string pool — "Java" is created once, not a thousand times |
| Safe as a key | HashMap keys can't silently change |
Why StringBuilder is faster in a loop:
String s = "";
for (int i = 0; i < 1000; i++) s += i; // creates 1000 objects!
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.append(i); // one object, resized
Every += on a String builds a brand-new String and abandons the old one.
Rule: String for fixed text · StringBuilder inside a loop · StringBuffer if many threads share it.
The five sentences worth memorising
If you remember nothing else from this page, remember these — each one is a whole exam answer in a single line.
- "Java forbids multiple class inheritance because of the diamond problem — two parent classes could both define the same method, and Java couldn't choose."
- "A static method is chosen by the reference type, not the object, so it cannot be overridden — only hidden."
- "Overloading is decided by the compiler from the argument types; overriding is decided by the JVM from the actual object."
- "Checked exceptions come from outside the program and are recoverable, so the compiler forces you to handle them. Unchecked ones are bugs."
- "
finallyruns even after areturn. OnlySystem.exit()can stop it."