Runnable Interface
A functional interface is a Java interface with a single abstract method, and Runnable is a standard example with one method, run.
Predicting What a Small Program Prints
Before any theory, read a very small Java program and predict what it prints. The program below uses a lambda expression in a place where many Java programmers would otherwise write a named class. Do not worry yet about the arrow symbol. Focus on the variable called greeting and the method call on its last line.
What do you think happens?
What does this program print when greeting.run() is called?
Reveal answer
Hello from a lambda
The variable greeting has the type Runnable, and the lambda on the right supplies the body of Runnable's single method, run(). Calling greeting.run() executes that body, which prints the message.
Functional Interfaces and Their Single Abstract Method
A functional interface is a Java interface with exactly one abstract method. That single method defines the shape of a piece of behavior: what inputs it takes, what it returns, and what it is called.
The interface itself does not say how the behavior is carried out. It only provides a template that some implementation must fill in. Think of the interface as a blank form with one question on it. Whoever fills in the answer decides what the behavior actually does.
To identify a functional interface, count its abstract methods. If there is exactly one, the interface is functional, and a lambda expression can supply a value of that interface type.
Runnable as the Simplest Example
The Runnable interface shows the idea in its simplest form. Its declaration contains exactly one method, named run, which takes no parameters and returns nothing. Because the interface has a single method to implement, a lambda expression can stand in for a complete implementation of it.
public void run()Read this signature as three parts that matter. The method is public, so code outside the class can call it. It returns void, so it produces no value. It takes no parameters, so everything run() needs must be available from the object's own fields or from the lambda's surrounding code. The name run is fixed by the interface; you cannot choose a different name and still satisfy Runnable.
Lambda Expressions as Implementations
Go back to the first program. The empty parentheses on the left stand for the parameter list of run, which has no parameters. The body on the right is the code that runs when run is called. The lambda therefore carries exactly the information the interface template leaves open: the implementation of the one method.
The same pattern works for any interface you define yourself, as long as it has one abstract method. The example below is original and is not from the source. It declares an interface with one method that takes a String and returns a String. Notice that the lambda's parameter list matches the parameter list of that method.
HI!- Transformer has exactly one abstract method, apply, so it is functional.
- The lambda s -> ... has one parameter, matching
apply(String s). - The body computes the return value that apply must produce.
Interface Names as Types
The name of a functional interface is a type, in the same way that String and double are types. This matters because you can use the name to declare variables, to declare parameters, and to specify what a method returns. Once a type is a functional interface, any value of that type can be created by writing a lambda expression instead of a conventional class.
task ran
task ran- The return type Runnable tells callers that
makeTaskproduces a Runnable value. - The lambda returned is a value of type Runnable, so the caller can invoke run on it.
- The second call creates and runs a fresh lambda in one expression.
The type name does the work of connecting the lambda to the method it implements. The caller of makeTask never needs to know that a lambda was used; it only needs to know the value has type Runnable.
Task Versus Thread
Earlier in the chapter, a thread was programmed by defining a subclass of Thread. The second way is to define a class that implements the interface Runnable. Both approaches let you write the code a thread should execute. The difference is in what your class is. With the subclass approach, your class is itself a thread. With the Runnable approach, your class is only a task, and a separate Thread object is the thing that carries out that task.
A Runnable describes work; a Thread object carries that work on its own thread of control. Keeping the task and the thread separate is the central idea of the Runnable approach.
Tracing a Runnable Passed to a Thread
The following class is an original example in the style the source describes. It declares implements Runnable, stores a name in a field, and has a run() method that prints a message using that field. It does not extend Thread and does not call start(). It only describes a task.
Hello, Ada- new
Greeter("Ada")builds the task object; its name field is set to Ada. - new
Thread(...)builds a Thread object and hands it the Runnable. start()creates the new thread, which then calls the task'srun()method.
Follow the path step by step. The Greeter object is created first and holds the name. The Thread constructor accepts that Runnable, so the thread's run() forwards to the Greeter's run(). Nothing new runs yet. The call to start() is the step that creates the separate thread of control, and on that thread the Greeter's run() executes and prints the message.
How the Thread, Task, and run() Method Relate
The Thread class has a constructor that takes a Runnable as its parameter. When you pass an object that implements Runnable to that constructor, the thread's run() method simply calls the run() method of the Runnable you supplied. The thread does not contain the task; it forwards to the task.
start() Versus Calling run() Directly
A common confusion is whether calling run() directly is the same as calling start(). The source makes the difference clear: start() is the call that creates the new thread of control in which run() executes. Calling run() as an ordinary method call does not create a new thread. The method body runs on whichever thread made the call.
Mistakes When Tracing Runnable Programs
When a program using Runnable produces an unexpected result, the cause is usually one of a small number of mistakes. Each one leaves a trace that you can follow back to its source.
Common mistake Expecting output from a Thread that was never started
What it looks likeThread t = new Thread(new Greeter("Ada")); with no t.start() callWhy it failsCreating the Thread object alone does not start any new work.
Do this insteadCall start() on the Thread so that its run() method executes.
Common mistake Assuming run() creates a new thread
What it looks likeCalling greeter.run() directly and expecting a separate thread
Why it failsOnly
start()creates the new thread; a directrun()call executes on the calling thread.Do this insteadUse start() when the work should run on its own thread.
Common mistake Believing the Thread itself contains the task
Why it failsThe thread forwards its
run()to the Runnable you supplied.Do this insteadLook at the Runnable object's run() method to find the printed text.
Common mistake Trying to use a different method name
Why it failsThe name run is fixed by the interface, so a class with a differently named method does not satisfy Runnable.
Practice Tracing a Runnable
Without running the code, trace the following program. Name which thread prints each line and say whether the lines may appear in a different order than written. Then write one line of Java that would make the task run on a new thread.
Hints
- Ask whether
start()is ever called. - Ask which thread runs the task's
run()method when it is called directly.
Key Takeaways
- A functional interface has exactly one abstract method, and Runnable is a standard example whose single method is public void
run(). - A lambda expression can supply the body of a functional interface's method, and the interface name works as a type for variables, parameters, and return values.
- A Runnable describes work; a Thread object carries that work, and its
run()forwards to the Runnable'srun(). start()creates the new thread in whichrun()executes, while callingrun()directly runs on the calling thread.- When tracing output, check whether
start()was called, which thread made each call, and whether the method invoked is the one you expect.