CS 596 Java Programming Fall Semester, 1998 Nested & Inner Classes |
||
---|---|---|
© 1998, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 13-Nov-98 |
Nested Classes
Java 1.1 introduced nested classes
On first reading you may find that nested classes and inner classes have some seemingly arbitrary restrictions. The restrictions occur because nested classes were not part of the original language design. They did not want to change the JVM to incorporate nested/inner classes. As a result the compiler has to use Java 1.0 features to implement nested/inner classes. In order to do this some restrictions are placed on nested/inner classes.
The need for some types of nested/inner class may not be apparent at this time. However, they were added they were needed. The event model for GUI components introduced in Java 1.1 is a major motivation for inner classes. Also inner classes are used with inheritance and interfaces. When we cover interfaces and Java's GUI components inner classes will make much more sense.
Static nested classes
Top-Level Nested Classes
A class defined inside another class (but not in any other block) and declared static is a top-level nested class
public class OutSide { private String name = "Roger"; public static class Nested { private int size; public Nested( int size ) { this.size = size; } public void print() { System.out.println( "Nested " + size ); } } public void print() { System.out.println( "Outside " + name ); (new Nested( 10 )).print(); } } public class TestNested { public static void main( String[] arguments) { OutSide pitch = new OutSide(); pitch.print(); OutSide.Nested a = new OutSide.Nested( 5 ); a.print(); } }Output Outside Roger Nested 10 Nested 5
Difference Between Ordinary Classes
Nested top-level classes differ from ordinary classes by:
- access levels
- name of the class
Access levels
Ordinary classes have two access levels:
Using Import & Packages with Static Nested Classes
Package with Nested Classpackage NameExample; public class OutSide { public static class Nested { } }Import the outer class, qualify nested class
import NameExample.OutSide; public class NameTest { public static void main( String[] args ) { OutSide a = new OutSide(); OutSide.Nested b = new OutSide.Nested( 3 ); } }The wild card does not allow you to shorten the name of the nested class
import NameExample.*; public class Test { public static void main( String[] args ) { OutSide a = new OutSide(); OutSide.Nested b = new OutSide.Nested( ); } }
Using the import to shorten the nested class name
import NameExample.OutSide; import NameExample.OutSide.Nested; public class NameTest { public static void main( String[] args ) { OutSide a = new OutSide(); Nested b = new Nested( 3 ); } }
Using full class names
public class NameTest { public static void main( String[] args ) { NameExample.OutSide a = new NameExample.OutSide(); NameExample.OutSide.Nested b = new NameExample.OutSide.Nested( ); } }
Inner Classes
Nested top-level classes are nested for organizational convenience. The nesting has no additional semantics than the name and access level of the class. With inner classes the nesting serves an additional purpose. All objects created from an inner class must be enclosed in an object created from the enclosing class. Examples will make this clear.
Member Classes
A non-static class nested in another class
Each instance has an enclosing instance and can access its enclosing instances fields and methods.
Can not have static members
Can not have the same name as the containing class
On the next slide there is an example of a member class, "Counter". "Counter" is declared inside the class "DataStuff", but "Counter" is not a static class. Note that "Counter" can access the private field of "DataStuff".
The class "Test" shows the two ways to create a Counter object. The first is from inside the class "DataStuff". This is done in the method getCounter(). The statement " a.getCounter();" creates a counter object that is enclosed (or connected) to the DataStuff object referenced by "a". The compiler does this enclosing (or connecting) by inserting a private field in the class "Counter" which references the proper DataStuff object. The statement "counterA.increase();" uses this special field to access the field "size" in the object referenced by "a".
The second way to create a Counter object is from outside the class "DataStuff". The statement "b.new Counter();" shows the syntax of doing this. Note the new syntax for new. It is preceded with an object reference and a ".". In "b.new Counter();" object b becomes the enclosing object of the new Counter.
Member Class Example public class DataStuff { private int size = 0; public class Counter { public void increase( ) { size++; //accessing enclosing classes private field } }// class Counter public String toString() { return String.valueOf( size); } public Counter getCounter() { return new Counter(); } }//class DataStuff public class Test{ public static void main( String args[] ) { DataStuff a = new DataStuff(); DataStuff b = new DataStuff(); DataStuff.Counter counterA = a.getCounter(); DataStuff.Counter counterB = b.new Counter(); counterA.increase(); counterA.increase(); counterB.increase(); System.out.println( "A " + a + " B " + b ); } }Output A 2 B 1
Member Classes & Access Levels
Member Classes can have the access levels:
import DataStuff.Counter; public class Test { public static void main( String args[] ) { DataStuff a = new DataStuff(); DataStuff b = new DataStuff(); Counter counterA = a.getCounter(); Counter counterB = b.new Counter(); counterA.increase(); counterA.increase(); counterB.increase(); System.out.println( "A " + a + " B " + b ); } }
Member Classes & this
"this" in a member class refers to the current object. It can be used to refer to fields and methods of the current object. "this" can not be used to refer to fields and method of the enclosing class.
public class DataStuff { private int size = 0; public class Counter { public void increase( ) { this.size++; // Compile error } }// class Counter }To refer to the enclosing class use ClassName.this
public class DataStuff { private int size = 0; public class Counter { public void increase( ) { DataStuff.this.size++; } }// class Counter }
More this & Member Class
Another example of using the className.this to access enclosing class fields
public class A { String name = "A"; public class B { String name = "B"; public class C { String name = "C"; public void print( String name ) { System.out.println( name ); //prints the argument System.out.println( this.name ); // C System.out.println( C.this.name ); // C System.out.println( B.this.name ); // B System.out.println( A.this.name ); // A } } } }
Local Classes
A local class is a class declared in within a block of code
A local class is visible and usable within the block of code in which it is defined
A local class can use any final variable or method parameters that are visible from the scope in which it is defined
A local class cannot:
Local Class Example This example shows the syntax of a local class. It also shows one of the problems with a local class: readability. How does one format the source code to make it readable? This example is a good example of how to misuse a local class. As with nested and all inner classes, local classes should be use infrequently.
public class TopLevel { private int size = 0; public int process( final int input ) { class Helper { int localSize; public Helper( int sizeIn ) { localSize = sizeIn; } public int useful( ) { return input + localSize; } }// Class Helper Helper myFriend = new Helper( size); return myFriend.useful(); } // process } // class TopLevel
Anonymous Classes
An anonymous class is a local class with no name
Since an anonymous class has no name, you can not declare a variable or field to be of that type. For this reason, anonymous classes must either be a subclass of another class or implement an interface. Below is an example of an anonymous class implementing an interface. The basic syntax is:
new InterfaceName() { classDefinitionHere };
An anonymous class is always defined in a statement. In this example, it is part of a return statement. This means the semi-colon is needed after the "}" in the class definition to end the return statement.
public class SimpleVector { Object[] elements = new Object[100]; int elementCount = 0; public void addElement( Object element ) { elements[elementCount++] = element; } public Object elementAt( int index ) { return elements[ index ]; } public int size() { return elementCount; } public Enumeration elements() { return new Enumeration( ) { int nextElementIndex = 0; public boolean hasMoreElements() { return nextElementIndex < elementCount; } public Object nextElement() { return elements[ nextElementIndex++ ]; } }; // ";" is needed to end the return statement } }
Anonymous Class as Subclass
In the example on the next slide, an anonymous class is declared a subclass. The basic syntax is:
new ParentClassName( parameters ) { classDefinitionHere };
The parameter list is used to match the constructor in the parent class. In the example, the class Foo has a constructor with one parameter. Thus, the anonymous class in Outer must have one argument. Since anonymous classes can not define their own type, it does not make any sense to define new methods in an anonymous class. Note there is no way to use the method notReachable below.
Anonymous Class as Subclass Example
public class Foo { int data; public String toString() { return String.valueOf( data ); } public void add( int addValue ) { data = data + addValue; } public Foo( int initialValue ) { data = initialValue; } } public class Outer { public void bar(int aValue ) { Foo what = new Foo( aValue ) { public void add( int addValue ) { data += addValue + 2; } public void notReachable() { data++; } }; System.out.println( what ); what.add( 1 ); System.out.println( what ); } } public class FooTest() { public static void main(String args[] ) { Outer test = new Outer(); test.bar( 1 ); test.bar( 10 ); } }Output 1 4 10 13
Enumeration Examples of Nested/Inner Classes
I will use SimpleVector and SimpleVectorEnumeration from Doc 10, slide 12 to illustrate the different types of nested and inner classes.
No Nested Classes
The first example uses normal classes. SimpleVector is a public class, SimpleVectorEnumeration a package level class.
public class SimpleVector { Object[] elements = new Object[100]; int elementCount = 0; public void addElement( Object element ) { elements[elementCount++] = element; } public Object elementAt( int index ) { return elements[ index ]; } public int size() { return elementCount; } public Enumeration elements() { return new SimpleVectorEnumeration( this ); } } class SimpleVectorEnumeration implements Enumeration { SimpleVector myVector; int nextElementIndex = 0; public SimpleVectorEnumeration( SimpleVector aVector ) { myVector = aVector; } public boolean hasMoreElements() { return nextElementIndex < myVector.size(); } public Object nextElement() { return myVector.elementAt( nextElementIndex++ ); } }
Top-Level Nested Class
SimpleVectorEnumeration as a top-level nested class. We gain better protection, as SimpleVectorEnumeration can be made private. However, this is not a big gain. Who would know that package level SimpleVectorEnumeration in the last slide exists? We lose some readability. Where does SimpleVector end?
public class SimpleVector { Object[] elements = new Object[100]; int elementCount = 0; public void addElement( Object element ) { elements[elementCount++] = element; } public Object elementAt( int index ) { return elements[ index ]; } public int size() { return elementCount; } public Enumeration elements() { return new SimpleVectorEnumeration( this ); } private static class SimpleVectorEnumeration implements Enumeration { SimpleVector myVector; int nextElementIndex = 0; public SimpleVectorEnumeration( SimpleVector aVector ) { myVector = aVector; } public boolean hasMoreElements() { return nextElementIndex < myVector.size(); } public Object nextElement() { return myVector.elementAt( nextElementIndex++ ); } } }
Member Class
As a member class, SimpleVectorEnumeration becomes simpler. It has direct access to SimpleVector's fields. This does violate the information hiding of the SimpleVector class. However, the two classes are already tightly coupled. While there seems to be no reason to make SimpleVectorEnumeration a top-level nested class, the implementation simplification as member class is attractive.
public class SimpleVector { Object[] elements = new Object[100]; int elementCount = 0; public void addElement( Object element ) { elements[elementCount++] = element; } public Object elementAt( int index ) { return elements[ index ]; } public int size() { return elementCount; } public Enumeration elements() { return new SimpleVectorEnumeration( ); } private class SimpleVectorEnumeration implements Enumeration { int nextElementIndex = 0; public boolean hasMoreElements() { return nextElementIndex < size(); } public Object nextElement() { return elementAt( nextElementIndex++ ); } } }
Local Class
As a local class the SimpleVectorEnumeration can only be used in the elements() method. This may or may not be desirable. It does make the elements() method harder to read. The one statement in the method gets lost in the declaration of the local class.
public class SimpleVector { Object[] elements = new Object[100]; int elementCount = 0; public void addElement( Object element ) { elements[elementCount++] = element; } public Object elementAt( int index ) { return elements[ index ]; } public int size() { return elementCount; } public Enumeration elements() { class SimpleVectorEnumeration implements Enumeration { int nextElementIndex = 0; public boolean hasMoreElements() { return nextElementIndex < size(); } public Object nextElement() { return elementAt( nextElementIndex++ ); } } return new SimpleVectorEnumeration( ); } }
Anonymous Class
As an anonymous class the SimpleVectorEnumeration can only be used in the elements() method. This may or may not be desirable. This seems to me to be much easier to read than the local class example, as it starts with the return. The last semi-colon seems out of place.
public class SimpleVector { Object[] elements = new Object[100]; int elementCount = 0; public void addElement( Object element ) { elements[elementCount++] = element; } public Object elementAt( int index ) { return elements[ index ]; } public int size() { return elementCount; } public Enumeration elements() { return new Enumeration( ) { int nextElementIndex = 0; public boolean hasMoreElements() { return nextElementIndex < elementCount; } public Object nextElement() { return elements[ nextElementIndex++ ]; } }; } }
Copyright © 1998 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
All rights reserved.
visitors since 20-Oct-98