class OuterClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
These internal classes are called nested.
They are divided into 2 types:
- Non-static nested classes. These are also called inner classes.
- Static nested classes.
- a local class
- an anonymous class
public class Bicycle {
private String model;
private int weight;
public Bicycle(String model, int weight) {
this.model = model;
this.weight = weight;
}
public void start() {
System.out.println("Let's go!");
}
public class Handlebar {
public void right() {
System.out.println("Steer right!");
}
public void left() {
System.out.println("Steer left!");
}
}
public class Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
}
Here we have the Bicycle
class. It has 2 fields and 1 method: start()
.
It differs from an ordinary class in that it contains two classes: Handlebar
and Seat
.
Their code is written inside the Bicycle
class.
These are full-fledged classes: as you can see, each of them has its own methods. At this point, you might have a question: why in the world would we put one class inside another?
Why make them inner classes? Well, suppose we need separate classes for the concepts of handlebar and seat in our program. Of course, it's not necessary for us to make them nested! We can make ordinary classes. For example, like this:
public class Handlebar {
public void right() {
System.out.println("Steer right!");
}
public void left() {
System.out.println("Steer left");
}
}
public class Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
Very good question!
Of course, we aren't limited by the technology.
Doing that is certainly an option. Here, the important thing is more the correct design of the classes from the perspective of a specific program and its purpose.
Inner classes are for separating out an entity that is inextricably connected to another entity.
Handlebars, seats, and pedals are components of a bicycle. Separated from the bicycle, they don't make much sense.
If we made all these concepts separate public classes, we would have had the code like this in our program:
public class Main {
public static void main(String[] args) {
Handlebar handlebar = new Handlebar();
handlebar.right();
}
}
Hmm... The meaning of this code is even difficult to explain.
We have some vague handlebar (Why is it necessary? No idea, to be honest). And this handle turns right... all by itself, without a bicycle... for some reason.
By separating the concept of the handlebar from the concept of the bicycle, we lost some logic in our program.
Using an inner class, the code looks very different:
public class Main {
public static void main(String[] args) {
Bicycle peugeot = new Bicycle("Peugeot", 120);
Bicycle.Handlebar handlebar = peugeot.new Handlebar();
Bicycle.Seat seat = peugeot.new Seat();
seat.up();
peugeot.start();
handlebar.left();
handlebar.right();
}
}
Console output:
Seat up!
Let's go!
Steer left!
Steer right!
Now what we see suddenly makes sense! :)
We created a bicycle object. We created two bicycle "subobjects" — a handlebar and a seat. We raised the seat for comfort and off we went: pedaling and steering as needed! :) The methods we need are called on the appropriate objects. It's all simple and convenient.
In this example, separating out the handlebar and seat enhances encapsulation (we hide data about the bicycle parts inside the relevant class) and lets us create a more detailed abstraction.
Now let's look at a different situation. Suppose we want to create a program that simulates a bike shop and spare parts for bikes.
In this situation, our previous solution won't be work.
At a bike store, each individual bicycle part makes sense even when separated from a bicycle. For example, we'll need methods like "sell pedals to a customer", "buy a new seat", etc.
It would be a mistake to use inner classes here — each individual bicycle part in our new program has meaning that stands on its own: it can be separated from the concept of a bicycle.
This is precisely what you need to pay attention to if you're wondering whether you should use inner classes or organize all the entities as separate classes.
Object-oriented programming is good in that it makes it easy to model real-world entities. This can be your guiding principle when deciding whether to use inner classes. In a real store, spare parts are separate from bicycles — this is okay. This means that it is also okay when designing a program.
Okay, we've figured out the "philosophy" :)
Now let's get acquainted with important "technical" features of inner classes. Here's what you definitely need to remember and understand:
An object of an inner class cannot exist without an object of an outer class.
This makes sense: this is why we made the
Seat
andHandlebar
inner classes in our program — so that we don't end up with orphaned handlebars and seats.This code does not compile:
public static void main(String[] args) { Handlebar handlebar = new Handlebar(); }
Another important feature follows from this:
An object of an inner class has access to the variables of the outer class.
For example, let's add an
int seatPostDiameter
variable (representing the diameter of the seatpost) to ourBicycle
class.Then in the
Seat
inner class, we can create adisplaySeatProperties()
method that displays the seat properties:public class Bicycle { private String model; private int weight; private int seatPostDiameter; public Bicycle(String model, int weight, int seatPostDiameter) { this.model = model; this.weight = weight; this.seatPostDiameter = seatPostDiameter; } public void start() { System.out.println("Let's go!"); } public class Seat { public void up() { System.out.println("Seat up!"); } public void down() { System.out.println("Seat down!"); } public void displaySeatProperties() { System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter); } } }
And now we can display this information in our program:
public class Main { public static void main(String[] args) { Bicycle bicycle = new Bicycle("Peugeot", 120, 40); Bicycle.Seat seat = bicycle.new Seat(); seat.displaySeatProperties(); } }
Console output:
Seat properties: seatpost diameter = 40
Note: the new variable is declared with the most strict access modifier (
private
). And still the inner class has access!An object of an inner class cannot be created in a static method of an outer class.
This is explained by the specific features of how inner classes are organized. An inner class can have constructors with parameters, or just the default constructor. But regardless, when we create an object of an inner class, a reference to the object of the outer class is invisibly passed to the created object of the inner class. After all, the presence of such an object reference is an absolute requirement. Otherwise, we won't be able to create objects of the inner class.
But if a method of the outer class is static, then we might not have an object of the outer class! And this would be a violation of the logic of how an inner class works. In this situation, the compiler will generate an error:
public static Seat createSeat() { // Bicycle.this cannot be referenced from a static context return new Seat(); }
An inner class cannot contain static variables and methods.
The logic is the same: static methods and variables can exist and be called or referenced even in the absence of an object.
But without an object of the outer class, we won't have access to the inner class.
A clear contradiction! This is why static variables and methods are not allowed in inner classes.
The compiler will generate an error if you try to create them:
public class Bicycle { private int weight; public class Seat { // An inner class cannot have static declarations public static void displaySeatProperties() { System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter); } } }
When creating an object of an inner class, its access modifier is important.
An inner class can be marked with the standard access modifiers:
public
,private
,protected
, andpackage private
.Why does this matter?
This affects where we can create instances of the inner class in our program.
If our
Seat
class is declared aspublic
, we can createSeat
objects in any other class. The only requirement is that an object of the outer class must also exist.By the way, we already did this here:
public class Main { public static void main(String[] args) { Bicycle peugeot = new Bicycle("Peugeot", 120); Bicycle.Handlebar handlebar = peugeot.new Handlebar(); Bicycle.Seat seat = peugeot.new Seat(); seat.up(); peugeot.start(); handlebar.left(); handlebar.right(); } }
We easily gained access to the
Handlebar
inner class from theMain
class.If we declare the inner class as
private
, we will be able to create objects only inside the outer class.We can no longer create a
Seat
object "on the outside":private class Seat { // Methods } public class Main { public static void main(String[] args) { Bicycle bicycle = new Bicycle("Peugeot", 120, 40); // Bicycle.Seat has private access in Bicycle Bicycle.Seat seat = bicycle.new Seat(); } }
You probably already understand the logic :)
Access modifiers for inner classes work the same as for ordinary variables.
The
protected
modifier provides access to an instance variable in subclasses and classes that are in the same package.protected
also works for inner classes. We can createprotected
objects of the inner class:- in the outer class;
- in its subclasses;
- in classes that are in the same package.
If the inner class does not have an access modifier (
package private
), objects of the inner class can be created:- in the outer class;
- in classes that are in the same package.
You have been familiar with modifiers for a long time, so no problems here.
GO TO FULL VERSION