In Java language, there is a possibility to define one class inside the body of another These kinds of classes are called nested classes or inner classes. Here is the simplest example:
public class Oyster {
public class Pearl {
}
}
There are various types of nested classes, and in this article, we are going to review them in detail.
Static nested classes
Similar to static fields and methods, a static nested class belongs to the enclosing class and not to an instance of the class. Also, it does not have access to the instance variables and the methods of the outer class. But it can access static variables and methods of the outer class.
The static nested class can be declared with all types of access modifiers.
A static nested java class is nested for only packaging convenience.
Here is an example:
public class Outer {
private int a = 1;
protected static int b = 1;
private static int c = 1;
static int d = 1;
public static class StaticNested {
public static void test1() {
// System.out.println(“Outer class field ” + a); // NOT COMPLILED
System.out.println(“Outer class field “ + new Outer().a);
System.out.println(“Outer class field “ + b);
System.out.println(“Outer class field “ + c);
System.out.println(“Outer class field “ + d);
}
public void test2() {
System.out.println(“Non-static method”);
}
}
}
As you can see, the outer class field without modifier static cannot be called inside the nested static class. But after the creation of an instance of Outer class, we can use any variable of Outer class, even with private modified. In this example also shown that the nested static class can access all static fields of the outer class.
Let’s try to call a method from our static nested class:
public static void main(String[] args) {
//call of static method of static nested class
Outer.StaticNested.test1();
//call of non-static method of static nested class
new Outer.StaticNested().test2();
}
Non-static nested classes
Non-static nested classes are also called inner. They can have any access modifier. The inner class belongs to an instance of the enclosing class, the same as its other members(fields and methods). Non-static nested classes have access to all static and non-static members of the enclosing class.
Let’s look at the example of an inner class:
public class Outer {
private int a = 1;
protected int b = 1;
int c = 1;
private static int d = 1;
public class Inner {
private int innerField = 1;
// private static int staticInnerField = 1;// NOT COMPLILED
public void test1() {
System.out.println(“Outer class field “ + a);
System.out.println(“Outer class field “ + b);
System.out.println(“Outer class field “ + c);
System.out.println(“Outer class field “ + d);
System.out.println(“Inner class field “ + innerField);
}
// public static void test2() {} // NOT COMPLILED
}
}
You can see that the Inner class can call any member with any modifier of the Outer class. An inner class cannot have any static field or method, but all access modifiers are applicable to Inner class members. Here is how we can call the inner class:
public static void main(String[] args) {
// instantiating outer class
Outer outer = new Outer();
// instantiating inner class
Outer.Inner inner = outer.new Inner();
inner.test1();
}
As you can see, to create an inner class, we must first create its enclosing class.
Local Inner classes
The local inner classes are the special type of inner classes, in which the class is defined inside a method or scope block. Let’s look at different places of local inner classes declarations:
public class Outer {
static {
class Inner1{}
}
{
class Inner2{}
}
public void test() {
class Inner3{
class Inner4{}
}
}
}
All of the above declarations are legal.
The method inner classes are not associated with an Object. Therefore, they cannot have any access modifiers in their declaration. The only allowed modifiers for local inner classes are abstract or final. The local inner classes have access to static and non-static members in the enclosing context. You can read the local variable’s values. But if you try to modify a local variable in method inner class, you will get a compile-time error. Let’s look at one more example:
public class Outer {
private String outerVariable = “outer”;
public void test() {
String localVariable = “local”;
class Inner3{
public void test() {
System.out.println(outerVariable);
System.out.println(localVariable);
outerVariable = “test”;
// localVariable = “test”; //NOT COMPILED
// We are getting the compile time error:Local
// variable localVariable defined in an enclosing
//scope must be final or effectively final
}
}
}
}
Let’s look at the example with an instantiation of a local inner class:
public class Outer {
static {
class Inner1 {}
Inner1 inner = new Inner1();
}
public void test1() {
// Inner1 inner = new Inner1(); //NOT COMPILED,
// because we are out of scope
class Inner2{
public void test() {
System.out.println(“Hello from the local inner class!”);
}
}
Inner2 inner = new Inner2();
inner.test();
}
public void test2() {
// Inner2 inner = new Inner2();//NOT COMPILED,
// because we are out of scope
}
}
Anonymous classes
In Java language, it is possible to create an inner class without a name, such kind of classes are called anonymous. For anonymous classes, definition and instantiation are combined in one single statement.
An anonymous class has to implement an interface or to extend a class. It is not possible to define a constructor for an anonymous class, because it does not have any name. Because of the same reason, we can access anonymous inner classes only at the point where they are defined.
Let’s look at the example. First of all, we need to have a class or an interface:
abstract class AbstractClass {
abstract void doIt();
}
Here you can see our anonymous inner class declaration, instantiation, and call of its method:
public class Test {
public static void main(String[] args) {
String localVariable = “local variable”;
//anonymous inner class declaration and instantiation
AbstractClass anonymousClass = new AbstractClass() {
void doIt() {
System.out.println(“Hello from anonymous inner class!”);
System.out.println(“We can use here “+localVariable);
}
};
anonymousClass.doIt();
}
}
The specific of the anonymous inner classes is that they can implement only one interface or extend only one class at one time. We can group anonymous inner classes into 3 types based on declaration and behavior: an anonymous class that extends a class, an anonymous class that implements an interface, an anonymous class that defines inside method or constructor argument.
As for other inner classes in anonymous inner classes, you can read local variables, but you cannot modify them.
There are a lot of possible use cases of anonymous classes. For example, they are widely used for UI Event Listeners. Here is a code snippet where we add a listener to a button:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
…
}
}
In this example, the instance of the anonymous class that implements interface ActionListener is created here. Now, when you click on the button, the actionPerformed method will be triggered.
Anonymous classes give us the possibility to create a better class hierarchy and to reach a better encapsulation. We usually use anonymous classes in case of modifying the implementation of methods of some classes on the fly. It helps us to avoid adding redundant new *.java files to the project. This is especially good if the class from that added file would be used just one time. Therefore we will get a cleaner project structure. Since Java 8, we can replace some anonymous inner classes with lambda expressions. But we can use lambda expressions just in case of functional interfaces(interface with single abstract method). They implement only abstract functions and implement functional interfaces.
Shadowing
Fields and methods from the enclosing class can be shadowed with fields and methods from the inner class. It can be when inner class members have the same names.
In this situation, if we want to use members of an outer class, we have to use its name. Here is an example:
public class Outer {
private String string1 = “outer1”;
private static String string2 = “outer2”;
public class Inner {
String string1 = “inner1”;
final static String string2 = “inner2”;
public void test() {
// printing values from Inner class
System.out.println(string1);
System.out.println(string2);
// printing values from Outer class
System.out.println(Outer.this.string1);
System.out.println(Outer.string2);
System.out.println(Outer.this.string2);
}
}
}
Let’s call our test() method and look at the result:
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.test();
}
}
After running this application we will get printed this:
inner1
inner2
outer1
outer2
outer2
As you can understand, we were able to call successfully outer class and inner class values, even their names were overlapped.
Serialization of Nested classes
There are some nuances with inner classes related to serialization. We can get
a java.io.NotSerializableException in case of ignoring these rules:
- you must declare the nested class as static.
- you have to make both the nested class and the enclosing class implement Serializable.
7 Responses