In the world of Java programming, the concept of classes is central to the object-oriented paradigm. But did you know that classes can be nested within other classes? This unique feature is known as inner classes, and it opens up a whole new realm of possibilities in terms of code organization, encapsulation, and design patterns. In this blog post, we’ll delve into the fascinating world of inner classes, exploring their types, use cases, and benefits.
Introduction to Inner Classes
Sometimes, we can put a class inside another class. These are called “inner classes.” They were introduced in Java version 1.1 to fix problems with how events are handled in graphical interfaces. But because inner classes have useful features, programmers began using them in regular coding too.
We use inner classes when one type of object can’t exist without another type. For example, a university has departments. If there’s no university, there are no departments. So, we put the department class inside the university class.
class University { // Outer class
class Department { // Inner class
}
}
Similarly, a car needs an engine to exist. Since an engine can’t exist on its own without a car, we put the engine class inside the car class.
class Car { // Outer class
class Engine { // Inner class
}
}
Also, think of a map that has pairs of keys and values. Each pair is called an entry. Since entries depend on maps, we define an entry interface inside the map interface.
interface Map { // Outer interface
interface Entry { // Inner interface
}
}
Remember, inner classes can’t exist without the outer class. This relationship is not “is-a,” but more like “has-a” (composition or aggregation).
Types of Inner Classes
There are four types of inner classes based on where they’re declared and their behavior:
Normal or Regular Inner Classes:
- These are named classes declared inside another class without the
static
modifier. - They can access both static and instance members of the outer class.
- Example:
class Outer {
class Inner {
}
}
Method Local Inner Classes:
- These classes are defined within a method.
- They have access only to
final
variables of the method. - They are used to implement a specific functionality within a method.
- Example:
class Outer {
void someMethod() {
class Inner {
}
}
}
Anonymous Inner Classes:
- These classes are defined without a name.
- They are often used for implementing interfaces or extending classes on-the-fly.
- Example:
class Outer {
interface MyInterface {
void doSomething();
}
MyInterface obj = new MyInterface() {
public void doSomething() {
}
};
}
Static Nested Classes:
- These are declared as
static
inside another class. - They can access only static members of the outer class.
- Example:
class Outer {
static class Nested {
}
}
Remember:
- Normal inner classes can access both static and instance members of the outer class.
- Method local inner classes are declared inside methods and can only access
final
variables. - Anonymous inner classes are often used for implementing interfaces or extending classes without creating separate files.
- Static nested classes are like regular classes but are defined within another class and can access only static members of the outer class.
When working with inner classes
Normal or Regular Inner Classes:
- These are named classes declared within another class without the
static
keyword. - Compiling the below example generates two
.class
files:Outer.class
andOuter$Inner.class
. - Example:
class Outer {
class Inner {
}
}
Running Inner Classes:
- You can’t directly run an inner class from the command prompt unless it has a
main
method. - Attempting to run
java Outer
orjava Outer$Inner
without amain
method leads to “NoSuchMethodError:main”.
Main Method Inside Outer Class:
- By adding a
main
method in the outer class, you can run it. - Now, if we run below code
java Outer
will produce “Outer class main method”. - Example:
class Outer {
class Inner {
}
public static void main(String[] args) {
System.out.println("Outer class main method");
}
}
Static Members in Inner Classes:
- Inner classes can’t include
static
members, such asmain
methods. - Trying to place a
main
method inside an inner class results in a compile error: “Inner classes cannot have static declarations”.
In short, normal inner classes are named classes within another class, they can’t have static members, and their ability to be run directly depends on the presence of a
main
method.
Accessing Inner class code
Case 1: Accessing Inner Class Code from Static Area of Outer Class
class Outer {
class Inner {
public void m1() {
System.out.println("Inner class method");
}
}
public static void main(String[] args) {
Outer o = new Outer();
Outer.Inner i = o.new Inner();
i.m1();
// Alternatively:
// 1. Outer.Inner i = new Outer().new Inner();
// 2. new Outer().new Inner().m1();
}
}
In this code:
- The
Outer
class contains an inner class namedInner
. - Inside the
main
method, we create an instance ofOuter
calledo
. - We then create an instance of the inner class using
o.new Inner()
, and call them1()
method on it. - The two alternative ways to create the inner class instance are shown as comments.
- Running this code will print “Inner class method” to the console.
Case 2: Accessing Inner Class Code from Instance Area of Outer Class
class Outer {
class Inner {
public void m1() {
System.out.println("Inner class method");
}
}
public void m2() {
Inner i = new Inner();
i.m1();
}
public static void main(String[] args) {
Outer o = new Outer();
o.m2();
}
}
In this code:
- The
Outer
class has an inner class namedInner
. - The
m2()
method in theOuter
class creates an instance of the inner class (Inner i = new Inner();
) and calls them1()
method on it. - The
main
method in theOuter
class creates an instance ofOuter
calledo
, and then calls them2()
method on it usingo.m2()
. - Running this code will also print “Inner class method” to the console, just like the previous example.
Case 3: Accessing Inner Class Code from Outside of Outer Class
class Outer {
class Inner {
public void m1() {
System.out.println("Inner class method");
}
}
}
class Test {
public static void main(String[] args) {
Outer o = new Outer();
Outer.Inner i = o.new Inner();
i.m1();
}
}
In this code:
- The
Outer
class contains an inner class namedInner
. - The
Test
class is separate from theOuter
class and has its ownmain
method. - Inside the
main
method of theTest
class, we create an instance ofOuter
calledo
. - We then create an instance of the inner class using
o.new Inner()
, and call them1()
method on it. - Running the
Test
class will also print “Inner class method” to the console.
Case 4: Accessing Inner Class Code
a) From Static Area of Outer Class or Outside of Outer Class:
- Create an instance of the outer class.
- Use that instance to create an instance of the inner class.
- Call methods on the inner class instance.
Outer o = new Outer();
Outer.Inner i = o.new Inner();
i.m1();
b) From Instance Area of Outer Class
- Directly create an instance of the inner class.
- Call methods on the inner class instance.
Inner i = new Inner();
i.m1();
Remember, the approach you choose depends on where you are accessing the inner class from and the context in which you want to use it.
Normal inner class / Regular inner class
1. In a normal or regular inner class, you can access both static and non-static members of the outer class directly. This makes it convenient to use and interact with the outer class’s members from within the inner class.
class Outer {
int x = 10;
static int y = 20;
class Inner {
public void m1() {
System.out.println(x); // Accessing non-static member of outer class
System.out.println(y); // Accessing static member of outer class
}
}
public static void main(String[] args) {
new Outer().new Inner().m1();
}
}
In this code:
- The
Outer
class has an instance variablex
and a static variabley
. - The
Inner
class withinOuter
can directly access bothx
andy
from the outer class. - Inside the
m1()
method ofInner
, the non-static memberx
and the static membery
are both printed. - When you run the
main
method, the output will be:
10
20
This demonstrates how a normal inner class can freely access both static and non-static members of its enclosing outer class.
2. In an inner class, the keyword this
refers to the current instance of the inner class itself. If you want to refer to the instance of the outer class, you can use the syntax OuterClassName.this
. This is particularly useful when there might be naming conflicts or when you explicitly want to access the outer class’s instance.
class Outer {
int x = 10;
class Inner {
int x = 100;
public void m1() {
int x = 1000;
System.out.println(x); // 1000
System.out.println(this.x); // 100 (Inner class's x)
System.out.println(Outer.this.x); // 10 (Outer class's x)
}
}
public static void main(String[] args) {
new Outer().new Inner().m1();
}
}
In this code:
- The
Outer
class contains an instance variablex
with a value of 10. - Inside the
Outer
class, there’s anInner
class with its own instance variablex
set to 100. - The
m1()
method inside theInner
class has a local variablex
set to 1000. - Printing
x
will show the value of the local variable (1000
). - Printing
this.x
inside theInner
class refers to thex
within theInner
class (100
). - Printing
Outer.this.x
refers to thex
within theOuter
class (10
). - When you run the
main
method, the output will be:
1000
100
10
This code demonstrates the different levels of scope and how you can use
this
andOuterClassName.this
to access variables from various contexts within an inner class.
Applicable access modifiers for both outer and inner classes in Java
For outer classes:
- The access modifiers that can be applied are
public
, default (no modifier),final
,abstract
, andstrictfp
.
For inner classes:
- The access modifiers that can be applied are:
private
,protected
, andstatic
.
Nesting of inner classes
Nesting of inner classes is possible, which means you can define one inner class inside another inner class. This creates a hierarchical structure of classes within classes. This is also known as nested inner classes.
Here’s an example to illustrate this concept:
class A {
class B {
class C {
public void m1() {
System.out.println("Innermost class method");
}
}
}
}
class Test {
public static void main(String[] args) {
A a = new A();
A.B b = a.new B();
A.B.C c = b.new C();
c.m1();
}
}
In this example:
- Class
A
has an inner classB
, which itself has an inner classC
. - The
m1()
method in classC
prints “Innermost class method”. - In the
main
method of theTest
class, you create instances step by step:A
->A.B
->A.B.C
. - Then you call the
m1()
method on the instance of classC
. - Running this code will print “Innermost class method” to the console.
The above code effectively demonstrates the concept of nested inner classes and how to work with them.
Let’s see one more example:
class Outer {
int outerVar = 10;
class Inner {
int innerVar = 20;
class NestedInner {
int nestedVar = 30;
public void display() {
System.out.println("NestedVar: " + nestedVar);
System.out.println("InnerVar: " + innerVar);
System.out.println("OuterVar: " + outerVar);
}
}
}
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
Outer.Inner.NestedInner nestedInner = inner.new NestedInner();
nestedInner.display();
}
}
In this example, we have an Outer
class with an Inner
class inside it, and within the Inner
class, there’s a NestedInner
class. You can create instances of each class and access their members accordingly.
When you run the code, it will display:
NestedVar: 30
InnerVar: 20
OuterVar: 10
This shows that nesting of inner classes allows you to organize your code in a structured manner and access members at different levels of nesting.
Method Local Inner Classes
Method local inner classes are inner classes that are defined within a method’s scope. They are only accessible within that specific method and provide a way to encapsulate functionality that is needed only within that method. This type of inner class is particularly useful when you want to confine a class’s scope to a specific method, keeping the code organized and localized.
Main Purpose of Method Local Inner Classes:
- Method local inner classes are intended to define functionality that is specific to a particular method.
- They encapsulate code that is required repeatedly within that method.
- Method local inner classes are well-suited for addressing nested, localized requirements within a method’s scope.
- Method local inner classes can only be accessed within the method where they are defined.
- They have a limited scope and aren’t accessible outside of that method.
- Method local inner classes are the least commonly used type of inner classes.
- They are employed when specific circumstances demand a highly localized class definition.
Example:
class Test {
public void m1() {
class Inner {
public void sum(int x, int y) {
System.out.println("The Sum: " + (x + y));
}
}
Inner i = new Inner();
i.sum(10, 20);
// More code...
i.sum(100, 200);
// More code...
i.sum(1000, 2000);
}
public static void main(String[] args) {
Test t = new Test();
t.m1();
}
}
In this example:
- The
Test
class has a methodm1()
that contains a method local inner class namedInner
. - The
Inner
class has a methodsum()
that calculates and prints the sum of two numbers. - Within
m1()
, you create an instance of theInner
class and call itssum()
method multiple times. - Running the code produces the following output:
The Sum: 30
The Sum: 300
The Sum: 3000
The above code effectively demonstrates how method local inner classes can be used to encapsulate functionality within a specific method’s scope.
We can declare a method-local inner class inside both instance and static methods.
- If we declare an inner class inside an instance method, we can access both static and non-static members of the outer class directly from that method-local inner class.
- On the other hand, If we declare an inner class inside a static method, we can access only static members of the outer class directly from that method-local inner class.
Example
class Test {
int x = 10;
static int y = 20;
public void m1() {
class Inner {
public void m2() {
System.out.println(x); // Accessing instance member of outer class
System.out.println(y); // Accessing static member of outer class
}
}
Inner i = new Inner();
i.m2();
}
public static void main(String[] args) {
Test t = new Test();
t.m1();
}
}
Now, when we run this code, the output will be:
10
20
This demonstrates that method local inner classes can access both instance and static members of the outer class within the context of an instance method.
Now, If we declare the m1()
method as static, you will indeed get a compilation error at line 1 where you’re trying to access the non-static variable x
from a static context. Here’s how the code would look with the error:
class Test {
int x = 10;
static int y = 20;
public static void m1() {
class Inner {
public void m2() {
System.out.println(x); // Compilation error: non-static variable x cannot be referenced from a static context
System.out.println(y);
}
}
Inner i = new Inner();
i.m2();
}
public static void main(String[] args) {
Test.m1();
}
}
In this version of the code, since m1()
is declared as static, it can’t access instance variables like x
directly. The compilation error mentioned in a comment will occur at the line where you’re trying to access x
from the method local inner class’s m2()
method. The y
variable, being static, can still be accessed without an issue.
We will now look at a Very Important Concept in Inner Classes.
From a method-local inner class, we can’t access local variables of the method in which we declare the inner class. However, if the local variable is declared as final
, then we can access it.
class Test {
public void m1() {
final int x = 10; // Declaring a final local variable 'x' with a value of 10
class Inner {
public void m2() {
System.out.println(x); // Accessing the final local variable 'x' within the inner class
}
}
Inner i = new Inner(); // Creating an instance of the inner class
i.m2(); // Calling the method of the inner class to print the value of 'x'
}
public static void main(String[] args) {
Test t = new Test(); // Creating an instance of the outer class
t.m1(); // Calling the method of the outer class
}
}
Explanation:
- In the
m1()
method of theTest
class, a local variablex
is declared and initialized with the value10
. The variablex
is marked asfinal
, indicating that its value cannot be changed after initialization. - Inside the
m1()
method, an inner class namedInner
is defined. This inner class contains a methodm2()
. - The
m2()
method of theInner
class prints the value of the final local variablex
. Sincex
is declared asfinal
, it can be accessed within the inner class. - Back in the
m1()
method, an instance of theInner
class is created usingInner i = new Inner();
. - The
m2()
method of the inner class is called using the instancei
, which prints the value of the final local variablex
. - In the
main
method, an instance of theTest
class is created (Test t = new Test();
). - The
m1()
method of the outer class is called using the instancet
, which triggers the creation of an instance of the inner class and the printing of the value of the final local variablex
.
Output: When you run the code, the output will be
10
This output confirms that the inner class is able to access the final local variable
x.
Now Few Questions: Consider the following code
class Test {
int i = 10;
static int j = 20;
public void m1() {
int k = 30;
final int m = 40;
class Inner {
public void m2() {
// Line 1
}
}
}
}
a) At Line 1, which of the following variables can we access directly? i, j, k, m
Answer → We can access all variables except ‘k’ directly.
b) If we declare m1()
as static, then at Line 1, which variables can we access directly? i, j, k, m
Answer –> We can access only ‘j’ and ‘m’.
c) If we declare m2()
as static, then at Line 1, which variables can we access directly? i, j, k, m
Answer –> We will get a compilation error (CE) because we cannot declare static members inside inner classes.
Note → The only applicable modifiers for method-local inner classes are
final
,abstract
, andstrictfp
. If we try to apply any other modifier, we will get a compilation error (CE).
Anonymous Inner Class
Sometimes, inner classes can be declared without a name. Such inner classes are called ‘anonymous inner classes.’ The main purpose of anonymous inner classes is for instant use, typically for one-time usage.
Anonymous Inner Classes:
- Anonymous inner classes are inner classes declared without a name.
- They are primarily used for instant (one-time) usage.
- Anonymous inner classes can be categorized into three types based on their declaration and behavior.
Types of Anonymous Inner Classes
Based on their declaration and behavior, there are three types of anonymous inner classes:
1. Anonymous Inner Class that Extends a Class
- An anonymous inner class can extend an existing class.
- It provides an implementation for the methods of the superclass or overrides them.
2. Anonymous Inner Class that Implements an Interface
- An anonymous inner class can implement an interface.
- It provides implementations for the methods declared in the interface.
3. Anonymous Inner Class Defined Inside Arguments
- An anonymous inner class can be defined as an argument to a method.
- It’s often used for callbacks or event handling.
Anonymous Inner class that extends a class
class PopCorn {
public void taste() {
System.out.println("salty");
}
}
class Test {
public static void main(String[] args) {
PopCorn p = new PopCorn() {
public void taste() {
System.out.println("spicy");
}
};
p.taste(); // spicy
PopCorn p1 = new PopCorn();
p1.taste(); // salty
PopCorn p2 = new PopCorn() {
public void taste() {
System.out.println("sweet");
}
};
p2.taste(); // sweet
System.out.println(p.getClass().getName()); // Test$1
System.out.println(p1.getClass().getName()); // PopCorn
System.out.println(p2.getClass().getName()); // Test$2
}
}
The generated .class
files are:
PopCorn.class
: Compiled class file for thePopCorn
class.Test.class
: Compiled class file for theTest
class.Test$1.class
: Compiled class file for the first anonymous inner class within theTest
class.Test$2.class
: Compiled class file for the second anonymous inner class within theTest
class.
This is a common naming convention used by Java to generate class files for inner classes.
Analysis:
1. PopCorn p = new PopCorn();
- In this line, you are creating an instance of the
PopCorn
class using its constructor.
2. PopCorn p = new PopCorn() { }
- In this case, you are declaring an anonymous inner class that extends
PopCorn
(anonymously). - You’re creating an object of the anonymous inner class using the
PopCorn
referencep
.
3. PopCorn p = new PopCorn() { public void taste() { … } };
PopCorn p = new PopCorn() {
public void taste() {
System.out.println("Spicy");
}
};
- You’re declaring an anonymous inner class that extends
PopCorn
. - You’re overriding the
taste()
method within this anonymous inner class. - You’re creating an object of this anonymous inner class using the
PopCorn
referencep
.
Different approaches to working with threads
Normal Class Approach:
// Example using a normal class that extends Thread
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Child Thread");
}
}
}
class ThreadDemo {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("Main Thread");
}
}
}
Anonymous Inner Class Approach:
// Example using an anonymous inner class extending Thread
class ThreadDemo {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Child Thread");
}
}
};
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("Main Thread");
}
}
}
Anonymous Inner class that implements an Interface
Normal Class Approach:
class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Child Thread");
}
}
}
class ThreadDemo {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r); // where r is target runnable
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("Main Thread");
}
}
}
Anonymous Inner Class Implementing an Interface:
Note -> Defining a thread by implementing a runnable interface
// Example using an anonymous inner class implementing Runnable interface
class ThreadDemo {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Child Thread");
}
}
};
Thread t = new Thread(r);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("Main Thread");
}
}
}
Anonymous Inner class that defines inside arguments
// Example using an anonymous inner class inside arguments
class ThreadDemo {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Child Thread");
}
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println("Main Thread");
}
}
}
All the above code examples effectively illustrate the different ways to work with threads using both normal classes and anonymous inner classes
Normal Java Class Vs Anonymous Inner Class
The differences between normal Java classes and anonymous inner classes when it comes to extending classes, implementing interfaces, and defining constructors.
Extending a Class:
- A normal Java class can extend only one class at a time.
- An anonymous inner class can also extend only one class at a time.
Implementing Interfaces:
- A normal Java class can implement any number of interfaces simultaneously.
- An anonymous inner class can implement only one interface at a time.
Combining Extension and Interface Implementation:
- A normal Java class can extend a class and implement any number of interfaces simultaneously.
- An anonymous inner class can either extend a class or implement an interface, but not both simultaneously.
Constructors:
- A normal Java class can have multiple constructors.
- Anonymous inner classes cannot have explicitly defined constructors, primarily because they don’t have a specific name. The name of the class and the constructor must match, which is not feasible for anonymous classes.
Note: If the requirement is standard and required several times, then we should go for a normal top-level class. If the requirement is temporary and required only once (for instant use), then we should go for an anonymous inner class.
Where exactly Anonymous inner classes are used?
We can use anonymous inner classes frequently in GUI-based applications to implement event handling.
Anonymous inner classes are often used in GUI-based applications to implement event handling. Event handling in GUI applications involves responding to user interactions such as button clicks, mouse movements, and keyboard inputs. Anonymous inner classes provide a concise way to define event listeners and handlers directly inline within the code, making the code more readable and reducing the need for separate classes for each event.
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MyGUIFrame extends JFrame {
private JButton b1, b2, b3;
public MyGUIFrame() {
// Initialize components
b1 = new JButton("Button 1");
b2 = new JButton("Button 2");
b3 = new JButton("Button 3");
// Add buttons to the frame
add(b1);
add(b2);
add(b3);
// Attach anonymous action listeners to buttons
b1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Button 1 specific functionality
JOptionPane.showMessageDialog(MyGUIFrame.this, "Button 1 clicked!");
}
});
b2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Button 2 specific functionality
JOptionPane.showMessageDialog(MyGUIFrame.this, "Button 2 clicked!");
}
});
b3.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Button 3 specific functionality
JOptionPane.showMessageDialog(MyGUIFrame.this, "Button 3 clicked!");
}
});
// Set layout and size
setLayout(new FlowLayout());
setSize(300, 150);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new MyGUIFrame());
}
}
In this example, an ActionListener
is implemented as an anonymous inner class for each button (b1
, b2
, b3
) to handle their click events. The JOptionPane
is used to show a message dialog when each button is clicked. The SwingUtilities.invokeLater()
is used to ensure the GUI is created on the Event Dispatch Thread.
Remember to import the necessary classes (
JFrame
,JButton
,ActionEvent
,ActionListener
,JOptionPane
,SwingUtilities
, etc.) from the appropriate packages.
Static Nested Classes
Sometimes, we can declare an inner class with the static modifier. Such types of inner classes are called static nested classes.
In the case of a normal or regular inner class, without an existing outer class object, there is no chance of an existing inner class object. That is, the inner class object is strongly associated with the outer class object.
However, in the case of static nested classes, without an existing outer class object, there may be a chance of an existing nested class object. Hence, a static nested class object is not strongly associated with the outer class object.
class Outer
{
static class Nested
{
public void m1()
{
System.out.println("Static nested class method");
}
}
public static void main(String[] args)
{
Nested n = new Nested();
n.m1();
}
}
If you want to create a nested class object from outside of the outer class, you can do so as follows:
Outer.Nested n = new Outer.Nested();
Static Method Declaration
In normal or regular inner classes, we can’t declare static members. However, in static nested classes, we can declare static members, including the main method. This allows us to invoke a static nested class directly from the command prompt.
class Test
{
static class Nested
{
public static void main(String[] args)
{
System.out.println("Static nested class main method");
}
}
public static void main(String[] args)
{
System.out.println("Outer class main method");
}
}
Explanation:
- The main method of the outer class (
Test
) will be invoked when you executejava Test
. - The main method of the static nested class (
Nested
) will be invoked when you executejava Test$Nested
because the nested class is essentially a separate class namedTest$Nested
.
Output:
- Running
java Test
will output:Outer class main method
- Running
java Test$Nested
will output:Static nested class main method
Accessing static and non-static members from outer classes
In normal or regular inner classes, we can directly access both static and non-static members of the outer class. However, in static nested classes, we can only directly access the static members of the outer class and cannot access non-static members.
class Test
{
int x = 10;
static int y = 20;
static class Nested
{
public void m1()
{
// Compilation Error: non-static variable x cannot be referenced from a static context
// System.out.println(x);
System.out.println(y); // valid
}
}
}
Explanation:
- You cannot directly access the non-static variable
x
from the static methodm1()
in the static nested class because the static nested class and its methods are associated with the class itself, not an instance of the outer class. - However, you can access the static variable
y
since static members are associated with the class itself and can be accessed from both static and non-static contexts.
Differences between normal or regular inner class and static nested class
There are several significant differences between normal or regular inner classes and static nested classes. These differences revolve around aspects such as their association with the outer class, member accessibility, and more.
Normal or Regular Inner Class:
- Without an existing outer class object, there is no chance of an existing inner class object. In other words, the inner class object is strongly associated with the outer class object.
- In normal or regular inner classes, we can’t declare static members.
- Normal or regular inner classes cannot declare a main method, thus we cannot directly invoke the inner class from the command prompt.
- From normal or regular inner classes, we can directly access both static and non-static members of the outer class.
Static Nested Classes:
- Without an existing outer class object, there may be a chance of an existing static nested class object. However, the static nested class object is not strongly associated with the outer class object.
- In static nested classes, we can declare static members.
- In static nested classes, we can declare a main method, allowing us to invoke the nested class directly from the command prompt.
- From static nested classes, we can access only the static members of the outer class.
Various combinations of nested classes and interfaces
Case 1: Class Inside a Class
When there is no possibility of one type of object existing without another type of object, we can declare a class inside another class. For instance, consider a university that consists of several departments. Without the existence of a university, the concept of a department cannot exist. Therefore, it’s appropriate to declare the ‘Department’ class within the ‘University’ class.
class University
{
class Department
{
}
}
Case 2: Interface Inside a Class
When there is a need for multiple implementations of an interface within a class, and all these implementations are closely related to that particular class, defining an interface inside the class becomes advantageous. This approach helps encapsulate the interface implementations within the context of the class.
class VehicleTypes
{
interface Vehicle
{
int getNoOfWheels();
}
class Bus implements Vehicle
{
public int getNoOfWheels()
{
return 6;
}
}
class Auto implements Vehicle
{
public int getNoOfWheels()
{
return 3;
}
}
// Other classes and implementations can follow...
}
Case 3: Interface Inside an Interface
We can declare an interface inside another interface. For instance, consider a ‘Map’ which is a collection of key-value pairs. Each key-value pair is referred to as an ‘Entry.’ Since the existence of an ‘Entry’ object is reliant on the presence of a ‘Map’ object, it’s logical to define the ‘Entry’ interface inside the ‘Map’ interface. This approach helps encapsulate the relationship between the two interfaces.
interface Map
{
interface Entry
{
// Define methods and members for the Entry interface
// ...
// ...
// ...
}
}
Any interface declared inside another interface is always implicitly public
and static
, regardless of whether we explicitly declare them as such. This means that we can directly implement an inner interface without necessarily implementing the outer interface. Similarly, when implementing the outer interface, there’s no requirement to also implement the inner interface. In essence, outer and inner interfaces can be implemented independently.
interface Outer
{
void m1();
interface Inner
{
void m2();
}
}
class Test1 implements Outer
{
public void m1()
{
System.out.println("Outer interface method implementation");
}
}
class Test2 implements Outer.Inner
{
public void m2()
{
System.out.println("Inner interface method implementation");
}
}
public class Test
{
public static void main(String[] args)
{
Outer t1 = new Test1();
t1.m1();
Outer.Inner t2 = new Test2();
t2.m2();
}
}
Case 4: Class Inside an Interface
When a particular functionality of a class is closely associated with an interface, it is highly recommended to declare that class inside the interface. This approach helps maintain a strong relationship between the class and the interface, emphasizing the specialized functionality encapsulated within the interface.
interface EmailService
{
void sendMail(EmailDetails e);
class EmailDetails
{
String to_list;
String cc_list;
String subject;
String body;
}
}
In the given example, the EmailDetails
class is specifically required only for the EmailService
interface and is not used elsewhere. Thus, it’s recommended to declare the EmailDetails
class inside the EmailService
interface. This approach ensures that the class is tightly associated with the interface it serves.
Furthermore, class declarations inside interfaces can also be used to provide default implementations for methods defined in the interface, contributing to the interface’s flexibility and usability.
interface Vehicle
{
int getNoOfWheels();
class DefaultVehicle implements Vehicle
{
public int getNoOfWheels()
{
return 2;
}
}
}
class Bus implements Vehicle
{
public int getNoOfWheels()
{
return 6;
}
}
public class Test
{
public static void main(String[] args)
{
Vehicle.DefaultVehicle d = new Vehicle.DefaultVehicle();
System.out.println(d.getNoOfWheels()); // 2
Bus b = new Bus();
System.out.println(b.getNoOfWheels()); // 6
}
}
In the above example, the DefaultVehicle
class serves as the default implementation of the Vehicle
interface, while the Bus
class provides a customized implementation of the same interface.
It’s worth noting that a class declared inside an interface is always implicitly
public
andstatic
, regardless of whether we explicitly declare them as such. As a result, it’s possible to create an instance of the inner class directly without needing an instance of the outer interface.
Conclusions
1. In Java, both classes and interfaces can be declared inside each other, allowing for a flexible and versatile approach to structuring and organizing code.
Declaring a class inside a class:
class A
{
class B
{
}
}
Declaring an interface inside a class:
class A
{
interface B
{
}
}
Declaring an interface inside an interface:
interface A
{
interface B
{
}
}
Declaring a class inside an interface:
interface A
{
class B
{
}
}
2. The interface declared inside an interface is always implicitly public
and static
, regardless of whether we explicitly declare them as such.
interface A {
interface B {
// You can add methods and other members here
}
}
3. The class which is declared inside an interface is always public
and static
, whether we explicitly declare it as such or not.
interface A {
class B {
// You can add fields, methods, and other members here
}
}
4. The interface declared inside a class is always implicitly static
, but it doesn’t need to be declared as public
.
class A {
interface B {
// You can add methods and other members here
}
}
Conclusion
Inner classes are a powerful and versatile feature in Java, enabling you to create complex relationships and encapsulate functionality with elegance. Whether you’re organizing code, implementing event handling, or providing default implementations, inner classes offer a rich toolkit to tackle a variety of scenarios. By understanding the types of inner classes and their benefits, you can wield this feature to enhance code readability, maintainability, and design patterns.