HomeJava Studying for the Java Programmer certification
Studying for the Java Programmer certification
Written by Mike Noel
Thursday, 11 August 2005
Page 3 of 4
Chapter 7: Nested Classes and Interfaces
This chapter covers the complexities of static and non-static nested classes in a lot of detail. A nested class is simply a class defined inside another class. When applied to a nested class the static or non-static label has the same sort of semantics that is has when applied to other members of a class. Java also allows for nested interfaces, local classes (basically, nested within a block of code), and anonymous nested classes (classes without a name). The topics listed below explain some of the details concerning these topics.
Constructing non-static member classes
A non-static member class is a class defined within the context of
another class. Since the classes are non-static they must have an
associated encapsulating object. The standard class constructor syntax:
MyClass myClass = new MyClass();
Doesn't work because there is no way to specify the encapsulating
object. Suppose that MyClass is a non-static member of the
TopClass class. Since it's non-static there must be a
TopClass object associated with any instance of a
MyClass object. The constructor shown above doesn't enforce
that requirement.
Doesn't work because there is no way to specify the encapsulating
object. Suppose that MyClass is a non-static member of the
TopClass class. Since it's non-static there must be a
TopClass object associated with any instance of a
MyClass object. The constructor shown above doesn't enforce
that requirement.
A new form of a class constructor is required for non-static member
classes. The basic form of this constructor is:
Notice that an OuterClass object, referred to here by
outerClassRef is required, not just the outer class name. When
the constructor is called from within an outer class method the
this keyword can be used in place of the outerClassRef. This
examples shows this:
class OuterClass {
// ...
public InnerClass makeInnerObject() {
InnerClass innerRef = this.new InnerClass();
return(innerRef);
}
class InnerClass {
// ... inner class details
}
}
The makeInnerObject() method is not static. It must be called
within the context of an OuterClass object. When this method is
called a new InnerClass object is created and a reference to it is
returned. As a shorthand the this keyword can be omitted.
This results in:
// ...
public InnerClass makeInnerObject() {
InnerClass innerRef = new InnerClass();
return(innerRef);
}
// ...
With the this keyword dropped this constructor call doesn't
look any different than any other class constructor invocation.
Accessing members of sibling nested classes
It seems intuitive that a nested class would have access to all of the
fields and methods of the enclosing class. What doesn't seem
intuitive is the fact that member classes have access to all of the
fields and methods, regardless of access specifier, of all other
nested classes. There is no access hierarchy when it comes to
accessing sibling nested classes.
Multiple non-static member class objects in a single top-level
object
Non-static member classes are instantiated in the context of an
enclosing object (see the section above for a description of this).
It is tempting to think of a class as a "type" and then assume that
there can exist only one nested class instance for a given outer class.
This isn't true. It is possible to have any number of inner class
objects associated with the same outer class object. The states of
each of the internal objects are distinct (but they have access to
each other). They are completely different objects.
Local classes and formal parameters
It is hard to explain this issue simply but I'll try. A local class
is a class declared in a block. Typical blocks in a program are
method bodies and if/for/while statement bodies. Just as local
variables can be declared in any of these blocks, classes can be
declared in this blocks. The scope of these classes extends from the
point of declaration through the end of the block. Again, just as
with local variables. A typical local class might look like this:
class TopLevel {
// ... other class stuff
public Object myMethod(String p) {
class LocalClass {
// ... local class stuff
public void toString() {
return("Value = " + p);
}
}
return(new LocalClass());
}
}
This example can look complicated so I'll explain it a bit. First of
all, there is a top level class which contains a method
myMethod. This method takes a String parameter and returns an
instance of LocalClass. The LocalClass type is not
visible outside of the myMethod method so only an Object
ref can be passed back to the client. For this example the
toString() method is overridden in LocalClass. So far
this is confusing but reasonable. Pay attention to the parameter
p. Notice that when myMethod is called a value for
p is supplied. It is not used anywhere in the method but it is
referenced by the toString() method of the local class. By the
time the toString() method is called the myMethod() call
will be completed. However, the value of p when the class was
declared will be preserved and used.
Accessing hidden enclosing object fields
An inner class can hide a field from the enclosing class by declaring
a new field by the same name. A special form of the this
syntax is required to access the hidden field.
class TopLevel {
int f = 3;
class Inner {
int f = 5; // hides the top-level field
void func() {
System.out.println(f); // (1)
System.out.println(TopLevel.this.f) // (2)
}
}
}
The code at (1) prints the value of the visible field. That is, it
prints "5". The code at (2) accesses the hidden field. It prints "3".
Chapter 8: Object Lifetime
Static fields can be initialized before declared
It is common to see a field declaration combined with an initial value
assignment. This is typically of the form:
class MyClass {
int x = 10;
// methods follow this
}
The member x is given an initial value when a MyClass object is
constructed. There is a similar form for static members. In
those cases the static field is initialized when the class is
initialized. An unexpected form of static field initializatoim allows
the static field to be assigned a value before it is declared.
The following example illustrates this:
class MyClass {
static int x = y = 10; // (1)
static int y; // (2)
}
Notice the use assignment of the value "10" to the variable "y" at (1)
before the static field "y" is declared at (2). This same behavior
can be seen when using a static initializer block.
class MyClass {
static {
y = 10; // (1)
}
// Other methods
static int y; // (2)
}
Chapter 9: Threads
A important feature of the Java language is the built-in support for
threads. Constructs in the language make it relatively easy to create
threads and manage their execution. This chapter covers these
language features and explains some of the issues involved with
multi-threaded Java programs.
Using the yeild(), sleep(), and wait() methods
There are several ways that a running thread can become paused (not
stopped, just paused). The scheduler can decide to pause a thread in
order to enforce it's policies, an IO resource can block a thread and
put it in a paused state, or a thread can try to access a locked
object and end up paused while it waits for the lock to be freed.
These are mostly outside the control of the thread object and occur at
unpredictable points in the execution of the thread. There are three
methods that the thread can use to pause itself directly. These are
the yeild(), sleep(), or wait() methods.
The yeild() method is the simplest of these. This method
simply pauses the thread and puts it back in the ready-to-run state.
The scheduler can decide when to re-run this thread. If there are no
other waiting thread it may decide to run thread immediately.
The sleep(t) method is not much more complicated. This method
is called with a time value specified in milliseconds. The thread
will pause for that period of time and then be put back in to the
ready-to-run state. After that point the scheduler can re-run the
thread at any time. Since the scheduler may not run the newly
awakened thread immediately it is likely that the elapsed time from
the time the sleeping start till the thread is running again will be
greater than the specified time value. Because of this the
sleep() method alone it not a good means of controlling
time-synchronous activity (such as animating graphics).
The wait() method is more involved and is only useful when
dealing with synchronized methods (object locks). In fact, the
wait() method can only be called on an object that is locked by
the calling thread. When a thread calls the wait() method it
releases it's lock on the object and pauses. The fact that it
releases the lock is one of the important distinctions between this
method and the other pausing methods. Neither yeild() or
sleep() release any locks.
start() runs in the current thread
Once a new thread is created it is started by calling the
start() method. The start method will eventually end up
calling the run() method. There is a crucial difference
between these two methods. The start() method is executed in
the same thread as the caller. The run() method is executed in
a newly created thread. Here's an example.
public class Main {
public static void main(String args[]) {
Thread ct = Thread.currentThread();
System.out.println("The main thread is " + ct.toString();
Thread t = new Thread {
public void start() {
System.out.println("Calling start with thread " + this.toString());
}
public void run() {
System.out.println("Calling run with thread " + this.toString());
}
}
t.start();
}
}
This program starts by getting the current (main) thread and printing
out it's name. Next it creates an anonymous class derived from
Thread. This class overrides both the start() and run()
methods. In each case the method prints a short message including the
thread id. The message printed from start() shows that it's
thread is the same as the main thread. The message printed from
run() shows that it's thread is new.