CATEGORII DOCUMENTE |
Asp | Autocad | C | Dot net | Excel | Fox pro | Html | Java |
Linux | Mathcad | Photoshop | Php | Sql | Visual studio | Windows | Xml |
In Java 1.1 it's possible to place a class definition within another class definition. This is called an inner class. The inner class is a useful feature because it allows you to group classes that logically belong together and to control the visibility of one within the other. However, it's important to understand that inner classes are distinctly different from composition.
Often, while you're learning about them, the need for inner classes isn't immediately obvious. At the end of this section, after all of the syntax and semantics of inner classes have been described, you'll find an example that should make clear the benefits of inner classes.
You create an inner class just as you'd expect: by placing the class definition inside a surrounding class: (See page if you have trouble executing this program.)
//: Parcel1.java
// Creating inner classes
package c07.parcel1;
public class Parcel1
}
class Destination
String readLabel()
}
// Using inner classes looks just like
// using any other class, within Parcel1:
public void ship(String dest)
public static void main(String[] args)
} ///:~
The inner classes, when used inside ship( ), look just like the use of any other classes. Here, the only practical difference is that the names are nested within Parcel1. You'll see in a while that this isn't the only difference.
More typically, an outer class will have a method that returns a handle to an inner class, like this:
//: Parcel2.java
// Returning a handle to an inner class
package c07.parcel2;
public class Parcel2
}
class Destination
String readLabel()
}
public Destination to(String s)
public Contents cont()
public void ship(String dest)
public static void main(String[] args)
} ///:~
If you want to make an object of the inner class anywhere except from within a non-static method of the outer class, you must specify the type of that object as OuterClassName.InnerClassName, as seen in main( ).
So far, inner classes don't seem that dramatic. After all, if it's hiding you're after, Java already has a perfectly good hiding mechanism - just allow the class to be "friendly" (visible only within a package) rather than creating it as an inner class.
However, inner classes really come into their own when you start upcasting to a base class, and in particular to an interface. (The effect of producing an interface handle from an object that implements it is essentially the same as upcasting to a base class.) That's because the inner class can then be completely unseen and unavailable to anyone, which is convenient for hiding the implementation. All you get back is a handle to the base class or the interface, and it's possible that you can't even find out the exact type, as shown here:
//: Parcel3.java
// Returning a handle to an inner class
package c07.parcel3;
abstract class Contents
interface Destination
public class Parcel3
}
protected class PDestination
implements Destination
public String readLabel()
}
public Destination dest(String s)
public Contents cont()
}
class Test
} ///:~
Now Contents and Destination represent interfaces available to the client programmer. (The interface, remember, automatically makes all of its members public.) For convenience, these are placed inside a single file, but ordinarily Contents and Destination would each be public in their own files.
In Parcel3, something new has been added: the inner class PContents is private so no one but Parcel3 can access it. PDestination is protected, so no one but Parcel3, classes in the Parcel3 package (since protected also gives package access; that is, protected is also "friendly"), and the inheritors of Parcel3 can access PDestination. This means that the client programmer has restricted knowledge and access to these members. In fact, you can't even downcast to a private inner class (or a protected inner class unless you're an inheritor), because you can't access the name, as you can see in class Test. Thus, the private inner class provides a way for the class designer to completely prevent any type-coding dependencies and to completely hide details about implementation. In addition, extension of an interface is useless from the client programmer's perspective since the client programmer cannot access any additional methods that aren't part of the public interface class. This also provides an opportunity for the Java compiler to generate more efficient code.
Note that Contents doesn't need to be an abstract class. You could use an ordinary class here as well, but the most typical starting point for such a design is an interface.
What you've seen so far encompasses the typical use for inner classes. In general, the code that you'll write and read involving inner classes will be "plain" inner classes that are simple and easy to understand. However, the design for inner classes is quite complete and there are a number of other, more obscure, ways that you can use them if you choose: inner classes can be created within a method or even an arbitrary scope. There are two reasons for doing this:
As shown previously, you're implementing an interface of some kind so that you can create and return a handle.
You're solving a complicated problem and you want to create a class to aid in your solution, but you don't want it publicly available.
In the following examples, the previous code will be modified to use:
A class defined within a method
A class defined within a scope inside a method
An anonymous class implementing an interface
An anonymous class extending a class that has a non-default constructor
An anonymous class that performs field initialization
An anonymous class that performs construction using instance initialization (anonymous inner classes cannot have constructors)
This will all take place within the package innerscopes. First, the common interfaces from the previous code will be defined in their own files so they can be used in all the examples:
//: Destination.java
package c07.innerscopes;
interface Destination ///:~
The point has been made that Contents could be an abstract class, so here it will be in a more natural form, as an interface:
//: Contents.java
package c07.innerscopes;
interface Contents ///:~
Although it's an ordinary class with an implementation, Wrapping is also being used as a common "interface" to its derived classes:
//: Wrapping.java
package c07.innerscopes;
public class Wrapping
public int value()
} ///:~
You'll notice above that Wrapping has a constructor that requires an argument, to make things a bit more interesting.
The first example shows the creation of an entire class within the scope of a method (instead of the scope of another class):
//: Parcel4.java
// Nesting a class within a method
package c07.innerscopes;
public class Parcel4
public String readLabel()
}
return new PDestination(s);
}
public static void main(String[] args)
} ///:~
The class PDestination is part of dest( ) rather than being part of Parcel4. (Also notice that you could use the class identifier PDestination for an inner class inside each class in the same subdirectory without a name clash.) Therefore, PDestination cannot be accessed outside of dest( ). Notice the upcasting that occurs in the return statement - nothing comes out of dest( ) except a handle to the base class Destination. Of course, the fact that the name of the class PDestination is placed inside dest( ) doesn't mean that PDestination is not a valid object once dest( ) returns.
The next example shows how you can nest an inner class within any arbitrary scope:
//: Parcel5.java
// Nesting a class within a scope
package c07.innerscopes;
public class Parcel5
String getSlip()
}
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()
public static void main(String[] args)
} ///:~
The class TrackingSlip is nested inside the scope of an if statement. This does not mean that the class is conditionally created - it gets compiled along with everything else. However, it's not available outside the scope in which it is defined. Other than that, it looks just like an ordinary class.
The next example looks a little strange:
//: Parcel6.java
// A method that returns an anonymous inner class
package c07.innerscopes;
public class Parcel6 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value()
}; // Semicolon required in this case
}
public static void main(String[] args)
} ///:~
The cont( ) method combines the creation of the return value with the definition of the class that represents that return value! In addition, the class is anonymous - it has no name. To make matters a bit worse, it looks like you're starting out to create a Contents object:
return new Contents()
but then, before you get to the semicolon, you say, "But wait, I think I'll slip in a class definition":
return new Contents() {
private int i = 11;
public int value()
};
What this strange syntax means is "create an object of an anonymous class that's inherited from Contents." The handle returned by the new expression is automatically upcast to a Contents handle. The anonymous inner class syntax is a shorthand for:
class MyContents extends Contents {
private int i = 11;
public int value()
}
return new MyContents();
In the anonymous inner class, Contents is created using a default constructor. The following code shows what to do if your base class needs a constructor with an argument:
//: Parcel7.java
// An anonymous inner class that calls the
// base-class constructor
package c07.innerscopes;
public class Parcel7
}; // Semicolon required
}
public static void main(String[] args)
} ///:~
That is, you simply pass the appropriate argument to the base-class constructor, seen here as the x passed in new Wrapping(x). An anonymous class cannot have a constructor where you would normally call super( ).
In both of the previous examples, the semicolon doesn't mark the end of the class body (as it does in C++). Instead, it marks the end of the expression that happens to contain the anonymous class. Thus, it's identical to the use of the semicolon everywhere else.
What happens if you need to perform some kind of initialization for an object of an anonymous inner class? Since it's anonymous, there's no name to give the constructor so you can't have a constructor. You can, however, perform initialization at the point of definition of your fields:
//: Parcel8.java
// An anonymous inner class that performs
// initialization. A briefer version
// of Parcel5.java.
package c07.innerscopes;
public class Parcel8 {
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel()
};
}
public static void main(String[] args)
} ///:~
If you're defining an anonymous inner class and want to use an object that's defined outside the anonymous inner class, the compiler requires that the outside object be final. This is why the argument to dest( ) is final. If you forget, you'll get a compile-time error message.
As long as you're simply assigning a field, the above approach is fine. But what if you need to perform some constructor-like activity? With Java 1.1 instance initialization, you can, in effect, create a constructor for an anonymous inner class:
//: Parcel9.java
// Using 'instance initialization' to perform
// construction on an anonymous inner class
package c07.innerscopes;
public class Parcel9
private String label = dest;
public String readLabel()
};
}
public static void main(String[] args)
} ///:~
Inside the instance initializer you can see code that couldn't be executed as part of a field initializer (that is, the if statement). So in effect, an instance initializer is the constructor for an anonymous inner class. Of course, it's limited; you can't overload instance initializers so you can have only one of these constructors.
So far, it appears that inner classes are just a name-hiding and code-organization scheme, which is helpful but not totally compelling. However, there's another twist. When you create an inner class, objects of that inner class have a link to the enclosing object that made them, and so they can access the members of that enclosing object - without any special qualifications. In addition, inner classes have access rights to all the elements in the enclosing class. The following example demonstrates this:
//: Sequence.java
// Holds a sequence of Objects
interface Selector
public class Sequence
public void add(Object x)
}
private class SSelector implements Selector
public Object current()
public void next()
}
public Selector getSelector()
public static void main(String[] args)
}
} ///:~
The Sequence is simply a fixed-sized array of Object with a class wrapped around it. You call add( ) to add a new Object to the end of the sequence (if there's room left). To fetch each of the objects in a Sequence, there's an interface called Selector, which allows you to see if you're at the end( ), to look at the current( ) Object, and to move to the next( ) Object in the Sequence. Because Selector is an interface, many other classes can implement the interface in their own ways, and many methods can take the interface as an argument, in order to create generic code.
Here, the SSelector is a private class that provides Selector functionality. In main( ), you can see the creation of a Sequence, followed by the addition of a number of String objects. Then, a Selector is produced with a call to getSelector( ) and this is used to move through the Sequence and select each item.
At first, the creation of SSelector looks like just another inner class. But examine it closely. Note that each of the methods end( ), current( ), and next( ) refer to o, which is a handle that isn't part of SSelector, but is instead a private field in the enclosing class. However, the inner class can access methods and fields from the enclosing class as if they owned them. This turns out to be very convenient, as you can see in the above example.
So an inner class has access to the members of the enclosing class. How can this happen? The inner class must keep a reference to the particular object of the enclosing class that was responsible for creating it. Then when you refer to a member of the enclosing class, that (hidden) reference is used to select that member. Fortunately, the compiler takes care of all these details for you, but you can also understand now that an object of an inner class can be created only in association with an object of the enclosing class. The process of construction requires the initialization of the handle to the object of the enclosing class, and the compiler will complain if it cannot access the handle. Most of the time this occurs without any intervention on the part of the programmer.
To understand the meaning of static when applied to inner classes, you must remember that the object of the inner class implicitly keeps a handle to the object of the enclosing class that created it. This is not true, however, when you say an inner class is static. A static inner class means:
You don't need an outer-class object in order to create an object of a static inner class.
You can't access an outer-class object from an object of a static inner class.
There are some restrictions: static members can be at only the outer level of a class, so inner classes cannot have static data or static inner classes.
If you don't need to create an object of the outer class in order to create an object of the inner class, you can make everything static. For this to work, you must also make the inner classes static:
//: Parcel10.java
// Static inner classes
package c07.parcel10;
abstract class Contents
interface Destination
public class Parcel10 {
private static class PContents
extends Contents {
private int i = 11;
public int value()
}
protected static class PDestination
implements Destination
public String readLabel()
}
public static Destination dest(String s)
public static Contents cont()
public static void main(String[] args)
} ///:~
In main( ), no object of Parcel10 is necessary; instead you use the normal syntax for selecting a static member to call the methods that return handles to Contents and Destination.
Normally you can't put any code inside an interface, but a static inner class can be part of an interface. Since the class is static it doesn't violate the rules for interfaces - the static inner class is only placed inside the namespace of the interface:
//: IInterface.java
// Static inner classes inside interfaces
class IInterface
void f()
}
} ///:~
Earlier in the book I suggested putting a main( ) in every class to act as a test bed for that class. One drawback to this is the amount of extra code you must carry around. If this is a problem, you can use a static inner class to hold your test code:
//: TestBed.java
// Putting test code in a static inner class
class TestBed {
TestBed()
void f()
public static class Tester
}
} ///:~
This generates a separate class called TestBed$Tester (to run the program you say java TestBed$Tester). You can use this class for testing, but you don't need to include it in your shipping product.
If you need to produce the handle to the outer class object, you name the outer class followed by a dot and this. For example, in the class Sequence.SSelector, any of its methods can produce the stored handle to the outer class Sequence by saying Sequence.this. The resulting handle is automatically the correct type. (This is known and checked at compile time, so there is no run-time overhead.)
Sometimes you want to tell some other object to create an object of one of its inner classes. To do this you must provide a handle to the other outer class object in the new expression, like this:
//: Parcel11.java
// Creating inner classes
package c07.parcel11;
public class Parcel11
}
class Destination
String readLabel()
}
public static void main(String[] args)
} ///:~
To create an object of the inner class directly, you don't follow the same form and refer to the outer class name Parcel11 as you might expect, but instead you must use an object of the outer class to make an object of the inner class:
Parcel11.Contents c = p.new Contents();
Thus, it's not possible to create an object of the inner class unless you already have an object of the outer class. This is because the object of the inner class is quietly connected to the object of the outer class that it was made from. However, if you make a static inner class, then it doesn't need a handle to the outer class object.
Because the inner class constructor must attach to a handle of the enclosing class object, things are slightly complicated when you inherit from an inner class. The problem is that the "secret" handle to the enclosing class object must be initialized, and yet in the derived class there's no longer a default object to attach to. The answer is to use a syntax provided to make the association explicit:
//: InheritInner.java
// Inheriting an inner class
class WithInner {
class Inner
}
public class InheritInner
extends WithInner.Inner {
//! InheritInner() // Won't compile
InheritInner(WithInner wi)
public static void main(String[] args)
} ///:~
You can see that InheritInner is extending only the inner class, not the outer one. But when it comes time to create a constructor, the default one is no good and you can't just pass a handle to an enclosing object. In addition, you must use the syntax
enclosingClassHandle.super();
inside the constructor. This provides the necessary handle and the program will then compile.
What happens when you create an inner class, then inherit from the enclosing class and redefine the inner class? That is, is it possible to override an inner class? This seems like it would be a powerful concept, but "overriding" an inner class as if it were another method of the outer class doesn't really do anything:
//: BigEgg.java
// An inner class cannot be overriden
// like a method
class Egg
}
private Yolk y;
public Egg()
}
public class BigEgg extends Egg
}
public static void main(String[] args)
} ///:~
The default constructor is synthesized automatically by the compiler, and this calls the base-class default constructor. You might think that since a BigEgg is being created, the "overridden" version of Yolk would be used, but this is not the case. The output is:
New Egg()
Egg.Yolk()
This example simply shows that there isn't any extra inner class magic going on when you inherit from the outer class. However, it's still possible to explicitly inherit from the inner class:
//: BigEgg2.java
// Proper inheritance of an inner class
class Egg2
public void f()
}
private Yolk y = new Yolk();
public Egg2()
public void insertYolk(Yolk yy)
public void g()
}
public class BigEgg2 extends Egg2
public void f()
}
public BigEgg2()
public static void main(String[] args)
} ///:~
Now BiggEgg2.Yolk explicitly extends Egg2.Yolk and overrides its methods. The method insertYolk( ) allows BigEgg2 to upcast one of its own Yolk objects into the y handle in Egg2, so when g( ) calls y.f( ) the overridden version of f( ) is used. The output is:
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
The second call to Egg2.Yolk( ) is the base-class constructor call of the BigEgg2.Yolk constructor. You can see that the overridden version of f( ) is used when g( ) is called.
Since every class produces a .class file that holds all the information about how to create objects of this type (this information produces a meta-class called the Class object), you might guess that inner classes must also produce .class files to contain the information for their Class objects. The names of these files/classes have a strict formula: the name of the enclosing class, followed by a '$', followed by the name of the inner class. For example, the .class files created by InheritInner.java include:
InheritInner.class
WithInner$Inner.class
WithInner.class
If inner classes are anonymous, the compiler simply starts generating numbers as inner class identifiers. If inner classes are nested within inner classes, their names are simply appended after a '$' and the outer class identifier(s).
Although this scheme of generating internal names is simple and straightforward, it's also robust and handles most situations. Since it is the standard naming scheme for Java, the generated files are automatically platform-independent. (Note that the Java compiler is changing your inner classes in all sorts of other ways in order to make them work.)
At this point you've seen a lot of syntax and semantics describing the way inner classes work, but this doesn't answer the question of why they exist. Why did Sun go to so much trouble to add such a fundamental language feature in Java 1.1? The answer is something that I will refer to here as a control framework.
An application framework is a class or a set of classes that's designed to solve a particular type of problem. To apply an application framework, you inherit from one or more classes and override some of the methods. The code you write in the overridden methods customizes the general solution provided by that application framework to solve your specific problem. The control framework is a particular type of application framework dominated by the need to respond to events; a system that primarily responds to events is called an event-driven system. One of the most important problems in application programming is the graphical user interface (GUI), which is almost entirely event-driven. As you will see in Chapter 13, the Java 1.1 AWT is a control framework that elegantly solves the GUI problem using inner classes.
To see how inner classes allow the simple creation and use of control frameworks, consider a control framework whose job is to execute events whenever those events are "ready." Although "ready" could mean anything, in this case the default will be based on clock time. What follows is a control framework that contains no specific information about what it's controlling. First, here is the interface that describes any control event. It's an abstract class instead of an actual interface because the default behavior is control based on time, so some of the implementation can be included here:
//: Event.java
// The common methods for any control event
package c07.controller;
abstract public class Event
public boolean ready()
abstract public void action();
abstract public String description();
} ///:~
The constructor simply captures the time when you want the Event to run, while ready( ) tells you when it's time to run it. Of course, ready( ) could be overridden in a derived class to base the Event on something other than time.
action( ) is the method that's called when the Event is ready( ), and description( ) gives textual information about the Event.
The next file contains the actual control framework that manages and fires events. The first class is really just a "helper" class whose job is to hold Event objects. You could replace it with any appropriate collection, and in Chapter 8 you'll discover other collections that will do the trick without requiring you to write this extra code:
//: Controller.java
// Along with Event, the generic
// framework for all control systems:
package c07.controller;
// This is just a way to hold Event objects.
class EventSet
public Event getNext() while(events[next] == null);
return events[next];
}
public void removeCurrent()
}
public class Controller
public void run()
}
}
} ///:~
EventSet arbitrarily holds 100 Events. (If a "real" collection from Chapter 8 is used here you don't need to worry about its maximum size, since it will resize itself). The index is used to keep track of the next available space, and next is used when you're looking for the next Event in the list, to see whether you've looped around. This is important during a call to getNext( ), because Event objects are removed from the list (using removeCurrent( )) once they're run, so getNext( ) will encounter holes in the list as it moves through it.
Note that removeCurrent( ) doesn't just set some flag indicating that the object is no longer in use. Instead, it sets the handle to null. This is important because if the garbage collector sees a handle that's still in use then it can't clean up the object. If you think your handles might hang around (as they would here), then it's a good idea to set them to null to give the garbage collector permission to clean them up.
Controller is where the actual work goes on. It uses an EventSet to hold its Event objects, and addEvent( ) allows you to add new events to this list. But the important method is run( ). This method loops through the EventSet, hunting for an Event object that's ready( ) to run. For each one it finds ready( ), it calls the action( ) method, prints out the description( ), and then removes the Event from the list.
Note that so far in this design you know nothing about exactly what an Event does. And this is the crux of the design; how it "separates the things that change from the things that stay the same." Or, to use my term, the "vector of change" is the different actions of the various kinds of Event objects, and you express different actions by creating different Event subclasses.
This is where inner classes come into play. They allow two things:
To express the entire implementation of a control-framework application in a single class, thereby encapsulating everything that's unique about that implementation. Inner classes are used to express the many different kinds of action( ) necessary to solve the problem. In addition, the following example uses private inner classes so the implementation is completely hidden and can be changed with impunity.
Inner classes keep this implementation from becoming awkward, since you're able to easily access any of the members in the outer class. Without this ability the code might become unpleasant enough that you'd end up seeking an alternative.
Consider a particular implementation of the control framework designed to control greenhouse functions. Each action is entirely different: turning lights, water, and thermostats on and off, ringing bells, and restarting the system. But the control framework is designed to easily isolate this different code. For each type of action you inherit a new Event inner class, and write the control code inside of action( ).
As is typical with an application framework, the class GreenhouseControls is inherited from Controller:
//: GreenhouseControls.java
// This produces a specific application of the
// control system, all in a single class. Inner
// classes allow you to encapsulate different
// functionality for each type of event.
package c07.controller;
public class GreenhouseControls
extends Controller
public void action()
public String description()
}
private class LightOff extends Event
public void action()
public String description()
}
private class WaterOn extends Event
public void action()
public String description()
}
private class WaterOff extends Event
public void action()
public String description()
}
private class ThermostatNight extends Event
public void action()
public String description()
}
private class ThermostatDay extends Event
public void action()
public String description()
}
// An example of an action() that inserts a
// new one of itself into the event list:
private int rings;
private class
public void action()
public String description()
}
private class Restart extends Event
public void action()
public String description()
}
public static void main(String[] args)
} ///:~
Note that light, water, thermostat, and rings all belong to the outer class GreenhouseControls, and yet the inner classes have no problem accessing those fields. Also, most of the action( ) methods also involve some sort of hardware control, which would most likely involve calls to non-Java code.
Most of the Event
classes look similar, but
Restart is responsible for initializing the system, so it adds all the appropriate events. Of course, a more flexible way to accomplish this is to avoid hard-coding the events and instead read them from a file. (An exercise in Chapter 10 asks you to modify this example to do just that.) Since Restart( ) is just another Event object, you can also add a Restart object within Restart.action( ) so that the system regularly restarts itself. And all you need to do in main( ) is create a GreenhouseControls object and add a Restart object to get it going.
This example should move you a long way toward appreciating the value of inner classes, especially when used within a control framework. However, in the latter half of Chapter 13 you'll see how elegantly inner classes are used to describe the actions of a graphical user interface. By the time you finish that section you should be fully convinced.
This is very different from the design of nested classes in C++, which is simply a name-hiding mechanism. There is no link to an enclosing object and no implied permissions in C++.
On the other hand, '$' is a meta-character to the Unix shell and so you'll sometimes have trouble when listing the .class files. This is a bit strange coming from Sun, a Unix-based company. My guess is that they weren't considering this issue, but instead thought you'd naturally focus on the source-code files.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 656
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved