Concepts / Inner Classes

Inner Classes

A non-static nested class is logically attached to each containing object, so it can reach that object's instance variables and instance methods, including private ones.

  • Programming

Why Nesting Raises a Design Choice

Java lets you define one class inside the body of another. The class written inside is called a nested class. When you write one class inside another, you face a choice that is easy to make by habit and easy to get wrong: should the nested class be static or non-static? Both forms are legal, and both compile. The difference shows up only when the nested class's code needs something that belongs to a particular object of the containing class.

This article focuses on the non-static form, which has its own name: the inner class. A non-static nested class is not simply a class that happens to sit inside another class. It is tied to objects of the surrounding class, and that tie determines what the inner class can see and what it cannot. As you read, keep one question in mind: when an inner class needs a value that belongs to a particular outer object, where does that value come from? The answer explains almost everything about how inner classes work.

Tracing Two Outer Objects

Start with an ordinary non-static member. Even though its source code is written inside the class definition, it is not really part of the class as a single shared thing. A non-static field or method describes what each object created from the class will contain. A non-static inner class follows the same pattern. Its source code sits inside the containing class, but its existence is tied to individual objects. The right picture is not one inner class for the outer class, but an inner class attached to each outer object.

The following example is an original illustration written to make this association visible. The outer class Counter has a private counter and a label. Its inner class Incrementer increments the counter and prints it. A separate static nested class Helper is included for comparison, and it does not touch any outer instance data. Read the code first and think about what each outer object's counter will look like after the calls.

java
java
Output
A: 1
A: 2
B: 1

What do you think happens?

After ia.bump() runs twice, what does ib.bump() print next?

Reveal answer
Answer:

B: 1

ib is attached to the Counter object labeled B, whose count is still 0 before this call. Its bump() increments that object's count to 1. The count inside a, which is already 2, is never touched.

Counter AIncrementer iaCounter BIncrementer ibattached toattached to
Which outer object is each inner class instance attached to, and how do two outer objects keep their own inner objects separate?

The diagram shows the key consequence. The two inner objects come from the same class definition, but each one is attached to a different outer object. ia works with Counter A's count and label, and ib works with Counter B's. Two inner-class copies in two different containing objects refer to different instance variables, so they behave differently whenever those variables hold different values.

What Makes It an Inner Class

Definition

A non-static nested class is called an inner class. It is associated with an object of the containing class rather than with the containing class itself.

The word logically matters. A helpful way to reason about inner classes is to imagine that every object of the containing class carries its own copy of the nested class. An object does not physically contain a second copy of the compiled code for the nested class. What matters is behavior: each copy works with the instance variables and instance methods of the particular outer object it belongs to.

Reaching Private Fields From Inside

Because each inner copy is attached to its outer object, it can use all of that object's instance methods and instance variables. This access extends to members declared private. An inner class is therefore not restricted the way an unrelated class would be when it tries to read another class's private data. In the Counter example, Incrementer reads and changes count, which is private to Counter, and it can do so because it is part of Counter's structure for each object.

Counter Acount: 2Incrementer iabump()Counter Bcount: 1Incrementer ibbump()reads and changes countreads and changes count
When Incrementer's bump() changes count, which outer object's field is it touching, and how does it reach that object?

Trace one call. When ia.bump() runs, count++ does not refer to some shared Counter field. It refers to the count of the Counter object that ia is attached to, which is Counter A. Its value goes from 1 to 2. The printed label is also taken from Counter A, which is why the output reads A: 2. The call to ib.bump() uses Counter B's count and label instead, so the two inner objects never interfere with each other.

Worked example

Tracing count Through Two Calls

Starting with Counter A at count 0 and Counter B at count 0, what does the program print after ia.bump(), ia.bump(), and ib.bump() run in order?

  1. 1
    Start state

    Counter A has count 0 and label A. Counter B has count 0 and label B.

  2. 2
    First ia.bump()

    ia is attached to Counter A, so count in Counter A goes from 0 to 1. The line printed is A: 1.

  3. 3
    Second ia.bump()

    Counter A's count goes from 1 to 2. The line printed is A: 2.

  4. 4
    ib.bump()

    ib is attached to Counter B, so Counter B's count goes from 0 to 1. Counter A is not touched. The line printed is B: 1.

Result

The program prints A: 1, A: 2, B: 1. Counter A ends with count 2 and Counter B ends with count 1.

Deciding Between Static and Non-Static

Once you know that a non-static nested class is tied to an outer object, the choice becomes a practical decision. The rule asks a single question: does this nested class depend on a particular object of the containing class? The question is about dependence, not about how often the nested class is used or how it is named. If the answer is yes, the class must be non-static, because only a non-static inner class is attached to a containing object and can reach that object's instance members. If the answer is no, there is nothing for that attachment to provide, and static is the natural choice.

Uses outer instancestate?Make it non-staticStatic is naturalyes: uses an instance variable or methodno: needs no outer instance data
Given a nested class, what check tells me whether it needs an outer instance and should therefore be non-static?
Worked example

Applying the Rule to a Report Class

A class Report has an instance variable title. Should a nested class Formatter be static or non-static in each of two versions? Version 1: Formatter only converts a number into a padded string and never reads title or calls any Report method. Version 2: Formatter must include the title of the report it belongs to in its output.

  1. 1
    Version 1 check

    Formatter uses no instance variable or instance method of Report. The answer to the rule's question is no.

  2. 2
    Version 1 decision

    Nothing needs the link to a Report object, so the class can be static.

  3. 3
    Version 2 check

    Formatter now reads title, an instance variable of Report. The answer to the rule's question is yes.

  4. 4
    Version 2 decision

    The output depends on which Report object Formatter is attached to, so the class must be non-static.

Result

Version 1 should be static. Version 2 should be non-static, because it needs instance state from the containing object.

The reasoning follows from the association already traced. A non-static nested class has an outer object behind it, so it can reach that object's instance data. A static nested class has no such link. If it needs instance data from the containing class, it has no route to that data and must be non-static. If it does not need that data, the link adds nothing, and a static nested class is the natural choice. In the Counter example, Incrementer needs count and label, so it is non-static, while Helper needs neither, so it is static.

Best practice

Judge the choice by real dependence on the containing object's instance state, not by convenience or privacy. Making a class non-static just because it feels closely related to its container does not give it access it needs, and making it static just because it is tucked inside a class does not remove any dependence it has.

Creating Inner Objects Correctly

Because an inner object is attached to an outer object, creating one requires an outer instance. In the Counter example the expression a.new Incrementer() reads as follows: a supplies the Counter object the new Incrementer will be attached to, the keyword new creates the inner object, and Incrementer names the inner class. Written without an outer object, a bare new Incrementer() has nothing to attach to, which is why it cannot succeed on its own.

java

Mistakes With Inner Class Objects

The mistakes below come from treating an inner class as if it belonged to the class as a single shared thing, rather than to individual outer objects. Each one is easy to make when writing a quick test or a static main method.

  • Common mistake

    Creating an inner object with a bare new and no outer instance

    What it looks like
    Counter.Incrementer ia = new Counter.Incrementer();
    Why it fails

    A non-static inner object is associated with an outer object. Without one, there is no object for its instance members to refer to.

    Do this instead

    Create the outer object first, then use it: Counter a = new Counter("A"); followed by a.new Incrementer().

  • Common mistake

    Creating an inner object from a static method without an outer instance

    What it looks like
    public static void main(String[] args) { Incrementer ia = new Incrementer(); }
    Why it fails

    A static method runs without any particular Counter object, so it has no outer instance to attach the inner object to.

    Do this instead

    Create or obtain an outer object inside the static method and attach the inner object to it, as in a.new Incrementer().

  • Common mistake

    Assuming all inner objects share one count

    What it looks like

    Expecting ib.bump() to print A: 3 after ia.bump() has run twice.

    Why it fails

    Each inner object works with the instance variables of its own outer object, so separate outer objects keep separate data.

    Do this instead

    Track which outer object each inner object is attached to. ib.bump() prints B: 1 in the example.

  • Common mistake

    Choosing static by habit, then reading outer fields

    Why it fails

    A static nested class has no link to an outer object, so it cannot use that object's instance variables or methods.

    Do this instead

    Apply the dependence rule: if the nested class uses any instance variable or instance method of the containing class, make it non-static.

SituationCreation patternResult
Inner object created with an outer objecta.new Incrementer()Attached to a; can use a's count and label
Inner object created with no outer objectnew Incrementer() in a static contextNo outer object to attach to; does not work
Static nested objectnew Helper()Needs no outer object; uses no outer instance data

Creation patterns for nested class objects and what each one connects to

Practice the Static Decision

MEDIUM

A class Library has an instance variable name and a nested class Card. Card must print the name of the library it belongs to each time it is shown. Should Card be static or non-static, and why? Then consider a second nested class Sorter that sorts an array passed to it as a parameter and uses no field of Library. Should Sorter be static or non-static?

Hints
  • Ask whether the nested class needs any instance variable or instance method of Library.
  • Card prints the library's name, so it depends on a particular Library object.
  • Sorter receives its data through a parameter and never refers to the library, so it has no need for an outer object.

Key Takeaways

  • A non-static nested class is an inner class. Each inner object is logically attached to one containing object and works with that object's instance variables and methods.
  • Inner objects can read and change the private instance variables of their outer object, because they belong to its structure. Two outer objects keep their inner objects' data separate.
  • Creating an inner object requires an outer instance, as in outer.new Inner(). A bare new Inner() in a static context has no object to attach to.
  • Choose static or non-static by one question: does the nested class need any instance variable or instance method of the containing class? If yes, make it non-static; if no, static is natural.