Java Inner Classes
If you would like to create your own superhero, what would you do? Of course, you would open a favorite development environment and create a Superhero
class!
To be a proper superhero, our character will need a set of special items, including, for example, a magic cloak or a hammer. Here we may have a problem. What is the best way to organize the classes describing the equipment? And how can we mark that only one class, that is Superhero
, can use them?
Here appears our savior — an instrument called nested classes. They help us group classes logically and increase the encapsulation of our code.
What is a nested class?
Basically, you can call a class nested when it is declared inside another class.
This is what our superhero would look like:
class Superhero {
class MagicCloak {
}
class Hammer {
}
}
Both classes MagicCloak
and Hammer
are nested classes. The Superhero
class is often called an outer class, and a nested class is called a member of an outer class.
In this topic, we are going to talk about non-static nested classes, which are commonly known as inner classes.
Inner class
Let's move on to another example. Imagine that you are writing a Cat
class representing cats. The Cat
class may have a lot of fields and methods, but we may also use inner class structures. For example, let's say you want a cat to have a bow. Then, you need to create a new Bow
class. This class would be quite small and specific, and you know you won't need a bow without a cat. The solution is to create the Bow
class inside the class Cat
:
public class Cat {
private String name;
public Cat(String name) {
this.name = name;
}
public class Bow {
private String color;
public Bow(String color) {
this.color = color;
}
public void printColor() {
System.out.println("Cat " + Cat.this.name + " has a " + this.color + " bow.");
}
}
}
Let's create a cat Bob
with a red bow:
public class Main {
public static void main(String[] args) {
Cat cat = new Cat("Bob");
Cat.Bow bow = cat.new Bow("red");
bow.printColor();
}
}
Look, we have created an instance of Cat
and then created an instance of Bow
using quite an interesting syntax.
Here, the output will be:
Cat Bob has a red bow.
Remember that to use inner classes, we must create an instance of the outer class. In our example, we created a Cat
.
Scope of the inner class
Now let's discuss what we can see from the inner class and who can access the inner class from outside.
Here is our Cat
class with a new method — sayMeow
, and an inner Bow
class with a new method — putOnABow
.
public class Cat {
private String name;
public Cat(String name) {
this.name = name;
}
private void sayMeow() {
System.out.println(this.name + " says: \"Meow\".");
}
public class Bow {
String color;
public Bow(String color) {
this.color = color;
}
public void putOnABow() {
Cat.this.sayMeow();
System.out.println("Bow is on!");
}
public void printColor() {
System.out.println("Cat " + Cat.this.name + " has a " + this.color + " bow.\n");
}
}
}
Note, that inside the putOnABow
method of the Bow
class, we have access to the private sayMeow
method of the Cat
class. And, as you can see, in the sayMeow
method, we print the private field name
of the Cat
class and everything works fine.
Also, in the printColor
method, we have direct access to the private field name
of the Cat
class.
How about creating a cat Princess with a golden bow to prove that our code works?
Cat cat = new Cat("Princess");
Cat.Bow bow = cat.new Bow("golden");
bow.printColor();
bow.putOnABow();
And, yes, the bow is on!
Cat Princess has a golden bow.
Princess says: "Meow".
Bow is on!
As for the access from the outside world, remember that when you've instantiated an inner class, you can do whatever you want according to access modifiers.
Now let's collect all the rules together and put them to a "hard disk"!
Rules for Inner classes
From inside the inner class, we can see all methods and fields of the outer class even if they are private
. And don't forget that we can use everything else according to access modifiers as well.
An inner class is associated with an instance of its enclosing class. So to instantiate an inner class and get access to it, you need to instantiate the outer class first:
Outer outer = new Outer();
Outer.InnerClass inner = outer.new InnerClass();
With regards to access modifiers: if you make an inner class private
, then it can only be accessed from inside the outer class. The same works for fields and methods.
And be careful — there are some restrictions!
Prior to Java 16, inside an inner class, you could not define:
- any static members;
- enums;
- an Interface.
Reasons to use Inner Classes
Have you noticed what our two examples of Superhero
with the magic items and Cat
with a bow have in common? Sure you have — we hid our inner classes from the outside world so that only a Superhero may use a magic cloak and only a cat may put on a bow. Now, it will be easier to navigate between classes and to understand the structure of your code.
At last, the formal list of inner class benefits (just a few):
- They increase encapsulation — Our
Bow
is only forCat
. You can make a field (method)private
, and hide it from other classes, using it only inside the inner class. - Inner classes will organize your code and make your packages more logical so all the magic equipment for
Superhero
will be in one place.
You can create a class within another class, and such classes are called nested. A non-static nested class is called an inner class. The main idea of Inner classes is to hide some code from other classes and increase encapsulation.
Types of nested classes
Let’s look at the whole hierarchy of nested classes. There are four types of nested classes, highlighted in blue in the following picture:
First, all nested classes are divided into static class and non-static class (non-static inner classes). As you can see, only one type has the static
keyword.
In the Java documentation, you also may see that the non-static group includes local inner classes and anonymous classes along with the inner classes you know about.
Let's now take a closer look at static nested classes and local inner classes.
Static nested class
Imagine, that one day you woke up and decided to draw. Obviously, you would do so with the help of some Java code. Your ultimate goal is to draw a painting. But how can you create a masterpiece? For that, you'll have to make some sketches first.
How would you organize your code? It's a good idea to use a nested class here. But if you choose to employ an inner class then a Sketch
would only exist if the Painting
was instantiated previously. So, we'd prefer something different. And the static nested class is going to help us.
It allows us to create a Sketch
first, and then, only if you're ready to become an author of a masterpiece, you can create a Painting
.
public class Painting {
private String name;
public static class Sketch {
private int id;
public Sketch(int id) {
this.id = id;
}
public void drawSketch() {
drawForest();
drawBear();
}
private void drawForest() {
System.out.println("Forest was drawn in a sketch!");
}
}
private void drawBear() {
System.out.println("Bear was drawn in a sketch!");
}
}
}
Let's try it:
public class Main {
public static void main(String[] args) {
Painting.Sketch sketch = new Painting.Sketch(0);
sketch.drawSketch();
}
}
Greetings to our bear in a forest!
Forest was drawn in a sketch!
Bear was drawn in a sketch!
Scope of a static nested class
Let's modify our example a little bit with Sketch
and Painting
, and talk about their scope.
public class Painting {
private String name;
private static double length;
private static double width;
public static void setLength(double length) {
Painting.length = length;
}
public static void setWidth(double width) {
Painting.width = width;
}
public static class Sketch {
private int id;
public Sketch(int id) {
this.id = id;
}
public void drawSketch() {
drawForest();
drawBear();
}
private void drawForest() {
if (Painting.length > 5 && Painting.width > 3) {
System.out.println("Big forest was drawn in a sketch!");
} else {
System.out.println("Small forest was drawn in a sketch!");
}
}
private void drawBear() {
System.out.println("Bear was drawn in a sketch!");
}
}
}
We've added two static
fields to the Painting
class, namely length
and width
. And we've also added a condition to the drawForest
method of the Sketch
class.
With setters, we decide what sizes our Painting
will be and then use that information inside the drawForest
method.
public class Main {
public static void main(String[] args) {
Painting.setLength(10);
Painting.setWidth(7);
Painting.Sketch sketch = new Painting.Sketch(1);
sketch.drawSketch();
}
}
And here is a big forest with a bear:
Big forest was drawn in a sketch!
Bear was drawn in a sketch!
So, we've got access to private static
fields from a static nested class!
And is there anything that we can't see? Yes, instance variables and methods of an outer class, including the name
field in our example.
From the outside everything works as usual: we create an instance of a static nested class and good luck! Just mind the syntax:
OuterClass.NestedClass nested = new OuterClass.NestedClass();
With regards to access modifiers: if you make a static nested class private
, then it can only be accessed inside the outer class. The same works with fields and methods.
Local inner class
In real life, you won't face local inner classes often, but it is worth knowing how to use them if you want to be a proper programmer.
You can define a local inner class inside any block. But usually, local inner classes are defined inside a method body.
Let's move to an example now:
public class Outer {
private int number = 10;
void someMethod() {
class LocalInner {
private void print() {
System.out.println("number = " + Outer.this.number);
}
}
LocalInner inner = new LocalInner();
inner.print();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.someMethod();
}
}
Here we have an outer class named Outer
and a method named someMethod
in it. We define our local inner class inside someMethod
and we also create an instance of LocalInner
there.
Have you noticed that our LocalInner
class doesn't have an access modifier? And it can't!
There are other restrictions. Inside a local inner class you cannot define any static members, enum types, or interfaces.
Scope of a Local Inner class
The scope of the local inner class is restricted to its containing block, which is someMethod
in our example.
public class Outer {
private int number = 10;
void someMethod() {
final int x = 5;
class Inner {
private void print() {
System.out.println("x = " + x);
System.out.println("number = " + Outer.this.number);
}
}
Inner inner = new Inner();
inner.print();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.someMethod();
}
}
What can we see inside the local inner class? Members of the outer class, including the field — number
. And local variables of the enclosing block, such as someMethod
. Local variables must be declared as final
or be effectively final, the latter means their value is never changed after initialization and there's no need for the final
keyword.
Remember, a local inner class can be instantiated only within the block where the inner class is defined. So other parts of the code don't know that it exists.
Summary
If you want something close to a nested class, but don't want to instantiate an outer class — a static nested class is always there for you!
In Java programming language, static nested classes add functionality to an outer class and may be used for different purposes: usually, as a special structure that is connected with the outer class.
And have you heard about a class in a block? It is called a local inner class and its scope is restricted within a block.
Finally, nested classes organize code , increase encapsulation, since you can hide some code in a nested class, and the last point is that small classes may provide more readable and maintainable code.