Procedures

Goals

Concepts

Language

Javadoc

Library

Lesson

At the beginning of this course you learned that Java, like the majority of modern programming languages, allows you to group commonly used instructions together into procedures. Rather than “repeating yourself” with duplicated code, you can invoke or call a procedure. Procedures form the basis of procedural programming, providing a layer of indirection that reduces redundancy and make your program much more flexible. Java calls its procedures methods, and every one of them must be defined inside a class. The first method you studied looked like this:

A method for printing a welcome in the HelloWorld.java program.
package com.example;

public class HelloWorld
{
  public static void main(String[] args)
  {
    printWelcome();
    System.out.println("Don't hesitate to ask the teacher if you have any questions.");
    printWelcome();
  }

  /**
   * Prints a welcome to the students in the course.
   */
  static void printWelcome()
  {
    System.out.println("Welcome!");  //welcome the students
    System.out.println("Good luck in the course.");
  }
}

Parameters

Procedures, that is Java methods, gain a large part of their flexibility by allowing you to designate input variables, which are called parameters. When you declare a method you can specify parameters as a comma-separated list of variables, along with their types, between the parentheses that must follow the method name. Each time you call the method, the code invoking the method (the caller) will provide values within parentheses in the same order as the method specifies. Java will copy the values you provide and pass them to the method as arguments. For example you might declare a method indicating int count to determine the number of times to print a welcome message.

Adding parameters to a method.
…
public static void main(final String[] args)
{
  printWelcome(1);  //prints one welcome message
  System.out.println("Don't hesitate to ask the teacher if you have any questions.");
  printWelcome(2);  //prints two welcome messages
}

/**
 * Prints a welcome to the students in the course.
 * @param count The number of times to print the message.
 */
static void printWelcome(final int count)
{
  for(int n = 1; n <= count; n++) {
    System.out.println("Welcome!");  //welcome the students
    System.out.println("Good luck in the course.");
  }
}
…
Welcome!
Good luck in the course.
Don't hesitate to ask the teacher if you have any questions.
Welcome!
Good luck in the course.
Welcome!
Good luck in the course.

You can declare more than one parameter, which include object references as well. And you are not limited to using literal values as arguments. If you have a value stored in a variable, you can use the variable as a method argument as well. Java will copy the value from your variable to the parameter variable when it invokes the method. Thus it is said that Java uses pass by value for method parameters. It is impossible for a method to change the original value of a variable passed as an argument, as the method only receives a copy of that value. This is true regardless of whether the argument and/or parameter values are declared as final. It is still a good idea to use final, of course, to prevent code within each scope from modifying the variables.

Passing variables and objects as arguments.
…
public static void main(final String[] args)
{
  String greeting = "Hello and welcome!"
  int welcomeCount = 5;
  printWelcome(greeting, welcomeCount);  //prints five welcome messages
  System.out.println(welcomeCount);  //prints "5"
}

static void printWelcome(String greeting, int count)
{
  for(int n = 1; n <= count; n++) {
    System.out.println(greeting);
    System.out.println("Good luck in the course.");
  }
  count = 10;	//only affects the value of the parameter inside the method
}
…

If you recall that main(…) is itself a method, it should be obvious that methods can call other methods.

Calling methods from other methods.
…
public static void main(final String[] args)
{
  printWelcome("Welcome!", 2);  //prints two welcome messages with instructions
}

static void printWelcome(final String greeting, final int count)
{
  for(int n = 1; n <= count; n++) {
    System.out.println(greeting);
    printInstructions();
  }
}

static void printInstructions()
{
  System.out.println("Always complete your homework on time.");
}

…
Welcome!
Always complete your homework on time.
Welcome!
Always complete your homework on time.

Method Overloading

The name of a method, along with the number and type of its arguments, comprise a method signature. You can have multiple methods with the same name, as long as the signature is different e.g. by providing a different number of parameters. When you provide multiple methods with the same name, it is called method overloading.

Calling overloaded methods with default values.
…
public static void main(final String[] args)
{
  printWelcome("Welcome!");  //prints one welcome message with instructions
}

static void printWelcome(final String greeting)
{
  printWelcome(greeting, 1);  //default to printing the greeting one time
}

static void printWelcome(final String greeting, final int count)
{
  for(int n = 1; n <= count; n++) {
    System.out.println(greeting);
    printInstructions();
  }
}

static void printInstructions()
{
  System.out.println("Always complete your homework on time.");
}

…
Welcome!
Always complete your homework on time.

Varargs

If you need to pass several values to an array, but you don't know ahead of time how many arguments the caller will want to pass, you can always pass an array. For example, here is a way to print a series of welcome messages if you don't know ahead of time how many messages there are to print.

Passing a variable number of arguments by using an array.
…
public static void main(final String[] args)
{
  printWelcome(new String[]{"Hello!", "Welcome.", "Congratulations!"});
}

static void printWelcome(final String[] messages)
{
  for(final String message : messages) {
    System.out.println(message);
  }
}
…
Hello!
Welcome.
Congratulations!

The problem with this approach is that the caller is forced to create an array of points each time it wishes to call the printWelcome(…) method. In recent versions Java introduced a feature called varargs (for “variable arguments”) which allows you to indicate that the caller may pass some (unknown at compile time) number of arguments when the program is run. Varargs are indicated by adding ... after the type, such as String... instead of String[] in the example above. Behind the scenes, Java will automatically create and pass an array, and the method can treat the argument as it would an array. The benefit is that the caller may simply list the arguments and does not need to manually create an array, as shown below:

Passing a variable number of arguments using varargs.
…
public static void main(final String[] args)
{
  printWelcome("Hello!", "Welcome.", "Congratulations!");
}

static void printWelcome(final String... messages)
{
  for(final String message : messages) {
    System.out.println(message);
  }
}
…
Hello!
Welcome.
Congratulations!

Return Values

A method can optionally return a value to the caller by using the return statement followed by the value to return. This makes the method usable in places where an expression is expected. To declare that the method has a return value, simply place the type of value to return (instead of void) in front of the method. For example you could return the total length of all the strings printed in the printWelcome(…) method:

Returning a value from a method.
…
public static void main(final String[] args)
{
  System.out.println(absoluteValue(5)); //prints "5"
  System.out.println(absoluteValue(-5)); //prints "5"
}

/**
 * Determines the absolute value of a number.
 * If the input number is negative, the positive version of the number
 * will be returned. Otherwise the given number is returned as is.
 * @param value The input value.
 * @return The absolute value of the given input value
 */
static int absoluteValue(final int value)
{
  return value < 0 ? -value : value;
}
…
5
5

Scope

You already know that in Java a set of braces produces a new scope, and a method is no different. Any variables defined inside a method are stored on the stack, and when the method completes these variables will cease to exist.

But methods present another twist to the idea of scope. When one method calls another, the calling method's scope still exists (although normally inaccessible), waiting for the other method to complete. Once the second method returns, the all the variables defined in the first method are as they were left, and the first method can continue.

Below is an illustration of the call stack that is generated from the example Calling methods from other methods earlier in this lesson, as the main() method calls the printWelcome() method, which in turn calls the printInstructions() method. Read the table from the bottom up. This sequence of saved scopes is referred to as the call stack.

Call stack after three levels of method invocations.
Level Method
2 printInstructions()
1 printWelcome(…)
0 main(…)

Review

Gotchas

In the Real World

Short-Circuiting Returns

If you have a method that has a common control flow but still need to test for certain conditions, it is common to use a “short-circuiting” logic to exit early from a method if the conditions are not met. This is similar to short-circuiting evaluation of Boolean expressions, which you learned about a few lessons earlier. Consider a method that prints welcome to a user, but only if that user has logged in.

Checking a condition before performing a task in a method.
/**
 * Prints a welcome if a user is logged in.
 * If a user is not logged in, nothing is printed.
 * @param username The user's login ID,
 *     or <code>null</code> if the user is not logged in.
 */
static void printWelcome(final String username)
{
  if(username != null) //make sure the user is logged in
  {
    System.out.println(String.format("Welcome, %s!", username));
  }
}

That  would work just fine. But to make the typical flow of control more explicit, it is common to check for the negative situation and exit the method as a way of short-circuiting the logic. The following might make the intended purpose and common control flow of the method clearer.

Short-circuiting a method by testing a negative condition.
/**
 * Prints a welcome if a user is logged in.
 * If a user is not logged in, nothing is printed.
 * @param username The user's login ID,
 *     or <code>null</code> if the user is not logged in.
 */
static void printWelcome(final String username)
{
  if(username == null)  //if the user is not logged in
  {
    return;  //short-circuit the method
  }
  else //"else" logically not needed
  {
    System.out.println(String.format("Welcome, %s!", username));
  }
}

Note that “else” is not even needed, because if the first condition holds, program control will have exited, so control passing beyond the condition would only happen if the condition did not hold, which is exactly what else means.

Short-circuiting a method by testing a negative condition.
/**
 * Prints a welcome if a user is logged in.
 * If a user is not logged in, nothing is printed.
 * @param username The user's login ID,
 *     or <code>null</code> if the user is not logged in.
 */
static void printWelcome(final String username)
{
  if(username == null)  //if the user is not logged in
  {
    return;  //short-circuit the method
  }
  System.out.println(String.format("Welcome, %s!", username)); 
}

Think About It

Self Evaluation

Task

In your Booker program you printed information about each of several books. Some day you may wish you reuse this printing procedure elsewhere in your program. You may also find the logic easier to understand if printing logic is grouped separately.

Therefore create a Java method for printing a single book. Then, for each book, call this method, passing it all the information to print that book.

See Also

References