Chapter 7
IN THIS CHAPTER
Using inner classes
Creating static inner classes
Implementing anonymous classes
Adding lambda expressions
In this chapter, you find out how to use three advanced types of classes: inner classes, static inner classes, and anonymous inner classes. All three types are useful in certain circumstances. In particular, inner classes and anonymous inner classes are commonly used with graphical applications created with JavaFX. For more information about JavaFX, refer to Book 6. In this chapter, I concentrate on the mechanics of creating these types of classes.
You’ll also learn about a feature that was introduced with Java 8 called lambda expressions, which simplify the task of creating and using anonymous classes.
An inner class is a class that’s declared inside another class. Thus the basic structure for creating an inner class is as follows:
class outerClassName
{
private class innerClassName
{
// body of inner class
}
}
The class that contains the inner class is called an outer class. You can use a visibility modifier with the inner class to specify whether the class should be public
, protected
, private-package
, or private
. This visibility determines whether other classes can see the inner class.
At the surface, an inner class is simply a class that’s contained inside another class, but there’s more to it than that. Here are some key points about inner classes:
One of the main reasons for creating an inner class is to create a class-that’s of interest only to the outer class. As a result, you usually declare inner classes to be private so that other classes can’t access them.
this
. If the outer class is named MyOuterClass
, for example, you would use MyOuterClass.this
to refer to the instance of the outer class.Book 3, Chapter 5 introduces an application that uses the Timer
class in the Swing package (javax.swing.Timer
) to display the lines Tick…
and Tock…
on the console at one-second intervals. It uses a class named Ticker
that implements the ActionListener
interface to handle the Timer
object’s clock events.
Listing 7-1 shows a version of this application that implements the Ticker
class as an inner class.
LISTING 7-1 Tick Tock with an Inner Class
import java.awt.event.*;
import javax.swing.*;
public class TickTockInner
{
private String tickMessage = "Tick…";→6
private String tockMessage = "Tock…";→7
public static void main(String[] args)
{
TickTockInner t = new TickTockInner();→11
t.go();→12
}
private void go()→15
{
// create a timer that calls the Ticker class
// at one second intervals
Timer t = new Timer(1000, new Ticker());→19
t.start();
// display a message box to prevent the
// program from ending immediately
JOptionPane.showMessageDialog(null,→24
"Click OK to exit program");
System.exit(0);→26
}
class Ticker implements ActionListener→29
{
private boolean tick = true;
public void actionPerformed(ActionEvent event)→33
{
if (tick)
{
System.out.println(tickMessage);→37
}
else
{
System.out.println(tockMessage);→41
}
tick = !tick;
}
}
}
The following paragraphs describe some of the highlights of this program:
String
variables named tickMessage
and tockMessage
(line 7) contain the messages to be printed on the console. Note that these variables are defined as fields of the outer class. As you’ll see, the inner class Ticker
is able to access these fields directly.main
method. As a result, the main
method in this program simply creates an instance of the application class (TickTockInner
).go
method of the new instance of the TickTockInner
class.
The technique used in lines 11 and 12 is a fairly common programming technique that lets an application get out of a static context quickly and into an object-oriented mode.
go
method, called from line 12.Timer
class with the timer interval set to 1,000 milliseconds (1 second) and the ActionListener
set to a new instance of the inner class named Ticker
.JOptionPane
class is used to display a dialog box. This dialog box is necessary to give the timer a chance to run. The application ends when the user clicks OK.exit
method of the System
class, which immediately shuts down the Java Virtual Machine. This method call isn’t strictly required here, but if you leave it out, the timer continues to run for a few seconds after you click OK before the JVM figures out that it should kill the timer.Ticker
. Note that this class implements the ActionListener
interface.actionPerformed
method is called by the Timer
object every 1,000 milliseconds.A static inner class is similar to an inner class but doesn’t require an instance of the outer class. Its basic form is the following:
class outerClassName
{
private static class innerClassName
{
// body of inner class
}
}
Like a static method, a static inner class can’t access any nonstatic fields or methods in its outer class. It can access static fields or methods, however.
Listing 7-2 shows a version of the Tick Tock
application that uses a static inner class rather than a regular inner class.
LISTING 7-2 Tick Tock with a Static Inner Class
import java.awt.event.*;
import javax.swing.*;
public class TickTockStatic
{
private static String tickMessage = "Tick…";→6
private static String tockMessage = "Tock…";→7
public static void main(String[] args)
{
TickTockStatic t = new TickTockStatic();
t.go();
}
private void go()
{
// create a timer that calls the Ticker class
// at one second intervals
Timer t = new Timer(1000, new Ticker());
t.start();
// display a message box to prevent the
// program from ending immediately
JOptionPane.showMessageDialog(null,
"Click OK to exit program");
System.exit(0);
}
static class Ticker implements ActionListener→29
{
private boolean tick = true;
public void actionPerformed(
ActionEvent event)
{
if (tick)
{
System.out.println(tickMessage);
}
else
{
System.out.println(tockMessage);
}
tick = !tick;
}
}
}
This version of the application and the Listing 7-1 version have only three differences:
tickMessage
field is declared as static. This is necessary so that the static class can access it.tockMessage
field is also declared as static.Ticker
class is declared as static.Anonymous inner classes (usually just called anonymous classes) are probably the strangest feature of the Java programming language. The first time you see an anonymous class, you’ll almost certainly think that someone made a mistake and that the code can’t possibly compile. But compile it does, and it even works. When you get the hang of working with anonymous classes, you’ll wonder how you got by without them.
An anonymous class is a class that’s defined on the spot, right at the point where you want to instantiate it. Because you code the body of the class right where you need it, you don’t have to give it a name. (That’s why it’s called an anonymous class.)
The basic form for declaring and instantiating an anonymous class is this:
new ClassOrInterface() { class-body }
As you can see, you specify the new
keyword followed by the name of a class or interface that specifies the type of the object created from the anonymous class. This class or interface name is followed by parentheses, which may include a parameter list that’s passed to the constructor of the anonymous class. Then you code a class body enclosed in braces. This class body can include anything that a regular class body can include: fields, methods, and even other classes or interfaces.
Here’s an example of a simple anonymous class:
public class AnonClass
{
public static void main(String[] args)
{
Ball b = new Ball()
{
public void hit()
{
System.out.println("You hit it!");
}
};
b.hit();
}
interface Ball
{
void hit();
}
}
In this example, I create an interface named Ball
that has a single method named hit
. Then, back in the main
method, I declare a variable of type Ball
and use an anonymous class to create an object. The body of the anonymous class consists of an implementation of the hit
method that simply displays the message You hit it!
on the console. After the anonymous class is instantiated and assigned to the b
variable, the next statement calls the hit
method.
When you run this program, the single line You hit it!
is displayed on the console.
Here are some things to ponder when you work with anonymous classes:
Listing 7-3 shows a more complex example of an anonymous class: a version of the Tick Tock
application that uses an anonymous class as the action listener for the timer.
LISTING 7-3 Tick Tock with an Anonymous Class
import java.awt.event.*;
import javax.swing.*;
public class TickTockAnonymous
{
private String tickMessage = "Tick…";
private String tockMessage = "Tock…";
public static void main(String[] args)→9
{
TickTockAnonymous t = new TickTockAnonymous();
t.go();
}
private void go()
{
// create a timer that calls the Ticker class
// at one second intervals
Timer t = new Timer(1000,→19
new ActionListener()→20
{→21
private boolean tick = true;
public void actionPerformed(→23
ActionEvent event)
{
if (tick)
{
System.out.println(tickMessage);
}
else
{
System.out.println(tockMessage);
}
tick = !tick;
}
} );→36
t.start();
// display a message box to prevent the
// program from ending immediately
JOptionPane.showMessageDialog(null,
"Click OK to exit program");
System.exit(0);
}
}
By now, you’ve seen enough versions of this program that you should understand how it works. The following paragraphs explain how this version uses an anonymous class as the ActionListener
parameter supplied to the Timer
constructor:
main
method creates an instance of the TickTockAnonymous
class and executes the go
method.go
method, an instance of the Timer
class is created.TimerClass
constructor is an object that implements the ActionListener
interface. This object is created here via an anonymous class. ActionListener
is specified as the type for this class.actionPerformed
method is called every 1,000 milliseconds by the timer. Note that this method can freely access fields defined in the outer class.Timer
constructor. The left parenthesis that’s paired with this right parenthesis is on line 19. Finally, the semicolon marks the end of the assignment statement that started on line 19.A lambda expression is a new feature that in some ways is similar to anonymous classes, but with more concise syntax. More specifically, a lambda expression lets you create an anonymous class that implements a specific type of interface called a functional interface — which has one and only one abstract method.
The Ball
interface that was presented in the previous section meets that definition:
interface Ball
{
void hit();
}
Here the only abstract method is the hit
method.
A lambda expression is a concise way to create an anonymous class that implements a functional interface. Instead of providing a formal method declaration that includes the return type, method name, parameter types, and method body, you simply define the parameter types and the method body. The Java compiler infers the rest based on the context in which you use the lambda expression.
The parameter types are separated from the method body by a new operator, called the arrow operator, which consists of a hyphen followed by a greater-than symbol. Here’s an example that implements the Ball
interface:
() -> { System.out.println("You hit it!");}
Here the lambda expression implements a functional interface whose single method does not accept parameters. When the method is called, the text "You hit it!"
is printed.
You can use a lambda expression anywhere you can use a normal Java expression. You’ll use it most in assignment statements or as passed parameters. The only restriction is that you can use a lambda expression only in a context that requires an instance of a functional interface. For example, here’s a complete program that uses a lambda expression to implement the Ball
interface:
public class LambdaBall
{
public static void main(String[] args)
{
Ball b = () -> { System.out.println("You hit it!"); };
b.hit();
}
interface Ball
{
void hit();
}
}
The general syntax for a lambda expression is this:
(parameters) -> expression
or this:
(parameters) -> { statement;…}
If you use an expression, a semicolon is not required. If you use one or more statements, the statements must be enclosed in curly braces and a semicolon is required at the end of each statement.
Note also that if the functional interface that the lambda is based on returns a value (not void), the value of expression in the first syntax is used as the return value. In the second syntax, in which one or more statements are enclosed in a block, you must use a return
statement to return an appropriate value.
Don’t forget that the statement in which you use the lambda expression must itself end with a semicolon. Thus, the lambda expression in the previous example has two semicolons in close proximity:
Ball b = () -> { System.out.println("You hit it!"); };
The first semicolon marks the end of the statement that calls System.out.println
; the second semicolon marks the end of the assignment statement that assigns the lambda expression to the variable b
.