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 classIn 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):

  1. They increase encapsulation  Our Bow is only for Cat. You can make a field (method) private, and hide it from other classes, using it only inside the inner class.
  2. 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:

Types of nested classes

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.

Create a free account to access the full topic

“It has all the necessary theory, lots of practice, and projects of different levels. I haven't skipped any of the 3000+ coding exercises.”
Andrei Maftei
Hyperskill Graduate