Nested types and enclosing scope: which context is carried?
Trace which nested objects carry a link to an enclosing object, comparing Java static nested and inner classes with a Python class nested in another class. Methods and type contracts · Object-oriented programming.
You tuck a small Door helper inside a Cabinet class and expect each door to know which cabinet it belongs to. Does it? Java answers with one keyword, and Python answers no. Calling every nested class an “inner class” gets both languages wrong.
Java: static nested versus inner
A class declared inside another class is a nested class. Declared static, it is a static nested class: an ordinary class that lives inside Cabinet for naming, built without any cabinet, as new Cabinet.Label("shared label"). Declared without static, it is an inner class. Every inner object is created for one particular cabinet, written left.new Door(), and keeps a hidden reference to it, its enclosing instance. Code inside Door can read the cabinet's code directly, or name the cabinet as Cabinet.this.
A static nested class and an inner class
Read the steps as text
- Cabinet C1 exists and holds the code C1. The variable cabinet refers to it, while label and door refer to nothing yet.
- The program builds a new Label L1. Variable label now refers to Label L1. Nothing in the Label points at a Cabinet, because a static nested class needs no enclosing instance.
- The program builds a new Door D1 for Cabinet C1, and the Door's outer link points at that cabinet. Variable door now refers to Door D1. This Door was created for C1, so it holds a hidden link to that one cabinet.
- The variable is no longer the only way to reach C1, because the Door reaches it too.
- The program changes the code of Cabinet C1 from "C1" to "C9". The Door would read C9 through its link, because the link goes to the cabinet, not a copy of its code.
The scene shows the cabinet C1, a Label with no arrow, and a Door with one. The scene draws the hidden reference as an arrow labelled outer; Java gives you no field of that name to type, but every inner object has one.
final class Cabinet {
String code;
Cabinet(String code) { this.code = code; }
static final class Label {
final String text;
Label(String text) { this.text = text; }
}
final class Door {
String cabinetCode() { return code; }
Cabinet owner() { return Cabinet.this; }
}
}
public class Demo {
public static void main(String[] args) {
Cabinet cabinet = new Cabinet("C1");
Cabinet.Label label = new Cabinet.Label("shared label");
Cabinet.Door door = cabinet.new Door();
cabinet.code = "C9";
System.out.println(door.cabinetCode()); // C9
}
}The door printed C9, not C1. Its link goes to the cabinet object itself, so any later change to that cabinet shows through it. Writing new Cabinet.Door() with no cabinet does not compile: the error reads “an enclosing instance that contains Cabinet.Door is required”.
Each inner object keeps its own outer object
The link belongs to the object, not the class. Two doors of one class can reach different cabinets, because each is created for the cabinet written in front of new. The link is also a reference, like a variable or a field.
Each inner object keeps its own outer object
Read the steps as text
- Cabinets C2 and C3 hold the codes C2 and C3. The variables left and right refer to them, while door and spare refer to nothing yet.
- The program builds a new Door D2 for Cabinet C2, and the Door's outer link points at that cabinet. Variable door now refers to Door D2. Door is declared once, but this Door's link goes to the cabinet named in front of new.
- The program builds a new Door D3 for Cabinet C3, and the Door's outer link points at that cabinet. Variable spare now refers to Door D3. The second Door's link goes to C3, so two Doors of one class reach different cabinets.
- Variable left now refers to nothing. C2 lost its variable, but not every way to reach it.
- Only the Door's link still reaches C2, because nothing else refers to it now.
Cabinet left = new Cabinet("C2");
Cabinet right = new Cabinet("C3");
Cabinet.Door door = left.new Door();
Cabinet.Door spare = right.new Door();
left = null;
System.out.println(door.cabinetCode()); // C2After left = null; the program no longer names C2, yet door.cabinetCode() still returns C2. The door's link is a reference, so it keeps its cabinet reachable for as long as the door itself is reachable.
In real programs this decides a design. Suppose a door-like helper is stored somewhere long-lived, such as a long-lived list of callbacks or a cache. It silently keeps its whole cabinet, and everything that cabinet holds, from being freed. If a nested helper needs nothing from the outer object, declare it static so it holds only what you hand it.
Python: nesting adds a name, not a link
Python runs a class body once and turns the names assigned there into attributes of the new class. A class defined in that body becomes one more attribute, so Door is reached as Cabinet.Door. Calling Cabinet.Door("T1") builds an ordinary object. Nothing ties it to any Cabinet instance, and the cabinet in the scene exists regardless.
A Python nested class adds a name, not a link
Read the steps as text
- Cabinet C4 exists and holds the code C4. The variable cabinet refers to it, while door and spare refer to nothing yet.
- The program builds a new Door D4. Variable door now refers to Door D4. This Door's cabinet attribute is empty, so the nested class gave it no link.
- Door D4's cabinet link now points at Cabinet C4. Only this assignment connects the Door to a cabinet, exactly as for any other attribute.
- The program builds a new Door D5. Variable spare now refers to Door D5. Built the same way, this Door has no cabinet, because nothing linked it.
- Only the linked Door reaches C4: nesting the class added no link, the assignment did.
class Cabinet:
def __init__(self, code):
self.code = code
class Door:
def __init__(self, tag):
self.tag = tag
self.cabinet = None
def cabinet_code(self):
return self.cabinet.code
cabinet = Cabinet("C4")
door = Cabinet.Door("T1")
print(door.cabinet) # None
door.cabinet = cabinet # the program stores the link
print(door.cabinet_code()) # C4
spare = Cabinet.Door("T2")
print(spare.cabinet) # NoneCalling door.cabinet_code() before the assignment raises AttributeError, because the door's cabinet is None. After the assignment the arrow is an ordinary attribute reference. Passing the cabinet to the constructor and storing it there does the same job. A second door built the same way has no cabinet at all, and a bare Door("x") outside the class is a NameError, since the name lives on Cabinet.
Transfer the question
For any nested declaration ask one thing: does an instance of it carry a reference to an outer instance? In Java the answer is yes only for a non-static member class, and the keyword static settles it at the declaration. In Python there is no inner or static split, and the answer is yes only if your code stores one, so the link is something you can see in the constructor or the assignment. Classes declared inside a method, and the values they pick up from it, are a different link, taken up in the callbacks and capture lesson.
Recap
- Java static nested classes need no enclosing instance; a non-static member class is inner, and each inner object is created for one enclosing instance.
- The inner link goes to the enclosing object itself, so later changes show through it, and it keeps that object reachable.
- Python nesting a class adds a name inside the outer class; instances have no automatic link, and a stored attribute is the only one.
Go deeper
The compiler implements the inner link as a hidden field. Listing the declared fields of Cabinet.Door with reflection prints one synthetic field named this$0, which is the reference the scene draws as outer. The field's name and presence are compiler details, so rely on the language rule, not on the field. When a Python helper needs an outer instance, make that dependency explicit.
Continue to special methods and operator contracts for how Python looks up behavior implicitly, and revisit method receivers and class methods when the question is which object is bound at a call site. Practise object boundaries in Linked List Fundamentals, where a helper update still acts on a concrete node.