Inner Classes - 2020
A java class within another class is an inner class.
Inner class is a type of nested classes.
Inner Class:
- (Regular) Inner Class
- Static Inner Class
- Anonymous Inner Class
Here is the simple example of inner class.
class OuterClass { public static void main(String[] args) { System.out.println("Outer"); } class InnerClass { public void inner_print() { System.out.println("Inner"); } } }
The output is just one line:
Outer
and the file in bin directory should look like:
OuterClass.class OuterClass$InnerClass.class
But how do we get access to the method of inner class?
To the outer class, inner class is just another class. So, to create an instance of the inner class, we can use:
InnerClass inner = new InnerClass();
Then, we invoke the method of inner class on the reference.
inner.inner_print();
How about the following code?
class OuterClass { public static void main(String[] args) { System.out.println("Outer"); InnerClass inner = new InnerClass(); inner.inner_print(); } class InnerClass { public void inner_print() { System.out.println("Inner"); } } }
The preceding code won't work.
To create an instance of an inner class, we must have an instance of the outer class. Actually, when we create an inner class, an object of that inner class has a link to the outer object that made it. Compiler hand an implicit reference to the outer class to the inner class instances. In that way, the inner class has the ability to access members of the outer class.
So, at the moment of instantiating an inner class, we should know about the outer class. In this case, we should make an instance of outer class first. Then, on that reference, we instantiate the inner class.
OuterClass o = new OuterClass(); InnerClass inner = o.new InnerClass();
In other words, we're invoking a method on the outer instance. But the method is special in this case, and it happens to be instantiation method for an inner class.
The code that's working should look like this:
class OuterClass { public static void main(String[] args) { System.out.println("Outer"); OuterClass o = new OuterClass(); InnerClass inner = o.new InnerClass(); inner.inner_print(); } class InnerClass { public void inner_print() { System.out.println("Inner"); } } }
This time, the output is:
Outer Inner
The static nested class is a class enclosed within another class. It is marked with the static modifier.
Let's modify the preceding code and make it static:
class OuterClass { static class InnerClass { public void inner_print() { System.out.println("Inner"); } } }
Note that we made the InnerClass as static.
That means it's tied to the class, not a particular instance. Static nested classes are more like regular classes that are not nested. That is they don't enjoy a special relationship with an enclosing outer object. But because static nested classes are still treated as a member of the enclosing outer class, they still get access to any private members of the outer class. However, it can only access to static members. Since the static nested class isn't connected to an instance of the outer class, it doesn't have any special way to access the non-static instance variables and method.
The driver class is:
public class StaticNestedClassTest { public static void main(String[] args) { System.out.println("Outer"); OuterClass.InnerClass inner = new OuterClass.InnerClass(); inner.inner_print(); } }
Because a static nested class is static, we don't use an instance of the outer class. We just use the name of the class, the same way we invoke static methods or access static variables.
Anonymous inner classes are inner classes declared without any class name at all. We can define these classes even within an argument to a method.
As an example, we'll make two classes. One for the class that has static main method and the other has a very simple method of printing.
public class MyClass { public void myprint() { System.out.println("MyClass"); } } public class AnonymousTest { public static void main(String[] args) { MyClass myclass = new MyClass() { public void myprint() { System.out.println("Anonymous"); } }; myclass.myprint(); } }
The output is:
Anonymous
Let's look at what's in the code:
- We defined two classes, MyClass and AnonymousTest.
- MyClass has one method, myprint().
- AnonymousTest class has one instance variable, myclass.
This myclass refers not to an instance of MyClass, but to an instance of an anonymous subclass of MyClass.
So, the lineMyClass myclass = new MyClass() {
is declaring a reference variable myclass. Then we declare a new class that has no name. But it's a subclass of MyClass.
The method, myprint(), defined in the anonymous class is overriding the method in MyClass. So, the output is from the myprint() of a new class.
There are two reasons of using anonymous classes.
- To override methods of the superclass (as in this example).
- To implement methods of an interface (we'll see it later).
Here is a short quiz.
What will happen if we add another method myprint2() and call the method?
MyClass myclass = new MyClass() { public void myprint() { System.out.println("Anonymous"); } public void myprint2() { System.out.println("Anonymous2"); } }; myclass.myprint(); myclass.myprint2();
We'll have following error:
The method myprint2() is undefined for the type MyClass.
Why?
When we use the reference variable, myclass, we're using a superclass reference variable type to refer to a subclass object. So, we can only call methods on an anonymous inner class reference that are defined in the reference variable type.
We have another type of anonymous inner class. In the previous example, the anonymous inner class was created as a subclass of the specified class type. But in this example, an anonymous implementer will be created as the specified interface type.
interface MyInterfaceable { public void myprint(); } public class AnonymousTest { public static void main(String[] args) { MyInterfaceable myinterface = new MyInterfaceable() { public void myprint() { System.out.println("Anonymous"); } }; myinterface.myprint(); } }
What's going on in the line:
MyInterfaceable myinterface = new MyInterfaceable() {
It creates an instance of an anonymous inner class, an implementer of the MyInterface interface. Notice that we have
new MyInterfaceable()even though MyInterface is an interface and so we can't make an instance of it. But this syntax is saying, "create a new class (with no name) that implements the MyInterface interface, and we have the implementation of the interface methods: myprint().
Compare the following two cases:
- Illegal
MyInterfaceable myinterface = new MyInterfaceable();
- Legal
MyInterfaceable myinterface = new MyInterfaceable() { public void myprint() {} };
This third example is similar to the second example, implementer case. But at this time, we make sort of just-in-time implementer as an argument of a method.
Here is the example:
public class AnonymousTest { public static void main(String[] args) { CookableHandle handle = new CookableHandle(); handle.useCookableInterface(new MyCookable() { public void mycook() { System.out.println( "Cooking Anonymous Arg Inner Class"); } // end of mycook() } // end of inner class ); // end of argument } } public class CookableHandle { void useCookableInterface(MyCookable f) { f.mycook(); } } interface MyCookable { public void mycook(); }
The output is:
Cooking Anonymous Arg Inner Class
Let's look at what's going on the line:
handle.useCookableInterface(new MyCookable() {
We call useCookableInterface() on a CookableHandle object. However, the method takes an instance which is an interface MyCookable. That's why we have to make both an implementation class and an instance of that class.
So, we start with:
new MyCookable() {
It defines the new class for the anonymous class that implements the MyCookable interface. MyCookable interface has one method to implement, mycook(). So, we implement the mycook() method.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization