Inner classes and OOP consolidation
Classes that live inside classes, when nesting helps, and a recap that ties Phases 2 and 3 together.
Finished reading?
Mark this session so you can track where you are.
Classes that live inside classes, when nesting helps, and a recap that ties Phases 2 and 3 together.
Finished reading?
Mark this session so you can track where you are.
Every class you have written so far has stood on its own, one per idea, side by side in the file. That is the common case and it will stay the common case. But sometimes a class only makes sense in the shadow of another one. A little helper type that has no life of its own outside its host. Java lets you write that helper inside the class it serves, and this session is about when that is worth doing, and how the kinds differ.
static nested class apart from a (non-static) inner class in plain words.Picture a class that holds a person's full address: the street, the city, and the postcode. On its own, an address is a perfectly fine class. But suppose the only reason it exists in your program is to be a part of a Person. No other code ever makes an address by itself, talks about an address by itself, or cares about an address that is not attached to a person. The address is a detail of the person, not a citizen of the wider program.
You could still write it as a separate top-level class. Nothing forces you to nest it. But doing so leaves a small lie in the codebase: the file says “here is a free-standing Address that anyone may use”, when the truth is “this only ever belongs to a Person”. Nesting the class tells that truth. It puts the helper where it is used and signals that it is a private detail of its host.
Nest a class when it is a detail of another class, not a thing the rest of the program needs to know about. The nesting groups related code and quietly says “this belongs to me”.
Node inside a list, or a Builder inside the class it builds. A linked list is made of little boxes, each holding one value and a link to the next box. That box, the node, is meaningless outside the list. It is the perfect thing to nest. You do not have the collections that use this yet, so we will not build one here, but keep the shape in mind: a small part, tucked inside the whole it belongs to.When you write a class inside another class, you face exactly one decision, and it is a decision you already understand from static and final: does this nested class need a connection to a particular object of the outer class, or not?
That single question splits nested classes into two kinds:
static. It has nolink to any outer object. It just happens to live inside the outer class's braces for grouping. Think of it as a normal top-level class that you filed inside another class for tidiness.Recall the heart of static and final: a static member belongs to the class itself, while a non-static member belongs to one object. The exact same idea governs nested classes. A static nested class belongs to the outer class. An inner class belongs to an outer object. Once you see that, the two kinds stop being two new things to memorise and become one familiar idea applied again.
static.static keyword.new Outer.Helper().outer.new Inner().Here is the address example as a static nested class. The interpreter in this course does not run nested-class declarations, so read this one carefully rather than pressing Run. The output is what real Java prints, traced by hand below.
public class Main { public static void main(String[] args) { // A static nested class is built on its own, no Person object needed. Person.Address home = new Person.Address("Cairo"); Person p = new Person("Aisha", home); System.out.println(p.name + " lives in " + p.address.city); }} class Person { String name; Address address; Person(String name, Address address) { this.name = name; this.address = address; } // Static nested class: it lives inside Person for tidiness, but it has // no link to any particular Person object. It is just filed here. static class Address { String city; Address(String city) { this.city = city; } }}Aisha lives in Cairo
Read the type name carefully: Person.Address. Because Address lives inside Person, its full name is qualified by its host, the same way a static field was reached as ClassName.field in static and final. The static keyword on the class is what lets you write new Person.Address("Cairo") with no Person object in sight. The nested class is grouped inside Person, but it stands on its own feet.
Now the other kind. An inner class is tied to an outer object, so it can read that object's fields without being handed them. Below, a BankAccount has an inner Statementclass. A statement only makes sense for one particular account, and it reaches straight into that account's owner and balance.
public class Main { public static void main(String[] args) { BankAccount acc = new BankAccount("Aisha", 500.0); // An inner class is built FROM an outer object: acc.new Statement(). // The statement is bound to this one account. BankAccount.Statement s = acc.new Statement(); s.print(); }} class BankAccount { String owner; double balance; BankAccount(String owner, double balance) { this.owner = owner; this.balance = balance; } // Inner class (no static): each Statement belongs to one BankAccount and // can read that account's fields, owner and balance, directly. class Statement { void print() { System.out.println("Statement for " + owner + ": balance is " + balance); } }}Statement for Aisha: balance is 500.0
Look at print(). It says owner and balancewith no dot in front, just like an ordinary method reading its own object's fields. But those fields do not belong to the Statement. They belong to the BankAccount that this statement was made from. That reach into the outer object is the whole point of an inner class, and it is exactly why you must build it as acc.new Statement(): the statement needs to know which account it belongs to.
outer.new Inner() syntax. Showing a fake step-through would be dishonest, so these are honest read-alongs with the output Java actually produces, traced by hand. The recap snippets at the end of this session use only plain top-level classes, so those you can run.static. If the nested class never touches the outer object's instance fields, make it a static nested class; it is simpler and has no hidden link to carry. Drop the staticonly when the helper genuinely needs to reach into one outer object's state.There is a third shape worth meeting in passing, because you will see it in real code long before you write much of it yourself. Sometimes you need a class exactly once, in exactly one place, and giving it a name and its own block feels like ceremony. Java lets you define and build such a class in a single expression, with no name at all. That is an anonymous class.
The typical use is a callback: a small piece of behavior you hand to some other code so it can call you back later. Think “when the button is clicked, run this”. The “this” is a tiny one-off object whose only job is to carry that one method. Writing a whole named class for it would bury the intent. An anonymous class puts the behavior right where it is used.
public class Main { public static void main(String[] args) { // An anonymous class: we define a one-off subclass of Greeter AND build // an object of it, all in this single expression. It has no name. Greeter g = new Greeter() { void greet() { System.out.println("Hello from a class with no name"); } }; g.greet(); }} class Greeter { void greet() { System.out.println("a plain greeting"); }}Hello from a class with no name
Read the shape slowly. new Greeter() { ... } is not just building a Greeter. The block of braces right after the parentheses is a brand new class body, written on the spot, that extends Greeter and overrides greet(). There is no class Something extends Greeter line anywhere, because the class never gets a name. It is born, used once through g, and that is its whole life.
Inner classes are the last new idea of Phase 3. This is a good moment to stop adding and instead connect. Below is the single mental map that ties together everything from the first object you made to the keywords you just learned. Read it as one story, because that is what it is.
Everything starts with one move. Bundle the data that belongs together with the actions that work on it, and describe that bundle once as a class. From the blueprint you build many objects with new, each holding its own state. A class gives objects their behavior through methods, and a constructor makes sure every object is born in a valid state. See why objects and creating classes and objects.
On that foundation stand the four ideas that the whole of object-oriented programming is built from. Each one solves a clear problem.
private, expose controlled methods. Access modifiers and encapsulation.Animal[] can make each animal speak in its own voice. Polymorphism.Encapsulation, inheritance, polymorphism, abstraction. Four ideas, one purpose: to let you build large programs out of small, honest, reusable parts that each know their own job.
Around those four ideas sit the keywords that shape how classes relate and what they promise. They are not new ideas so much as precise tools for expressing the four above.
abstract class or methodinterface with implementsstaticfinalTo prove the map is not just words, here is one small program that uses several of these ideas at once, with plain top-level classes so the interpreter can run it. Press Run and step through. Shape is an abstract base with shared code; Circle and Square are concrete subclasses; one parent-typed array drives polymorphic calls; and a static counter, owned by the class rather than any object, tallies how many shapes were made.
public class Main { public static void main(String[] args) { Shape[] shapes = new Shape[2]; shapes[0] = new Circle(2.0); shapes[1] = new Square(3.0); int i = 0; while (i < shapes.length) { shapes[i].describe(); i = i + 1; } System.out.println("shapes made: " + Shape.count); }} abstract class Shape { static int count = 0; String name; Shape(String name) { this.name = name; count = count + 1; } abstract double area(); void describe() { System.out.println(name + " has area " + area()); }} class Circle extends Shape { double radius; Circle(double r) { super("Circle"); radius = r; } double area() { return 3.14 * radius * radius; }} class Square extends Shape { double side; Square(double s) { super("Square"); side = s; } double area() { return side * side; }}Circle has area 12.56 Square has area 9.0 shapes made: 2
Trace what prints. The first shape is a Circle of radius 2.0, so its area is 3.14 * 2.0 * 2.0 which is 12.56: the line reads Circle has area 12.56. The second is a Square of side 3.0, area 9.0: Square has area 9.0. Two shapes were constructed, so the shared static counter reads 2: shapes made: 2. In one short program you can see encapsulated fields, an abstract base with a forced contract, inheritance, polymorphic dispatch through an array, and a static member that belongs to the class. That is Phases 2 and 3 working together.
if checks on type led to polymorphism. Each keyword exists to solve a pain you can feel. When you remember the pain, the tool comes back on its own.Try each one yourself first, then open the answer.
static nested class, and when for an inner class?BankAccount example, print() says owner and balance with no dot in front. Whose fields are those, and how can the inner class see them?shapes made: print 2 rather than being stored separately on each shape? Tie your answer to a keyword.Take these away. They continue exactly what we just did.
Triangle, with fields base and height and an area() of 0.5 * base * height. Add it to the array, run the program, and confirm both its area line and the final shapes made: count change as you expect.Playlist class that contains a nested Song type holding a title and a length in seconds. Decide whether Song should be a static nested class or an inner class, and write one or two sentences justifying the choice. You do not need to run it.new Something() { ... } shape into your notes and label, in your own words, which method it overrides and what behavior it supplies. You are reading, not running.