http://www.ibm.com/developerworks/java/tutorials/j-intermed/section4.html
Nested classes
As its name suggests, a nested class in the Java language is a class declared within another class. Here's a simple example:
public class EnclosingClass { ... public class NestedClass { ... } } |
Typically, good programmers define nested classes when the nested class only makes sense within the context of the enclosing class. Some common examples include the following:
- Event handlers within a UI class
- Helper classes for UI components within those components
- Adapter classes to convert the innards of one class to some other form for users of the class
You can define a nested class as public
, private
, or protected
. You also can define a nested class as final
(to prevent it from being changed), abstract
(meaning that it can't be instantiated), or static
.
When you create a static
class within another class, you're creating what is appropriately called a nested class. A nested class is defined within another class, but can exist outside an instance of the enclosing class. If your nested class isn't static
, it can exist only within an instance of the enclosing class, and is more appropriately called an inner class. In other words, all inner classes are nested classes, but not all nested classes are inner classes. The vast majority of the nested classes you will encounter in your career will be inner classes, rather than simply nested ones.
Any nested class has access to all of the members of the enclosing class, even if they're declared private
.
You define a nested class just as you define a non-nested class, but you do it within an enclosing class. For a somewhat contrived example, let's define a Wallet
class inside Adult
. While in real life you could have a Wallet
apart from an Adult
, it wouldn't be all that useful, and it makes good sense that every Adult
has a Wallet
(or at least something to hold money, andMoneyContainer
sounds a little odd). It also makes sense that Wallet
wouldn't exist on Person
, because a Baby
doesn't have one, and all subclasses of Person
would inherit it if it existed up there.
Our Wallet
will be quite simple, since it only serves to illustrate the definition of a nested class:
protected class Wallet { protected ArrayList bills = new ArrayList(); protected void addBill(int aBill) { bills.add(new Integer(aBill)); } protected int getMoneyTotal() { int total = 0; for (Iterator i = bills.iterator(); i.hasNext(); ) { Integer wrappedBill = (Integer) i.next(); int bill = wrappedBill.intValue(); total += bill; } return total; } } |
We'll define that class inside Adult
, like this:
public class Adult extends Person { protected Wallet wallet = new Wallet(); public Adult() { } public void talk() { System.out.println("Spoke."); } public void acceptMoney(int aBill) { this.wallet.addBill(aBill); } public int moneyTotal() { return this.wallet.getMoneyTotal(); } protected class Wallet { ... } } |
Notice that we added acceptMoney()
to let an Adult
accept more money. (Feel free to expand the example to force your Adult
to give some up, which is a more common event in real life.)
Once we have our nested class and our new acceptMoney()
method, we can use them like this:
Adult anAdult = new Adult(); anAdult.acceptMoney(5); System.out.println("I have this much money: " + anAdult.moneyTotal()); |
Executing this code should report that anAdult
has a money total of 5.
The Java language defines an event handling approach, with associated classes, that allows you to create and handle your own events. But event handling can be much simpler than that. All you really need is some logic to generate an "event" (which really doesn't have to be an event class at all), and some logic to listen for that event and then respond appropriately. For example, suppose that whenever a Person
moves, our system generates (or fires) a MoveEvent
, which we can choose to handle or not. This will require several changes to our system. We have to:
- Create an "application" class to launch our system and illustrate using the anonymous inner class.
- Create a
MotionListener
that our application can implement, and then handle the event in the listener. - Add a
List
of listeners toAdult
. - Add an
addMotionListener()
method toAdult
to register a listener. - Add a
fireMoveEvent()
method toAdult
so that it can tell listeners when to handle the event. - Add code to our application to create an
Adult
and register itself as a handler.
This is all straightforward. Here's our Adult
with the new stuff:
public class Adult extends Person { protected Wallet wallet = new Wallet(); protected ArrayList listeners = new ArrayList(); public Adult() { } public void move() { super.move(); fireMoveEvent(); } ... public void addMotionListener(MotionListener aListener) { listeners.add(aListener); } protected void fireMoveEvent() { Iterator iterator = listeners.iterator(); while(iterator.hasNext()) { MotionListener listener = (MotionListener) iterator.next(); listener.handleMove(this); } } protected class Wallet { ... } } |
Notice that we now override move()
, call move()
on Person
first, then call fireMoveEvent()
to tell listeners to respond. We also added addMotionListener()
to add a MotionListener
to a running list of listeners. Here's what a MotionListener
looks like:
public interface MotionListener { public void handleMove(Adult eventSource); } |
All that's left is to create our application class:
public class CommunityApplication implements MotionListener { public void handleMove(Adult eventSource) { System.out.println("This Adult moved: /n" + eventSource.toString()); } public static void main(String[] args) { CommunityApplication application = new CommunityApplication(); Adult anAdult = new Adult(); anAdult.addMotionListener(application); anAdult.move(); } } |
This class implements the MotionListener
interface, which means that it implements handleMove()
. All we do here is print a message to illustrate what happens when an event is fired.
Anonymous inner classes allow you to define a class in place, without naming it, to provide some context-specific behavior. It's a common approach for event handlers in user interfaces, which is a topic beyond the scope of this tutorial. But we can use an anonymous inner class even in our simplistic event-handling example.
You can convert the example from the previous page to use an anonymous inner class by changing the call toaddMotionListener()
in CommunityApplication.main()
like so:
anAdult.addMotionListener(new MotionListener() { public void handleMove(Adult eventSource) { System.out.println("This Adult moved: /n" + eventSource.toString()); } }); |
Rather than having CommunityApplication
implement MotionListener
, we declared an unnamed (and thus anonymous) inner class of type MotionListener
, and gave it an implementation of handleMove()
. The fact that MotionListener
is an interface, not a class, doesn't matter. Either is acceptable.
This code produces exactly the same result as the previous version, but it uses a more common and expected approach. You will almost always see event handlers implemented with anonymous inner classes.
Nested classes can be very useful. They also can cause pain.
Use a nested class when it would make little sense to define the class outside of an enclosing class. In our example, we could have defined Wallet
outside Adult
without feeling too badly about it. But imagine something like a Personality
class. Do you ever have one outside a Person
instance? No, so it makes perfect sense to define it as a nested class. A good rule of thumb is that you should define a class as non-nested until it's obvious that it should be nested, then refactor to nest it.
Anonymous inner classes are the standard approach for event handlers, so use them for that purpose. In other cases, be very careful with them. Unless anonymous inner classes are small, focused, and familiar, they obfuscate code. They can also make debugging more difficult, although the Eclipse IDE helps minimize that pain. Generally, try not to use anonymous inner classes for anything but event handlers.