In Part 1, Java Inner Classes – Intro, I covered most of the basics of inner classes. But there is much more. Some of it might get complex and confusing. But with all of that, I am beginning to understand and value what Bruce Eckel tries to say when he introduces inner classes.
At first, inner classes look like a simple code-hiding mechanism: You place classes inside other classes. You’ll learn, however, that the inner class does more than that–it knows about and can communicate with the surrounding class–and the kind of code you can write with inner classes is more elegant and clear, although there’s certainly no guarantee of this.
Initially, inner classes may seem odd, and it will take some time to become comfortable using them in your designs. The need for inner classes isn’t always obvious, but after the basic syntax and semantics of inner classes have been described, the section “Why inner classes?” should begin to make clear the benefits of inner classes.
In part 2, I am going to cover even more obscure, but more advanced topics. I like to learn by example, this part is heavy on examples.
Did you know you can define a class within a method? Yes, you can. It’s called a local inner class. Example below.
// Nesting a class within a method. // Example from Thinking in Java public class Parcel5 { public Destination destination(String s) { class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel() { return label; } } return new PDestination(s); } public static void main(String[] args) { Parcel5 p = new Parcel5(); Destination d = p.destination("Tasmania"); } } // /:~
How about a class within an “if” statement. Yes, you can do that as well. It’s called a class within arbitrary scope, see below.
// Nesting a class within a scope. // Thinking in Java example public class Parcel6 { private void internalTracking(boolean b) { if (b) { class TrackingSlip { private String id; TrackingSlip(String s) { id = s; } String getSlip() { return id; } } TrackingSlip ts = new TrackingSlip("slip"); String s = ts.getSlip(); } // Can't use it here! Out of scope: // ! TrackingSlip ts = new TrackingSlip("x"); } public void track() { internalTracking(true); } public static void main(String[] args) { Parcel6 p = new Parcel6(); p.track(); } } // /:~
One other interesting part about the example above is that the class TrackingSlip will get compiled and a class file created. However, this class will only be accessible from within the scope it got created in.
Did you know you can return a class from inside the method body? A class that is not accessible from anywhere else. A class that has no name. Yes, that’s why it’s called anonymous. See example below.
// Returning an instance of an anonymous inner class. // Thinking in Java example public class Parcel7 { public Contents contents() { return new Contents() { // Insert a class definition private int i = 11; public int value() { return i; } }; Semicolon required in this case } public static void main(String[] args) { Parcel7 p = new Parcel7(); Contents c = p.contents(); } } // /:~
Observe the syntax. The first statement in the method body is a return statement. It looks like you are returning a new instance of a class or interface. But that’s not it. You are actually creating/implementing the class, so you open a curly brackets { and close with }; and put the class definition inside. Very tricky and hard to get used to, I think.
Passing Arguments / Anonymous ConstructorWhat if you need to pass an argument and do some constructor initialization. It turns out you can.
// Creating a constructor for an anonymous inner class. // Thinking in Java example abstract class Base { public Base(int i) { print("Base constructor, i = " + i); } public abstract void f(); } public class AnonymousConstructor { public static Base getBase(int i) { return new Base(i) {{ print("Inside instance initializer"); } public void f() { print("In anonymous f()"); }}; } public static void main(String[] args) { Base base = getBase(47); base.f(); } } /** Output: Base constructor, i = 47 Inside instance initializer In anonymous f() */// :~
One note about arguments. If you’re using them inside the inner class, they have to be passed as final. In the above case, it’s not used directly so a non-final argument is fine.
More: You can even define an instance variable in an inner class!
Here’s a snippet from Thinking in Java that illustrates that:
public Destination destination(final String dest, final float price) { return new Destination() { private int cost; // Instance initialization for each object: {cost = Math.round(price); if(cost > 100) System.out.println("Over budget!"); } private String label = dest; public String readLabel() { return label; }}; }
Note that in the above example, because price was used in the inner class, it had to be defined as final.
Here’s a final note from Bruce about anonymous inner classes.
Anonymous inner classes are somewhat limited compared to regular inheritance, because they can either extend a class or implement an interface, but not both. And if you do implement an interface, you can only implement one.
More fun with inner classes to come! In Part 3, I’ll cover nested classes.
Reference
Thinking in Java (4th), Bruce Eckel
Java Inner Classes – Part 1 – Intro, The Pragmatic Craftsman
Now I can see that it’s possible to create a class like PDestination, but you say little as to why one would one to do it or what it actually does.