Exam Kit Deep Answers — Tier 1 & 2

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.md instead.

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.

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":

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.

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 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:

  1. Integer division truncates. 10 / 3 is 3.
  2. + is overloaded for String. Once a String appears, everything after is joined. 10 + 20 + "J" + 10 + 20 → 30J1020.
  3. && 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.

  1. "Java forbids multiple class inheritance because of the diamond problem — two parent classes could both define the same method, and Java couldn't choose."
  2. "A static method is chosen by the reference type, not the object, so it cannot be overridden — only hidden."
  3. "Overloading is decided by the compiler from the argument types; overriding is decided by the JVM from the actual object."
  4. "Checked exceptions come from outside the program and are recoverable, so the compiler forces you to handle them. Unchecked ones are bugs."
  5. "finally runs even after a return. Only System.exit() can stop it."