# Advanced Classes

Course: [Java](https://www.devtutorials.ai/learn/java) · Section: Object-Oriented Programming
Source: https://www.devtutorials.ai/learn/java/object-oriented-programming/advanced-classes

> 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**.

```java runnable title="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:

```text
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 `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`.

```java runnable title="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:

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

```text
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
```

Inside the outer class's own instance methods, you can simply write `new Inner()`.

```java runnable title="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:

```text
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.

```java runnable title="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:

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

```text
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.

```java runnable title="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:

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

```text
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`.

```java runnable expect-error title="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`.

```text
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".

```java runnable title="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:

```text
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 `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
