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:

  1. What nested classes are, and how static nested classes work.
  2. How inner classes can use the fields of their outer object.
  3. How to declare a local class inside a method.
  4. How to create a one-time anonymous class.
  5. How final stops a class from being extended.
  6. How sealed allows only a chosen list of child classes.
  7. 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:

KindDeclaredHas access to the outer object?
Static nested classInside the class body, with staticNo
Inner classInside the class body, without staticYes
Local classInside a methodYes, plus the method's variables
Anonymous classInside an expression, with no nameYes, 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 private nested class is invisible to the rest of the program.
  • Access. A nested class can use the outer class's private members.

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 classInner class
Keywordstatic classclass
Needs an outer object?NoYes
Can use outer instance fields?NoYes
Created withnew Outer.Nested()outer.new Inner()
Use it whenThe helper does not need outer dataThe 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:

  • final means it cannot be extended further.
  • sealed means it lists its own permitted children.
  • non-sealed means 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 classsealed classNormal class
Who may extend it?NobodyOnly the classes in permitsAnybody
PurposeLock the class completelyFix the list of kindsAllow free extension
Child classes must be—final, sealed or non-sealedAnything
ExampleString, IntegerShape permits Circle, SquareMost classes you write
Added inJava 1.0Java 17—

In simple words: final says "no children". sealed says "only these children".

Use Cases of Advanced Classes

FeatureUse it forExample
Static nested classA helper type grouped with the class that uses itOrder.Item, Phone.Spec
Inner classA helper that works with one outer object's dataBank.Account, an iterator over a list
Local classA helper needed by one method onlyA result type inside a report method
Anonymous classA one-time version of an interface or classA custom comparison or click handler
Final classA type whose behaviour must never changeMoney, IDs, security checks
Sealed classA type with a fixed set of kindsPayment 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.

SymptomCauseFix
an enclosing instance ... is requiredAn inner object was made without an outer objectUse outer.new Inner(), or make the class static
must be final or effectively finalA captured local variable was changedDo not change variables used by local or anonymous classes
';' expected after an anonymous classThe statement was not closedAdd ; after the closing brace
cannot inherit from finalA class tried to extend a final classRemove extends, or remove final if extension is safe
class is not allowed to extend sealed classThe class is not in permitsAdd it to permits, or do not extend
sealed, non-sealed or final modifiers expectedA permitted child has no modifierMark 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 final class cannot be extended. A sealed class can only be extended by the classes it permits.
  • Use these tools to keep helpers close to where they are used, and to control how types can grow.

Next: Enums and Records