Java language allows defining classes inside classes just like defining other variables. A class defined within another class is called an inner class or nested class. Inner classes follow declaration rules for Java classes. The access rules that apply to Java variables apply to inner classes as well. The class that contains the inner class is called an outer class. Just like Java variables, inner classes can be static or non-static, or local to a method. Just like a class’ methods, an inner class has access to variables and methods defined in its outer class.
Using inner classes brings a little more complexity but it has its own benefits. Inner classes enhance encapsulation by making it easier to put sensitive code in a separate class and hiding it from the outside world as a whole. In addition, putting logically similar code in a separate inner class makes the code more readable overall.
At the class level, just like Java variables, inner classes can be grouped into two categories: static and non-static. In addition to these, based on how they are declared, inner classes can be local or anonymous.
Non-static Inner Classes
Non-static inner classes are defined without the static keyword, within an outer class just like a regular Java class. They follow the same access rules as a class’ instance variables. Non-static inner classes can have instance variables and methods but they cannot have static variables or static methods.
Let’s define a Java class and a non-static inner class within this class as the outer class.
public class OuterClass {
public class InnerClass {
public void printHello() {
System.out.println(“Hello”);
}
}
}
Here, the InnerClass is defined within OuterClass. Therefore, InnerClass is the inner class and OuterClass is the outer class of InnerClass.
Initializing a non-static inner class
An non-static inner class can be declared only using the outer class’ name and the . operator. The initialization of a non-static inner class can take place only through an instance of its parent class. Therefore, the outer class needs to be initialized before a non-static inner class can be initialized. The standard way to initialize a non-static inner class is as follows:
<Outer Class Name>.<Static Inner Class Name> <variable name> = new <Outer Class Name>.new <Static Inner Class Name>();
Let’s initialize the inner class we defined in the previous example.
public class OuterClass {
public class InnerClass {
public void printHello() {
System.out.println(“Hello”);
}
}
public static void main (String [] args) {
OuterClass outer = new OuterClass();
OuterClass.InnerClass innerClass = outer.new InnerClass();
inner.printHello();
}
}
Inside the main method, first an instance of OuterClass is initialized. There’s nothing specific about this operation as OuterClass is not an inner class. Following this, we declare an object reference of type InnerClass as follows:
OuterClass.InnerClass innerClass
In the declaration, the class name is not directly used but it is used with the outer class name. On the other hand, the initialization takes place using the new operator but not directly. It is done through the outer class instance that was defined before.
outer.new InnerClass();
Here in this example, we defined the outer class instance before initializing the inner class. It is possible to put all of them together in one statement.
OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
When compiled and run, the output would be
Hello
Accessing outer class fields and methods
Just like the components of a class, inner classes have full access to the outer class’ fields and methods.
Now, let’s add new fields and methods to our previous example.
public class OuterClass {
public String name;
public static String HELLO_WORLD = “Hello World”;
public OuterClass(String name) {
this.name = name;
}
public void say Hello() {
System.out.println(“Hello”);
}
private class InnerClass {
public void sayHello() {
System.out.println(“Hello “ + name);
}
public void printHelloWorld() {
System.out.println(HELLO_WORLD);
}
}
public static void main(String [] args) {
OuterClass.InnerClass innerClass = new OuterClass(“James West”).new InnerClass();
innerClass.sayHello();
innerClass.printHelloWorld();
}
}
The variables and methods defined in the outer class are directly accessible to inner class. Compiling and running OuterClass would give the output
Hello
Hello James West
Hello World
Inner Class Shadowing
If an inner class declares a variable already declared in the outer class with the same name and type, the variable declared in the outer class can no longer be accessed within the inner class without using the outer class name. In this case, inside the inner class, the variable name alone refers to the one declared in the inner class. In Java, this concept is known as shadowing and the outer class variable is said to be shadowed by the inner class variable. The variable declared in the outer class can be accessed only by using the outer class name followed by the .this. Operator.
public class OuterClass {
public String hello = “Hello from the outer class”;
private class InnerClass {
public String hello = “Hello from the inner class”;
public void printInnerHello() {
System.out.println(name);
}
public void printOuterHello() {
System.out.println(OuterClass.this.hello);
}
}
public static void main(String [] args) {
OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
System.out.println(“Calling the inner class variable”);
innerClass.printInnerHello();
System.out.println(“Calling the outer class variable”);
innerClass.printOuterHello();
}
}
The output would be
Hello from the inner class
Hello from the outer class
Using this with inner classes
When used within the inner class, this keyword refers to the inner class reference. To access the outer class object, this keyword is used as follows:
<Outer Class Name>.this
We can change our previous example to demonstrate how this keyword is used with inner classes as follows:
public class OuterClass {
public String hello = “Hello from the outer class”;
private class InnerClass {
public String hello = “Hello from the inner class”;
public void printInnerHello() {
System.out.println(this.name);
}
public void printOuterHello() {
System.out.println(OuterClass.this.hello);
}
}
public static void main(String [] args) {
OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
System.out.println(“Calling the inner class variable”);
innerClass.printInnerHello();
System.out.println(“Calling the outer class variable”);
innerClass.printOuterHello();
}
}
Static inner classes
A static inner class is defined the same way as the non-static inner classes but with the static keyword preceding the class name. Because it is declared static, a static inner class belongs to all instances of the outer class it is defined within. In addition, because of the same reason, they can have static methods or variables, they can access static methods and variables of the outer class but they do not have access to instance methods and variables of the outer class.
A static inner class is defined as follows:
public class MyOuterClass {
public static MyInnerClass {
}
}
Initializing a static inner class
Static inner classes are initialized not through an instance of the outer class but using the outer class name. The standard way to initialize a static inner class is as follows:
<Outer Class Name>.<Static Inner Class Name> <variable name> = new <Outer Class Name>.<Static Inner Class Name>();
public class MyOuterClass {
private static String outerHello = “Hello from outer class”;
private String name = “James West”;
private static class MyInnerClass {
private static String innerHello = “Hello from inner class”;
private int x = 5;
public void printHello() {
System.out.println(outerHello);
System.out.println(innerHello);
System.out.println(x);
//This would cause a compiler error
//System.out.println(name);
}
}
public static void main(String [] args) {
}
When compiled and run, the output would be
Hello from outer class
Hello from inner class
5
The inner class MyInnerClass defines two fields: a static string and a non-static integer. The non-static method printHello() defined in the inner class can access the private static String defined in the outer class but the instance variable name that is declared in the outer class is not accessible to static class MyInnerClass.
Local Inner Classes
Just like local variables, inner classes can be declared inside a method. In such cases, the inner class is accessible only within the method itself.
public class MyOuterClass {
public void sayHello(String firstName, String lastName) {
public class MyLocalInnerClass {
private String firstName;
private String lastName;
public MyInnerClass(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getName() {
return firstName + “ “ + lastName;
}
}
MyLocalInnerClass inner = new MyLocalInnerClass(“James”, “West”);
System.out.println(“Hello “ + inner.getName());
}
public static void main(String [] args) {
MyOuterClass outer = new MyOuterClass();
outer.sayHello();
}
}
Compiling and running MyOuterClass would give he following output.
Hello James West
Here, we define an inner class, MyLocalInnerClass, inside the sayHello() method of MyOuterClass. MyInnerClass’ getName method concatenates firstName and lastName fields and returns the concatenated string as the name of the person. sayHello() method initializes the inner class defined within the method itself, and then calls its getName() method to get the full name. Then this full name is used while saying hello.
In this example, MyLocalInnerClass is accessible only within the sayHello() method.
Anonymous Inner Classes
An anonymous inner class does not have a class name; it has just the class body. Such classes cannot be used alone. They are used as subclasses or implementations for overriding superclass or interface methods.
An anonymous class is initialized from an interface as follows:
<Interface name> <variable name> = new <Interface name>() {
<method implementations>
};
An anonymous class is initialized from a class as follows (the class can be abstract or not):
<Class name> <variable name> = new <Class name>(<constructor args>) {
<overridden methods>
};
Now, let’s define an interface and see how anonymous methods are used with interfaces.
public interface MyInterface {
public void sayHello();
}
Now, let’s implement this interface with an anonymous inner class.
public class MyClass {
public static void main(String [] args) {
MyInterface myInterface = new MyInterface() {
public void sayHello() {
System.out.println(“Hello”);
}
}
}
}
The output would be
Hello.
Here, inside the main method, we provide an implementation for MyInterface without declaring an implementing class and the implements keyword. When the anonymous class is created from an interface, the new keyword is used with the interface name.
Anonymous inner classes can also be used to create subclasses to override parent class methods without declaring a subclass or using the extends keyword.
public class MySuperClass {
public void sayHello() {
System.out.println(“Hello”);
}
public static void main(String [] args) {
MySuperClass mySuperClass = new MySuperClass() {
public void sayHello() {
System.out.println(“Hello everyone”);
}
}
mySuperClass.sayHello();
}
}
The output would be
Hello everyone
Here, in this example, sayHello() method of MySuperClass is overridden during initialization by an anonymous subclass. As a result of this, when sayHello() method is called through the new instance, the overridden version is invoked.
If an anonymous class is initialized from an interface or abstract class, it is regarded as a concrete implementation and is required to provide the implementations for all the abstract methods.
While accessing outside variables, anonymous inner classes follow the following rules:
1 – If an anonymous inner class is defined as a static variable, it can access only other static variables and methods.
2 – If an anonymous inner class is defined as an instance variable, it can access all the other methods and variables.
3 – If an anonymous inner class is defined inside an instance method, it can access all the other methods and instance variables of the outer class.
4 – If an anonymous inner class is defined inside a static method, it can access only static methods and variables defined in the outer class.
5 – An anonymous inner class that is defined inside a method can access only final variables defined in the same method, whether the method is an instance method or a static method.
Now, let’s change our anonymous class initialized from interface a little bit to see how an anonymous class can access local variables. For that, let’s add a final variable to the main method.
public class MyClass {
public static void main(String [] args) {
final String fullName = “James West”
MyInterface myInterface = new MyInterface() {
public void sayHello() {
System.out.println(“Hello ” + fullName);
}
}
}
}
The output would be
Hello James West
In this example, local variable fullName can be accessed by the inner class since it is final. Trying to access a non-final variable from the inner class would cause a compiler error.