O'Reilly Book Excerpts: Hardcore Java
Nested Classes, Part 1
|
Related Reading
|
Author's note: The use of nested classes in Java is a constant source of confusion for many programmers. Yet understanding nested classes is critical to such things as the proper implementation of UML composition relationships in Java. In addition, such understanding will help the professional developer unravel spaghetti code put out by other developers.
In this first excerpt in a three-part series of excerpts from Chapter 6 ("Nested Classes") of my book, I cover the first of the three basic categories of nested classes--inner classes. Over the next two weeks I will cover limited-scope inner classes and static nested classes.
This chapter was actually one of the last I wrote for Hardcore Java. I was inspired to write it after spending a long day with a junior developer explaining the concept of nested classes. Furthermore, I had read other texts that referred to static nested classes as inner classes (which is incorrect). As I researched the issue more and more, it became clear that these classes were overwhelmingly poorly understood. Questions about nested classes are among the most prevalent on the Java Developer Connection forums, and the tutorials on Java that I was able to find only glossed over the issues. I wasn't able to find a single Java text that discussed them in detail. All these factors inevitably led to the decision to include a chapter on the subject in Hardcore Java.
While writing the chapter, I used many code examples to test various concepts and to fully explore the concept of nested classes in detail. In fact, I can safely say that I learned as much writing the chapter as I hope you will learn reading it. My goal was to make the most complete description of nested classes to be found anywhere.
The concept of nesting a class within another class or method presents unique issues not found elsewhere in object-oriented programming. Not all types of nested classes should be used routinely; however, you will likely encounter most of them in other people’s code. Therefore, it is important that you understand how the various nested classes function.
Nested classes fall into one of three basic categories:
- Inner classes
- Limited-scope inner classes
- Static nested classes
Each of these categories has its own access rules and usage. Let's take a look at the first--inner classes.
Inner Classes
Inner classes are fairly common within the JDK, especially in collections. Example 6-1 shows a class-scoped inner class.
Example 6-1. A class-scoped inner class
package oreilly.hcj.nested;
public class InnerClassDemo extends JDialog {
public InnerClassDemo(final int beepCount) {
super( );
setTitle("Anonymous Demo");
contentPane = getContentPane( );
contentPane.setLayout(new BorderLayout( ));
JLabel logoLabel = new JLabel(LOGO);
contentPane.add(BorderLayout.NORTH, logoLabel);
JButton btn = new BeepButton("Beep");
contentPane.add(BorderLayout.SOUTH, btn);
pack( );
this.beepCount = beepCount;
}
private class BeepButton extends JButton implements ActionListener {
public BeepButton(final String text) {
super(text);
addActionListener(this);
}
public void actionPerformed(final ActionEvent event) {
try {
for (int count = 0; count < beepCount; count++) {
Toolkit.getDefaultToolkit().beep( );
Thread.sleep(100); // wait for the old beep to finish.
}
} catch (final InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}
}
This code shows how inner classes are used in a GUI dialog. To create a special kind
of button that beeps when you click on it, the javax.swing.JButton class has been
extended and the appropriate action listener has been added. In the constructor,
another of these special buttons is instantiated and added to the dialog. Although
this seems pretty simple, there are a couple of interesting points worthy of study.
First of all, you may notice that the variable beepCount, used in the for statement,is
in scope inside the action listener, even though it was not declared in the
actionPerformed( ) method. This is because inner classes can access all members of
the declaring class, even private members. In fact, the inner class itself is said to be a
member of the class; therefore, following the rules of object-oriented engineering, it
should have access to all members of the class.
The other interesting point is the visibility keyword on the class declaration line:
private class BeepButton extends JButton implements ActionListener {
Because this class is declared as private, other classes can't instantiate this class. This
restriction behaves as public, protected, and private visibility keywords behave for any other member of the enclosing class.
Using public visibility with inner classes leads to some rather strange syntax. To demonstrate,let's create an example of a public inner class and then use it. The inner class is shown here:
// From oreilly.hcj.nested.MonitorScreen
public class MonitorScreen {
public class PixelPoint {
private int x;
private int y;
public PixelPoint(final int x, final int y) {
this.x = x;
this.y = y;
}
public int getX( ) {
return x;
}
public int getY( ) {
return y;
}
}
}
In this example, an inner class represents a pixel on your screen. Since the inner class
is public, you can create an instance of PixelPoint outside of the enclosing class.
Your first attempt may look something like the following:
package oreilly.hcj.nested;
public class PublicInnerClassAccess {
public static final void main(final String[] args) {
MonitorScreen.PixelPoint obj =
new MonitorScreen.PixelPoint( ); // <= Compiler error.
}
}
The problem here is that the scope of the inner class is restricted only to an instance of the enclosing class. For this reason, such a static declaration won't work. You need an instance of the class to complete the construction. This can be accomplished with the following code:
package oreilly.hcj.nested;
public class PublicInnerClassAccess {
public static final void main(final String[] args) {
MonitorScreen screen = new MonitorScreen( );
MonitorScreen.PixelPoint pixel = screen.new PixelPoint(25, 40);
System.out.println(pixel.getX( ));
System.out.println(pixel.getY( ));
}
}
In this version,you create a new instance of MonitorScreen and use it to create a new
instance of PixelPoint with the following code:
screen.new PixelPoint(25, 40);
The syntax of the creation looks strange, but it really does work, as you can see in this book's sample code. That being said, I don't recommend you use this sort of syntax routinely, as it is confusing to read and violates the object-oriented engineering principles behind inner classes.
Inner classes are best used to represent composition relationships in an object model.
Therefore, the enclosing instances should always have total control over the enclosed
instances. In this case, a PixelPoint cannot exist outside of a monitor screen, so composition
is indicated.
Note: If you recall, composition models aggregation in which the life cycle of the objects being aggregated are under the control of the object performing the aggregation. If the object performing the aggregation goes out of scope, so should the objects it is composed of. The decision to use composition instead of aggregation comes down to a question of whether the composed class can exist on its own. If it can't, then composition is indicated; otherwise, aggregation is indicated.
Since inner classes are best used to model composition, I strongly suggest you stick to private and protected visibility for your inner classes.
Hierarchies of Inner Classes
Like normal classes, inner classes can exist in a hierarchy. In fact, inner classes can
even carry the hierarchy restrictions of abstract and final. To understand these hierarchies, study the abstract inner class in Example 6-2.
Example 6-2. A basic monitor screen class
package oreilly.hcj.nested;
public abstract class BasicMonitorScreen {
private Dimension resolution;
public BasicMonitorScreen(final Dimension resolution) {
this.resolution = resolution;
}
public Dimension getResolution( ) {
return this.resolution;
}
protected abstract class PixelPoint {
private int x;
private int y;
public PixelPoint(final int x, final int y) {
this.x = x;
this.y = y;
}
public int getX( ) {
return x;
}
public int getY( ) {
return y;
}
}
}
The BasicMonitorScreen class provides the base class for all monitor screens in your
overall class hierarchy. Since the screen is composed of pixels,which indicates composition,
the PixelPoint class is declared as an inner class.
Note that both the enclosing class and the inner class are declared abstract. However,
there is no connection at all between the two. The hierarchies for the enclosing
class and inner class should be considered independently. In the example, the
BasicMonitorClass is said to be abstract because it can't exist without more specificity.
The PixelPoint class also has the same dynamics, but it could have been declared
concrete and simply used by subclasses. In this case, however, the subclasses have to
define a concrete subclass of PixelPoint to use it. Also, make sure that an abstract
inner class doesn't make the enclosing class abstract, as with an abstract method.
Creating concrete classes within an abstract base class is a good way to model composition relationships in an inheritance hierarchy. You can declare the composed classes in the base class and let the subclasses use that composed class.
In this situation, you need to create a concrete type of PixelPoint to use it. The subclasses
are shown in Example 6-3.
Example 6-3. A class for a color monitor
package oreilly.hcj.nested;
public class ColorMonitorScreen extends BasicMonitorScreen {
public ColorMonitorScreen(final Dimension resolution) {
super(resolution);
}
protected class ColorPixelPoint extends PixelPoint {
private Color color;
public ColorPixelPoint(final int x, final int y, final Color color) {
super(x, y);
this.color = color;
}
public Color getColor( ) {
return this.color;
}
}
}
In this example,you create inheritance hierarchies for both the enclosing and inner classes. Now you have an inner class that can be instantiated in the virtual machine. However, watch out for one little gotcha with inherited inner classes:
package oreilly.hcj.nested;
public class ColorMonitorScreen extends BasicMonitorScreen {
protected class ColorPixelPoint extends PixelPoint {
public void someMethod( ) {
System.out.println(resoultion); // <= compiler error
}
}
}
Remembering that inner classes can access the members of their enclosing instance,
try to access BasicMonitorScreen's resolution class variable in your ColorPixelPoint
inner class. The compiler won't let you access resolution because the special privileged
access to the enclosing class's members apply only to the inner classes declared
in the enclosing class. This naturally follows since these inner classes are members of
the enclosing class, but subclasses of them declared outside the enclosing class are
not members. Since ColorPixelPoint is not a member of BasicMonitorScreen, access
to BasicMonitorScreen's private and protected members is not allowed.
Finally, keep in mind that public inner classes can also be extended by classes that are not inner classes. However, since public inner classes are generally a bad idea, you shouldn't be doing this anyway. If you have an overwhelming compulsion to employ this technique, you should consider using static nested classes instead.
Robert Simmons, Jr. lives and works as a senior software architect in Germany. He is the author of O'Reilly's Hardcore Java.
Return to ONJava.com.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 11 of 11.
-
if a class part of Composition and Agregation then what?
2008-03-28 14:48:41 Murray9654 [Reply | View]
-
nested classes in swing components
2006-02-10 13:37:32 mrodent [Reply | View]
hi,
Found my way to yr site after repeated postings in the swing part of the java forums failed to produce an answer to this: in a JTextPane, using styled text (i.e. with attributes such as bold etc.), going Ctrl-C (copy) on a selection actually turns the styled text into plain text in front of your eyes! The culprit here is StyledEditorKit.StyledTextAction, a nested class, which somehow has to be overridden. Nor is it the first time I've encountered a need to get on top of these mysterious nested classes - they're everywhere, and I personally dread finding myself having to use one... but only because I can't (like you say) find a source of info about how they work and above all how to manipulate them. Your contents doesn't seem to cover swing nested classes specifically... maybe you need to write a whole new book called "hard core swing nested classes"???
Only joking. Will probably buy yr book anyway... any tips about my styledtoolkit prob?
-
inner class
2004-12-30 00:53:13 khairun [Reply | View]
public class PublicInnerClassAccess {
public static final void main(final String[] args) {
MonitorScreen screen = new MonitorScreen( );
MonitorScreen.PixelPoint pixel = screen.new PixelPoint(25, 40);
System.out.println(pixel.getX( ));
System.out.println(pixel.getY( ));
}
}
Dear sir
I got the above code from your books. with the line "public static final void main(final String[] args) {" is generating an runtime error with the 'final' keyword between "public static final void main". i would like to know, should i use any other technic to run the above code or why it is not running properly. if i remove the final keyword from that place it runs ok.
thanking you
Khairun Afroz.
-
static inner class
2004-06-21 06:41:15 pzikos [Reply | View]
i understand the concept of inner classes and i have used them a lot in my programs. yet why would somebody declare a static inner class and not create another class? -
static inner class
2004-06-21 07:11:08 Robert Simmons Jr. (Kraythe) [Reply | View]
Oh, that is easy. Compositional relationships. For example, consider an entry in a Hashtable. The entry itself cannot exist outside of the hashtable. Nor is there any reason for users of the table to access the class. Therefore, a private static nexted class is the perfect answer.
-
Accessing Protected Members
2004-05-18 12:33:43 bdell [Reply | View]
I changed resolution from private to protected access and ColorMonitorScreen compiled just fine, so I don't think the statement, "Since ColorPixelPoint is not a member of BasicMonitorScreen, access to BasicMonitorScreen's private and protected members is not allowed," is entirely correct.
I only tried it with 1.5beta, so maybe it's a language change, but I think it makes sense. A member of a sub-class should have access to protected members of the super-class. -
Accessing Protected Members
2004-05-18 14:33:35 Robert Simmons Jr. (Kraythe) [Reply | View]
That is because protected members of a class are accessible to classes in the same package. I suppose I should have said that the other class needs to be in another package to be more clear.
I appreciate the feedback. -
Accessing Protected Members
2004-05-18 21:28:10 bdell [Reply | View]
I placed BasicMonitorScreen in package package1 and ColorMonitorScreen in package package2 and it still compiles. I also tried 1.4.2 and it works there as well. Perhaps I am misunderstanding what the "other" class is.
Given the way inner classes are implemented, it seems natural to provide access as the accessor method the inner class depends on would have access to protected members of the enclosing class's super-class and, as I wrote before, it seems consistent with the definition of protected access. I couldn't find anything in the Java Language Specification that said protected members in this circumstance shouldn't be accessible. -
Accessing Protected Members
2004-05-21 15:42:38 kristianmonsen [Reply | View]
I understand it so that if the enclosing class can access the variable, than the inner class can as well. Since the enclosing class is a subclass of the class where the variable is declared, it will always be able to access a protected variable.





I just want to ask you one question? If a class part of Composition and Aggregation then what to do? I had a class called Test. If the "name" property of the test contains a specific value it is part of some composition. when it contains other values it is part of Aggregation? so how to deal this?
one last this is if you look at the following link http://www.webtechniques.com/archives/1997/11/java/ in the first two lines of the article the Author says Inner class is different from composition. Please comment on this.