A bill that knows its shop
Riya is writing billing software for a chain of chai stalls. Every Bill needs the stall's name and GST number on top. She has been passing the Shop object into every bill by hand, and twice she passed the wrong shop.
Sir, every bill needs its shop's name. I keep passing the shop in, and sometimes it's the wrong shop.
Then make Bill an inner class of Shop. You create a bill from a shop: shop.new Bill(120). Each bill then remembers its shop automatically and can read the shop's fields, even private ones.
And the GST calculation? It doesn't need any particular shop.
That's a static nested class: grouped inside Shop for neatness, but with no link to a shop object. And for the festival discount used in just one place, you don't even need a name: an anonymous class, written right where you use it.
What are nested classes?
A nested class is a class declared inside another class or interface. A static nested class is declared with static and is not tied to an instance of the outer class. A non-static nested class is an inner class; there are three kinds: a member inner class (declared at class level), a local class (declared inside a block such as a method) and an anonymous class (declared and instantiated in a single expression, without a name). Every inner class instance holds an implicit reference to an instance of its enclosing class, and inner classes can access the enclosing class's members, including private ones. Local and anonymous classes may use local variables of the enclosing block only if they are final or effectively final.
A class written inside another class. If it's static, it's just stored there. If it isn't, each of its objects belongs to an outer object and can use that object's data. Anonymous classes are quick, nameless classes for one-time use.
Words you'll meet in this lesson
final.The four kinds at a glance
| Kind | Declared | Create with | Outer object? | Class file |
|---|---|---|---|---|
| Member inner | in the class body | shop.new Bill() | Yes, required | Shop$Bill.class |
| Static nested | in the class body, static | new Shop.Tax() | No | Shop$Tax.class |
| Local | inside a method/block | new Receipt() in that block | Yes, if the method isn't static | Shop$1Receipt.class |
| Anonymous | in an expression | new Discount() { … } | Yes, if in instance code | Shop$1.class |
Exam tip: a static nested class is not an inner class. It can't use the outer class's instance fields directly (non-static variable cannot be referenced from a static context). An inner class object can't exist without an outer object, so new Inner() inside a static method fails; write outer.new Inner().
The four kinds, with code
Member inner
Belongs to an outer object; reads its fields. A bill of a shop, a seat of a train.
has this$0Static nested
Just grouped inside the outer class. Helpers, builders, map entries.
no outer objectLocal
Declared inside a method, visible only there.
rareAnonymous
No name; implements an interface or extends a class in one expression.
one-off behaviour1. Member inner class
class Shop {
private final String name;
Shop(String name) { this.name = name; }
class Bill { // inner: no 'static'
int amount;
Bill(int amount) { this.amount = amount; }
String print() {
return name + ": ₹" + amount; // reads the outer object's private field
}
}
}
public class Main {
public static void main(String[] args) {
Shop kharghar = new Shop("Chai Point, Kharghar");
Shop vashi = new Shop("Chai Point, Vashi");
Shop.Bill b1 = kharghar.new Bill(120); // a bill OF the Kharghar shop
Shop.Bill b2 = vashi.new Bill(45);
System.out.println(b1.print());
System.out.println(b2.print());
}
}Behind the scenes javac gives Bill a hidden field final Shop this$0 and passes the outer object into its constructor. Here is javap -p on a similar class (JDK 21):
class Bank$Account {
int bal;
final Bank this$0; ← the link to the outer object
Bank$Account(Bank); ← the constructor receives it
int interest();
java.lang.String bank();
}2. Static nested class
class Shop {
static class Tax { // static: no Shop object needed
static int gst(int amount) {
return amount * 5 / 100;
}
}
static class Address {
final String city, pin;
Address(String city, String pin) { this.city = city; this.pin = pin; }
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Shop.Tax.gst(120));
Shop.Address a = new Shop.Address("Navi Mumbai", "410210"); // no shop object
System.out.println(a.city + " " + a.pin);
}
}3. Local class
public class Main {
static void printReceipt(String customer, int amount) {
class Line { // visible only inside this method
String text(String label, int value) {
return label + " for " + customer + ": ₹" + value; // uses a captured local
}
}
Line l = new Line();
System.out.println(l.text("Amount", amount));
System.out.println(l.text("GST", amount * 5 / 100));
}
public static void main(String[] args) {
printReceipt("Riya", 200);
}
}4. Anonymous class
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
interface Discount {
int apply(int price);
}
public class Main {
public static void main(String[] args) {
Discount festive = new Discount() { // class with no name, created at once
public int apply(int price) {
return price - price / 10; // 10% off
}
};
System.out.println(festive.apply(500));
List<String> names = new ArrayList<>(List.of("Riya", "Kabir", "Om"));
names.sort(new Comparator<String>() { // a one-off sort rule
public int compare(String a, String b) {
return a.length() - b.length();
}
});
System.out.println(names);
}
}Since Java 8, an anonymous class implementing an interface with a single abstract method is usually written as a lambda: p -> p - p / 10 (Lesson 14.1). Anonymous classes are still needed when you extend a class, implement several methods, or need fields.
Captured variables must be effectively final
int off = 50;
off = 60; // now 'off' changes...
Discount d = new Discount() {
public int apply(int p) { return p - off; } // error
};error: local variables referenced from an inner class must be final or effectively finalThe anonymous object copies the value of off into a hidden field (val$off) when it's created. If the local could change later, the copy and the original would disagree, so Java forbids it.
Which one? Start with a static nested class: it's simpler and holds no hidden reference. Make it an inner class only if each object truly needs its outer object. Use anonymous classes (or lambdas) for short, one-place behaviour, and a normal top-level class when other files need it.
What happens on shop.new Bill(120)
javac compiles two classes
From oneShop.java, javac writesShop.classandShop$Bill.class. To the JVM, an inner class is an ordinary class with a$in its name; Java 11+ records them as “nestmates” so they can share private members.The constructor gets an extra argument
javac passes the outer object as a hidden first argument. IfShop.Bill b = kharghar.new Bill(120); // compiled roughly as: new Shop$Bill(kharghar, 120)khargharwere null, this line would throwNullPointerException.The link is stored in this$0
The constructor saves it in the synthetic fieldfinal Shop this$0. The Bill object on the heap now points to its Shop object.Reading an outer field goes through the link
Each use of an outer field compiles to a read throughreturn name + ": ₹" + amount; // really: this$0.name + ": ₹" + this.amountthis$0. If both classes have a field with the same name, writeShop.this.nameto mean the outer one.The link keeps the outer object alive
As long as any Bill is reachable, its Shop can't be garbage-collected (Lesson 8.3). A long-lived inner object (for example a listener stored in a static list) can therefore leak a large outer object. Static nested classes don't have this problem.
Inner vs static nested: both views
Left: the code. Right: the objects on the heap, including the hidden link an inner object keeps to its outer object.
- A
Shopobject is created as usual. k.new Bill(120): the bill is created from shopk.- Another bill from the same shop.
b1.print()shows the shop's name, thoughBillhas nonamefield of its own.Shop.Tax.gst(120)is called on the static nested class directly: no shop, no bill.- We set
kto null. Is the shop gone? See the right side.
kpoints to a new Shop object holding the name.- javac compiled
k.new Bill(120)asnew Shop$Bill(k, 120): the bill stores the shop in its hidden fieldthis$0. - The second bill also points back to the same shop through its own
this$0. - Inside
print(),namereally meansthis$0.name. That's how an inner object reads its outer object's fields. Taxis a static nested class:gstis an ordinary static method. No Tax object, no link to any shop.- Even with
knull, the Shop object is still reachable through the bills'this$0, so the garbage collector must keep it. Inner objects keep their outer object alive.
Nested classes in real systems
You use nested classes every day in the JDK and in frameworks, often without noticing the $ in their names.
Watch it with an example: sorting a cricket leaderboard
A turf-cricket app sorts batters by runs using an anonymous Comparator. Watch the list ask the anonymous object to compare pairs.
- The app creates an anonymous class object implementing
Comparatorand hands it tosort. javac named the classMain$1. sortcallscompare(a, b)on the anonymous object. A negative result putsafirst. We returnb.runs − a.runs, so more runs come first: Riya moves ahead of Om.- Kabir (12) is compared with Om (34): he belongs after Om.
- One more comparison confirms Kabir's place at the end.
- The leaderboard is sorted by runs, highest first. The rule lived in a class with no name, written exactly where it was needed. (Simplified: the exact comparisons depend on the sorting algorithm.)
1. Data structure internals
HashMap keeps its entries in a static nested class HashMap.Node; LinkedList has LinkedList.Node; ArrayList's iterator is the inner class ArrayList.Itr, which reads the list's private array through its outer link.
2. Builders
Classes with many optional fields often expose a static class Builder nested inside them: new Pizza.Builder().size(LARGE).cheese().build(). The builder is grouped with the class it builds but needs no existing object.
3. Event handlers and callbacks
Desktop and Android apps attach click listeners as anonymous classes (now usually lambdas). Thread code passes a new Runnable() { … }. Each is a small piece of behaviour written exactly where it's used.
4. Memory leaks to watch for
An inner-class listener registered with a long-lived service keeps its outer object (say a whole screen with images) in memory through this$0. Android guidelines recommend static nested classes for such cases.
Examples are simplified. JDK class names and internals vary between versions.
A chai stall with bills, tax and a festival offer
interface Offer {
int apply(int amount);
}
class Stall {
private final String name;
private int billCount = 0;
Stall(String name) { this.name = name; }
class Bill { // inner: belongs to one stall
final int no;
final int amount;
Bill(int amount) {
this.amount = amount;
billCount++; // updates the OUTER object's field
this.no = billCount;
}
String print(Offer offer) {
int pay = offer.apply(amount);
return name + " #" + no + ": ₹" + pay + " + GST ₹" + Tax.gst(pay);
}
}
static class Tax { // static nested: no stall needed
static int gst(int amount) { return amount * 5 / 100; }
}
}
public class Main {
public static void main(String[] args) {
Stall s = new Stall("Chai Point");
Offer diwali = new Offer() { // anonymous class
public int apply(int amount) { return amount >= 100 ? amount - 20 : amount; }
};
Stall.Bill b1 = s.new Bill(120);
Stall.Bill b2 = s.new Bill(60);
System.out.println(b1.print(diwali));
System.out.println(b2.print(diwali));
}
}class BillInner class: every bill is created from a stall and reads itsname.billCount++An inner object can change the outer object's private field: both bills share one counter.static class TaxStatic nested: called asTax.gst(…), no stall object required.new Offer() { … }Anonymous class implementingOffer, written where it's used.
The hidden link, recorded from the JVM
This trace is recorded from the real JVM. Look at the inner Bill object on the heap: besides amount it has a field this$0 pointing to the outer object. The static nested Tax call needs no object at all.
Worked examples: real problems, solved and traced in memory
Three designs using each kind of nested class. Watch the heap for this$0 and val$…: the hidden fields javac adds.
Seats that know their train
Problem statement
A Train books seats. Each Seat must print its label as 12123/S4-32 using the train's number, and every booking increases the train's booked count.
Train "12123": book S4-32, S4-33
12123/S4-32, 12123/S4-33 | booked 2
Approach
- Make
Seata member inner class, so each seat is tied to one train. - The Seat constructor increments the outer
bookedfield. label()reads the outernumberthrough the hidden link.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
A one-off alert format that captures a variable
Problem statement
A kitchen screen needs alerts prefixed with the outlet name, like [Chai Point] Order #12 ready. Write the formatter as an anonymous class of interface Alert, using a local variable for the outlet name.
outlet "Chai Point", events "Order #12 ready", "Order #13 ready"
[Chai Point] Order #12 ready [Chai Point] Order #13 ready
Approach
- Declare the outlet name as a local variable; it stays effectively final.
- Create
new Alert() { … }: javac copies the local into the anonymous object (hidden fieldval$outlet). - Call
formatfor each event.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
An order built step by step
Problem statement
An Order has a customer, an item and optional extras (express delivery, cutlery). Use a static nested Builder so callers can't mix up the arguments, and print the final bill.
Riya, Masala Dosa ₹120, express (+₹30)
Riya pays ₹150
Approach
Orderhas a private constructor that takes aBuilder.static class Buildercollects values with named methods, each returningthis.build()creates the immutable Order.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 10.5 in one screen
- Nested = class inside a class. Static nested (no outer object) vs inner (member, local, anonymous).
- Member inner:
outer.new Inner(); holds hiddenthis$0; can read outer private members;Outer.this.xfor name clashes. - Static nested:
new Outer.Nested(); can't use outer instance fields; preferred when no outer object is needed. - Local class: inside a method, visible only there.
- Anonymous:
new Interface() { … }; one use; filesOuter$1.class. Often replaced by lambdas. - Captured locals must be final or effectively final (copied into
val$x). - Class files:
Outer$Inner,Outer$1,Outer$1Local. Inner objects keep outer objects alive.
Interview and viva questions
What is the difference between a static nested class and an inner class?
A static nested class is like a top-level class placed inside another for grouping; it has no reference to an outer object and is created with new Outer.Nested(). An inner class instance is tied to an outer instance (hidden this$0), can use its instance members directly, and is created with outer.new Inner().
Can an inner class access private members of the outer class?
Yes, and the outer class can access the inner class's private members too. Before Java 11, javac generated hidden accessor methods (access$000) for this; since Java 11 the JVM knows they are nestmates and allows it directly.
Anonymous class vs lambda?
A lambda can only implement a functional interface (one abstract method) and has no state of its own; this inside a lambda means the enclosing object. An anonymous class can extend a class, implement several methods, have fields, and its this is the anonymous object itself. Lambdas don't create a $1.class file per use; they're linked at run time (invokedynamic).
Why are captured variables required to be effectively final?
Local variables live on the stack and disappear when the method returns, but the inner object may live longer. So Java copies the value into the object. If the local could change afterwards, the copy would be stale and confusing, so the language forbids changing it.
Can a nested class be private or static? Can a top-level class?
Nested classes can use any access modifier (private, protected, …) and static. Top-level classes can only be public or package-private and can't be static (Lesson 6.5).
Practice problems
Choose the right kind of nested class and use each one correctly. The last three are business tasks.
Create an inner object
Class College has an inner class Student that prints the college name with the student's name. Create one from main.
College "GES", student "Riya"
Riya @ GES Law College
Show solution and explanation
class College {
private final String name;
College(String name) { this.name = name; }
class Student {
final String sname;
Student(String sname) { this.sname = sname; }
String card() { return sname + " @ " + name; }
}
}
public class Main {
public static void main(String[] args) {
College c = new College("GES Law College");
College.Student s = c.new Student("Riya");
System.out.println(s.card());
}
}How it works: c.new Student(...) ties the student to college c; card() reads the outer name.
Use a static nested helper
Put a static nested class Units inside Converter with a method kmToMiles. Convert 42 km.
42 km
26.10 miles
Show solution and explanation
class Converter {
static class Units {
static double kmToMiles(double km) { return km * 0.621371; }
}
}
public class Main {
public static void main(String[] args) {
System.out.printf("%.2f miles%n", Converter.Units.kmToMiles(42));
}
}How it works: a static nested class is reached through the outer class name; no objects are needed.
An anonymous greeting
Implement interface Greeter { String greet(String n); } with an anonymous class that returns Namaste, <name>!.
"Kabir"
Namaste, Kabir! Main$1
Show solution and explanation
interface Greeter {
String greet(String n);
}
public class Main {
public static void main(String[] args) {
Greeter g = new Greeter() {
public String greet(String n) { return "Namaste, " + n + "!"; }
};
System.out.println(g.greet("Kabir"));
System.out.println(g.getClass().getName());
}
}How it works: the anonymous class has no source name; javac calls it Main$1, which getClass().getName() shows.
Outer.this for clashing names
Both Account and its inner class Statement have a field id. Print both from inside Statement.
account id 501, statement id 9
Account 501, statement 9
Show solution and explanation
class Account {
int id = 501;
class Statement {
int id = 9;
String header() {
return "Account " + Account.this.id + ", statement " + id;
}
}
}
public class Main {
public static void main(String[] args) {
Account a = new Account();
System.out.println(a.new Statement().header());
}
}How it works: plain id is the nearest one (Statement's); Account.this.id names the outer object's field.
Local class with a captured variable
In a method report(String month, int[] sales), declare a local class that formats each line using month. Print two lines.
"Oct", {1200, 950}Oct week 1: ₹1200 Oct week 2: ₹950
Show solution and explanation
public class Main {
static void report(String month, int[] sales) {
class Line {
String text(int week, int amount) {
return month + " week " + week + ": ₹" + amount;
}
}
Line l = new Line();
for (int i = 0; i < sales.length; i++) {
System.out.println(l.text(i + 1, sales[i]));
}
}
public static void main(String[] args) {
report("Oct", new int[]{1200, 950});
}
}How it works: month is effectively final, so the local class may capture it. The class exists only inside report.
Discount rules as anonymous classes
Apply two discount rules to ₹800: a flat ₹50 off and 15% off. Implement each rule as an anonymous class of interface Rule and print the better price.
₹800
Flat: ₹750, 15%: ₹680 → best ₹680
Show solution and explanation
interface Rule {
int apply(int price);
}
public class Main {
public static void main(String[] args) {
Rule flat = new Rule() {
public int apply(int price) { return price - 50; }
};
Rule percent = new Rule() {
public int apply(int price) { return price - price * 15 / 100; }
};
int a = flat.apply(800), b = percent.apply(800);
System.out.println("Flat: ₹" + a + ", 15%: ₹" + b + " → best ₹" + Math.min(a, b));
}
}How it works: two anonymous classes (Main$1, Main$2) implement the same interface differently. Callers just call apply.
Bank account with an inner transaction log
A Wallet keeps a list of Txn objects. Txn is an inner class whose constructor updates the wallet's balance. Add ₹500, spend ₹120, and print the balance and log size.
+500, -120
Balance ₹380, 2 transactions
Show solution and explanation
import java.util.ArrayList;
import java.util.List;
class Wallet {
private int balance = 0;
private final List<Txn> log = new ArrayList<>();
class Txn {
final int amount;
Txn(int amount) {
this.amount = amount;
balance += amount; // outer field
log.add(this); // outer list
}
}
void add(int amt) { new Txn(amt); }
void spend(int amt) { new Txn(-amt); }
String summary() { return "Balance ₹" + balance + ", " + log.size() + " transactions"; }
}
public class Main {
public static void main(String[] args) {
Wallet w = new Wallet();
w.add(500);
w.spend(120);
System.out.println(w.summary());
}
}How it works: inside Wallet's own methods, new Txn(...) uses this as the outer object automatically. Each transaction updates the balance and logs itself.
Pizza order builder
Build a large pizza with extra cheese using a static nested Builder. Medium costs ₹249, large ₹399, extra cheese ₹40.
large + cheese
Large pizza: ₹439
Show solution and explanation
class Pizza {
final String size;
final int price;
private Pizza(Builder b) {
this.size = b.size;
this.price = b.price;
}
static class Builder {
private String size = "Medium";
private int price = 249;
Builder large() { size = "Large"; price = 399; return this; }
Builder cheese() { price += 40; return this; }
Pizza build() { return new Pizza(this); }
}
}
public class Main {
public static void main(String[] args) {
Pizza p = new Pizza.Builder().large().cheese().build();
System.out.println(p.size + " pizza: ₹" + p.price);
}
}How it works: the static nested builder can call Pizza's private constructor. Chained methods make each choice readable, and the Pizza itself is immutable.
Real-world scenario questions (asked in company interviews)
Nested-class questions test design judgement: should this class know about its outer object, and what does that link cost?
A screen that never leaves memory
After users open and close the “Order history” screen many times, the app runs out of memory. A heap dump shows dozens of screen objects kept alive by a listener list in a singleton service. The listeners are inner classes of the screen. Explain and fix.
The interviewer is checking: the hidden this$0 reference and its effect on GC.
Show a strong answer
- Each inner listener holds
this$0→ the screen. The singleton's list is reachable forever, so every screen (with its views and images) stays reachable. - Unregister listeners when the screen closes.
- Make the listener a static nested class that holds only what it needs (or a weak reference to the screen).
- Verify with a heap dump before and after.
Avoid: calling System.gc(). The objects are still reachable, so GC can't free them.
Where should Node live?
You're writing your own LinkedList with a Node class. Should Node be a top-level class, an inner class, or a static nested class?
The interviewer is checking: choosing the right nesting.
Show a strong answer
- Private static nested: Node is an implementation detail (so nested and private) and doesn't need the list object (so static).
- An inner Node would waste a
this$0reference per node: memory multiplied by every element. - The JDK does exactly this:
private static class Node<E>injava.util.LinkedList.
Avoid: making it public top-level “in case someone needs it”. That freezes your internals into your API.
Twenty anonymous comparators
A reporting module sorts lists with 20 copies of new Comparator<Order>() { … }, many identical. How would you clean this up?
The interviewer is checking: when anonymous classes stop being a good idea.
Show a strong answer
- Replace each with a lambda or
Comparator.comparing(Order::getAmount)(Lessons 11.8 and 14.1); one line instead of five. - Put reusable rules in one place:
static final Comparator<Order> BY_AMOUNTin theOrderclass. - Each anonymous class also generates a separate
.classfile; fewer, shared comparators are easier to test.
Avoid: leaving duplicated logic because “it works”. Duplicates drift apart over time.
A payment request with eleven optional fields
The PaymentRequest constructor has 11 parameters, and callers keep mixing up the order of amount and fee. What pattern would you use, and how does nesting help?
The interviewer is checking: the builder pattern with a static nested class.
Show a strong answer
- A static nested
Builder:PaymentRequest.builder().amount(500).fee(5).upi("riya@okbank").build(). Named methods make each value obvious. - The builder is nested so it can call the private constructor and stays grouped with the class it builds; it's static because no request exists yet.
build()validates required fields, and the resulting object can be immutable.
Avoid: setters on the request itself. A half-filled, mutable payment object is a bug waiting to happen.