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.
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.
A: 1
A: 2
B: 1What do you think happens?
After ia.bump() runs twice, what does ib.bump() print next?
Reveal 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.
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
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.
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.
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?
- 1Start state
Counter A has count 0 and label A. Counter B has count 0 and label B.
- 2First ia.bump()
ia is attached to Counter A, so count in Counter A goes from 0 to 1. The line printed is A: 1.
- 3Second ia.bump()
Counter A's count goes from 1 to 2. The line printed is A: 2.
- 4ib.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.
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.
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.
- 1Version 1 check
Formatter uses no instance variable or instance method of Report. The answer to the rule's question is no.
- 2Version 1 decision
Nothing needs the link to a Report object, so the class can be static.
- 3Version 2 check
Formatter now reads title, an instance variable of Report. The answer to the rule's question is yes.
- 4Version 2 decision
The output depends on which Report object Formatter is attached to, so the class must be non-static.
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.
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.
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 likeCounter.Incrementer ia = new Counter.Incrementer();Why it failsA 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 insteadCreate 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 likepublic static void main(String[] args) { Incrementer ia = new Incrementer(); }Why it failsA static method runs without any particular Counter object, so it has no outer instance to attach the inner object to.
Do this insteadCreate 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 likeExpecting ib.bump() to print A: 3 after ia.bump() has run twice.
Why it failsEach inner object works with the instance variables of its own outer object, so separate outer objects keep separate data.
Do this insteadTrack 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 failsA static nested class has no link to an outer object, so it cannot use that object's instance variables or methods.
Do this insteadApply the dependence rule: if the nested class uses any instance variable or instance method of the containing class, make it non-static.
| Situation | Creation pattern | Result |
|---|---|---|
| Inner object created with an outer object | a.new Incrementer() | Attached to a; can use a's count and label |
| Inner object created with no outer object | new Incrementer() in a static context | No outer object to attach to; does not work |
| Static nested object | new 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
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 newInner()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.