Advanced Classes in Java
By the Syncfusion DevTutorials team · Last updated
Organise code with static nested, inner, local and anonymous classes, and control extension with final and sealed classes.
Think about a car.
The car is one big object. Inside it are smaller parts, like the engine and the gearbox. Those parts only make sense as part of the car. You would never sell an engine on its own as "a car".
Java lets you organise classes the same way. You can put a class inside another class, create a class inside a method, or create a class with no name at all. You can also control who is allowed to extend a class.
In simple words: advanced classes let you place a class exactly where it is used, and decide which classes may build on it.
What You Will Learn
In this lesson, you will learn:
- What nested classes are, and how static nested classes work.
- How inner classes can use the fields of their outer object.
- How to declare a local class inside a method.
- How to create a one-time anonymous class.
- How
finalstops a class from being extended. - How
sealedallows only a chosen list of child classes. - When to use each of these.
Start With a Working Example
Press Run.
A class that lives inside another class
public class Main {
public static void main(String[] args) {
Order order = new Order("ORD-101");
order.addItem("Pen", 20);
order.addItem("Notebook", 45);
order.printBill();
}
}
class Order {
private String id;
private Item[] items = new Item[10];
private int count = 0;
Order(String id) {
this.id = id;
}
void addItem(String name, int price) {
items[count] = new Item(name, price);
count++;
}
void printBill() {
System.out.println("Bill for " + id);
for (int i = 0; i < count; i++) {
System.out.println(" " + items[i].name + ": " + items[i].price);
}
}
private static class Item {
String name;
int price;
Item(String name, int price) {
this.name = name;
this.price = price;
}
}
}
Expected output:
Bill for ORD-101
Pen: 20
Notebook: 45
Item is written inside Order, and it is private. Only Order can use it. The rest of the program does not even know Item exists.
Try This
Add a third item, order.addItem("Bag", 499);. The bill grows, and Item stays hidden inside Order.
Nested Classes
A nested class is a class declared inside another class. The class around it is called the outer class.
Java has four kinds:
| Kind | Declared | Has access to the outer object? |
|---|---|---|
| Static nested class | Inside the class body, with static | No |
| Inner class | Inside the class body, without static | Yes |
| Local class | Inside a method | Yes, plus the method's variables |
| Anonymous class | Inside an expression, with no name | Yes, plus the method's variables |
Why nest a class at all?
- Grouping. A helper that only one class uses lives right next to it.
- Hiding. A
privatenested class is invisible to the rest of the program. - Access. A nested class can use the outer class's
privatemembers.
Static Nested Classes
A static nested class is declared with static inside another class. It does not belong to any outer object. It behaves like a normal class that just happens to live inside another one.
From outside, you name it with the outer class first: Outer.Nested.
A static nested class is named through its outer class
public class Main {
public static void main(String[] args) {
Phone.Spec basic = new Phone.Spec(64, 4);
Phone.Spec pro = new Phone.Spec(256, 8);
System.out.println("Basic: " + basic.describe());
System.out.println("Pro: " + pro.describe());
}
}
class Phone {
static class Spec {
int storageGb;
int ramGb;
Spec(int storageGb, int ramGb) {
this.storageGb = storageGb;
this.ramGb = ramGb;
}
String describe() {
return storageGb + " GB storage, " + ramGb + " GB RAM";
}
}
}
Expected output:
Basic: 64 GB storage, 4 GB RAM
Pro: 256 GB storage, 8 GB RAM
new Phone.Spec(64, 4) creates a Spec without any Phone object. That is possible because Spec is static.
Inner Classes
An inner class is a nested class without static. Every inner object is tied to one outer object.
Because of that link, an inner class can use the outer object's fields and methods directly, even private ones.
To create an inner object from outside, you need an outer object first:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
Inside the outer class's own instance methods, you can simply write new Inner().
An inner class reads its outer object's private field
public class Main {
public static void main(String[] args) {
Bank bank = new Bank("City Bank");
Bank.Account a = bank.new Account("Asha");
Bank.Account b = bank.new Account("Ravi");
a.welcome();
b.welcome();
}
}
class Bank {
private String bankName;
Bank(String bankName) {
this.bankName = bankName;
}
class Account {
private String owner;
Account(String owner) {
this.owner = owner;
}
void welcome() {
System.out.println("Welcome to " + bankName + ", " + owner);
}
}
}
Expected output:
Welcome to City Bank, Asha
Welcome to City Bank, Ravi
welcome() uses bankName, which is a private field of Bank. The inner Account can see it, because each account belongs to one bank object.
Static Nested vs Inner
| Static nested class | Inner class | |
|---|---|---|
| Keyword | static class | class |
| Needs an outer object? | No | Yes |
| Can use outer instance fields? | No | Yes |
| Created with | new Outer.Nested() | outer.new Inner() |
| Use it when | The helper does not need outer data | The helper works with one outer object's data |
If a nested class does not need the outer object's fields, make it static. It is simpler, and it does not hold a hidden link to an outer object.
Local Classes
A local class is declared inside a method. It only exists inside that method, like a local variable.
A local class can use:
- the outer object's fields (if the method is not static);
- the method's local variables and parameters, as long as they never change after being set. Java calls these effectively final.
Use a local class for a small helper that only one method needs.
A class that only exists inside one method
public class Main {
public static void main(String[] args) {
printReport(new int[] {72, 45, 90, 38}, 40);
}
static void printReport(int[] marks, int passMark) {
class Result {
int mark;
Result(int mark) {
this.mark = mark;
}
String label() {
return mark >= passMark ? "Pass" : "Fail";
}
}
for (int m : marks) {
Result r = new Result(m);
System.out.println(m + " -> " + r.label());
}
}
}
Expected output:
72 -> Pass
45 -> Pass
90 -> Pass
38 -> Fail
Result is declared inside printReport. It reads passMark, a parameter of that method.
Outside printReport, the name Result does not exist.
Anonymous Classes
An anonymous class is a class with no name. You declare it and create one object of it in a single expression.
It always extends a class or implements an interface, and it is written right after new:
Greeting g = new Greeting() {
public void sayHello() {
System.out.println("Hi!");
}
};
Use an anonymous class when you need one special version of something, just once.
Two one-off behaviours, written where they are used
public class Main {
public static void main(String[] args) {
Discount festival = new Discount() {
public int apply(int price) {
return price - price * 20 / 100;
}
};
Discount flat = new Discount() {
public int apply(int price) {
return price - 50;
}
};
System.out.println("Festival price: " + festival.apply(1000));
System.out.println("Flat discount price: " + flat.apply(1000));
}
}
interface Discount {
int apply(int price);
}
Expected output:
Festival price: 800
Flat discount price: 950
Discount is an interface: a list of methods a class promises to have. You will learn interfaces properly in a later lesson.
Each anonymous class gives apply its own body. Neither one needed a class name, because each is used only once.
Note the ; after the closing brace. The whole anonymous class is part of one statement.
For an interface with only one method, like Discount, modern Java usually uses a shorter lambda instead: Discount flat = price -> price - 50;. You will meet lambdas with functional interfaces. Anonymous classes are still needed when there is more than one method to write.
Easy Way to Remember: static nested = lives inside, needs no outer object. Inner = tied to one outer object. Local = lives inside a method. Anonymous = no name, used once.
Final Classes
A final class cannot be extended. No other class can use it as a parent.
You add final before class:
final class Money { ... }
Use final when changing the class's behaviour in a child class could break it. String is a final class. If someone could extend String, they could create a "string" that behaves differently and break code that trusts strings.
Extending a class means creating a child class that builds on it. You will learn this in the Inheritance lesson. The keyword is extends.
No class may extend a final class
public class Main {
public static void main(String[] args) {
System.out.println(new Rupee(100).amount);
}
}
final class Rupee {
int amount;
Rupee(int amount) {
this.amount = amount;
}
}
class FakeRupee extends Rupee {
FakeRupee() {
super(1000000);
}
}
The compiler reports cannot inherit from final Rupee.
Sealed Classes
Sometimes "nobody may extend this" is too strict, and "anybody may extend this" is too loose. You want a fixed list of child classes.
A sealed class (or sealed interface) names exactly which classes may extend it, with permits.
sealed class Shape permits Circle, Square { ... }
Each permitted class must say how it continues the chain:
finalmeans it cannot be extended further.sealedmeans it lists its own permitted children.non-sealedmeans anyone may extend it again.
Sealed classes were added in Java 17.
Think of a traffic light. It can be red, amber or green, and nothing else. A sealed type says "these are all the kinds there will ever be".
A sealed type with a fixed list of kinds
public class Main {
public static void main(String[] args) {
Payment[] payments = {
new Cash(500),
new Card(1200, "4321"),
new Upi(250, "asha@bank")
};
for (Payment p : payments) {
System.out.println(p.describe());
}
}
}
sealed interface Payment permits Cash, Card, Upi {
String describe();
}
final class Cash implements Payment {
int amount;
Cash(int amount) { this.amount = amount; }
public String describe() { return "Cash: " + amount; }
}
final class Card implements Payment {
int amount;
String lastDigits;
Card(int amount, String lastDigits) { this.amount = amount; this.lastDigits = lastDigits; }
public String describe() { return "Card ending " + lastDigits + ": " + amount; }
}
final class Upi implements Payment {
int amount;
String id;
Upi(int amount, String id) { this.amount = amount; this.id = id; }
public String describe() { return "UPI " + id + ": " + amount; }
}
Expected output:
Cash: 500
Card ending 4321: 1200
UPI asha@bank: 250
Payment allows exactly three kinds. A fourth class, such as Cheque implements Payment, would not compile, because it is not in the permits list.
That gives you a guarantee: any Payment is a Cash, a Card or a Upi. Nothing else is possible.
Sealed Classes vs Final Classes
final class | sealed class | Normal class | |
|---|---|---|---|
| Who may extend it? | Nobody | Only the classes in permits | Anybody |
| Purpose | Lock the class completely | Fix the list of kinds | Allow free extension |
| Child classes must be | — | final, sealed or non-sealed | Anything |
| Example | String, Integer | Shape permits Circle, Square | Most classes you write |
| Added in | Java 1.0 | Java 17 | — |
In simple words: final says "no children". sealed says "only these children".
Use Cases of Advanced Classes
| Feature | Use it for | Example |
|---|---|---|
| Static nested class | A helper type grouped with the class that uses it | Order.Item, Phone.Spec |
| Inner class | A helper that works with one outer object's data | Bank.Account, an iterator over a list |
| Local class | A helper needed by one method only | A result type inside a report method |
| Anonymous class | A one-time version of an interface or class | A custom comparison or click handler |
| Final class | A type whose behaviour must never change | Money, IDs, security checks |
| Sealed class | A type with a fixed set of kinds | Payment types, shapes, order states |
Common mistakes
The following mistakes are common when using nested, local, anonymous and sealed classes. Each row shows what you see, why it happens and how to fix it.
| Symptom | Cause | Fix |
|---|---|---|
an enclosing instance ... is required | An inner object was made without an outer object | Use outer.new Inner(), or make the class static |
must be final or effectively final | A captured local variable was changed | Do not change variables used by local or anonymous classes |
';' expected after an anonymous class | The statement was not closed | Add ; after the closing brace |
cannot inherit from final | A class tried to extend a final class | Remove extends, or remove final if extension is safe |
class is not allowed to extend sealed class | The class is not in permits | Add it to permits, or do not extend |
sealed, non-sealed or final modifiers expected | A permitted child has no modifier | Mark it final, sealed or non-sealed |
What to Remember
- A nested class lives inside another class. Static nested classes need no outer object.
- An inner class is tied to an outer object and can use its fields, even private ones.
- A local class lives inside a method. An anonymous class has no name and is used once.
- Local and anonymous classes can only use local variables that never change.
- A
finalclass cannot be extended. Asealedclass can only be extended by the classes itpermits. - Use these tools to keep helpers close to where they are used, and to control how types can grow.
Next: Enums and Records