1. What is an inner class?
In Java, a class can be declared not only at the top level (in a file) but also inside another class. Such a class is called a nested class. If such a class is declared without the static modifier, it is called an inner class (a non-static inner class, or simply an inner class).
An inner class is a class declared inside another class and bound to an instance of that outer class. It can access all fields and methods of the outer class, even if they are private. It’s like an object having its own “secret assistant” who is allowed to do everything!
It’s similar to a house with rooms. The house is the outer class, and the room is the inner class. A room cannot exist without a house, yet it has access to the house’s resources: light, heating, furniture. If the house disappears, the room disappears too. That’s exactly how inner classes work in Java!
Why use inner classes?
- Logical cohesion: When one class is needed only to work with another class and makes no sense outside it.
- Encapsulation: Lets you hide implementation details without cluttering the package namespace.
- Access to private members: An inner class can access the outer class’s private fields and methods.
- Compactness: Reduces the number of “junk” classes at the package level.
Inner class declaration syntax
Declaring an inner class is very simple: it’s declared inside the body of another class without the static modifier.
class Outer {
// fields and methods of the outer class
class Inner {
// fields and methods of the inner class
void printHello() {
System.out.println("Hello from Inner!");
}
}
}
Here, Inner is an inner class relative to Outer.
Diagram: visualization
Outer
│
├─ fields/methods
│
└─ Inner (inner class)
└─ its own fields/methods
2. How to create an instance of an inner class
Here lies one of the most common traps for beginners! An instance of an inner class is always tied to a specific object of the outer class.
Example:
Outer outer = new Outer(); // create an object of the outer class
Outer.Inner inner = outer.new Inner(); // create the inner class via the outer object!
inner.printHello(); // Hello from Inner!
Trying to do just new Inner() will lead to a compilation error, because Java doesn’t know which outer class instance to associate this inner instance with.
Why is that?
An inner class can access the fields and methods of the outer class. Therefore, it must “know” which specific outer class object it works with.
3. Example of using an inner class
Let’s look at an example where an inner class is truly useful.
Example 1: “Backpack and items” model
Suppose we have a Backpack class that can store items (Item). But we want the Item class to be available only inside Backpack, because outside the backpack items don’t interest us.
public class Backpack {
private String owner;
public Backpack(String owner) {
this.owner = owner;
}
// Inner class — an item can exist only inside a backpack!
class Item {
private String name;
public Item(String name) {
this.name = name;
}
public void printInfo() {
// Magic! We can see the outer class’s private field
System.out.println(owner + " has an item: " + name);
}
}
}
Usage:
Backpack bp = new Backpack("John");
Backpack.Item item = bp.new Item("Java textbook");
item.printInfo(); // John has an item: Java textbook
Note: Item can access the owner field even though it is private!
Example 2: Iterator for your own collection
In Java, collections often implement an inner iterator class. Let’s build our own simple collection with an inner iterator class.
public class IntList {
private int[] data = new int[10];
private int size = 0;
public void add(int value) {
data[size++] = value;
}
// Inner class — iterator!
class Iterator {
private int index = 0;
public boolean hasNext() {
return index < size;
}
public int next() {
return data[index++];
}
}
}
Usage:
IntList list = new IntList();
list.add(10);
list.add(20);
IntList.Iterator it = list.new Iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
4. Features of inner classes
Access to members of the outer class
An inner class can access any (even private) fields and methods of the outer class. This is convenient but requires care: if the outer class changes, the inner one may suddenly “break.”
class Outer {
private int secret = 42;
class Inner {
void showSecret() {
System.out.println("Secret: " + secret);
}
}
}
An inner class cannot contain static members
A non-static inner class cannot contain static fields or methods, except static final constants (for example, public static final int MY_CONST = 123;). This is because an inner class is always “bound” to an instance of the outer class.
If you need to declare a static nested class, use the static modifier (more on this in the next lecture).
Visibility of an inner class
An inner class can be private, protected, public, or have package-private visibility.
public class Outer {
private class Inner { /* ... */ }
}
5. Useful nuances
When to use inner classes
Inner classes aren’t for looks but for logical code organization. Use them when:
- The class is needed only in one place (e.g., a helper iterator, a handler, or part of a complex structure).
- The class is tightly coupled to the outer class and doesn’t make sense outside it.
- You want to hide implementation details from other classes in the package.
Do not use inner classes just for fashion! Sometimes it’s better to move the class to the top level if it could be useful elsewhere.
Inner class and this
Inside an inner class you can refer to fields and methods of the outer class using OuterClassName.this.
class Car {
private String model = "Tesla";
class Engine {
void printModel() {
// Explicitly refer to the outer object
System.out.println("Model: " + Car.this.model);
}
}
}
This syntax is especially useful if the inner and outer classes have fields with the same names.
When you should NOT use inner classes
Do not use them if:
- The inner class doesn’t access fields/methods of the outer class
- The class could be useful in other parts of the program
- The outer class becomes too large and complex
In such cases, it’s better to:
- Make the class a static nested class (static class)
- Move the class into a separate file
- Use regular methods instead of an entire class
6. Typical mistakes when working with inner classes
Error #1: Trying to create an inner class without an instance of the outer class.
If you write new Inner(), the compiler will throw an error: "No enclosing instance of type Outer is accessible". Always create an inner class via an instance of the outer class: outer.new Inner().
Error #2: Attempting to declare static fields or methods in an inner class.
An inner class cannot contain static members (except constants). If you need them, use a static nested class—that is, declare the class with the static modifier.
Error #3: Overusing inner classes.
If the inner class does not use the outer class’s fields/methods, it likely should be static or moved out entirely. Don’t create inner classes “just in case.”
Error #4: Confusion when referring to fields of the outer class.
If the inner and outer classes have fields with the same names, use OuterClassName.this.field to explicitly refer to the outer class’s field.
GO TO FULL VERSION