As soon as a program becomes more than a few lines long, it is worth thinking about breaking the code down into small reusable blocks. In early high level languages these blocks of code were known as procedures or functions. In fact the idea that programs should be made up of reusable blocks gave rise to the model (or paradigm) of procedural programming. A collective term for procedures and functions is subroutines, which gives us an idea of how they are to be used: the subroutine is used by the program to complete some small (sub) task. Think of all the tasks that a waiter or waitress needs to complete when a group of customers arrive at a restaurant for a meal,
// restaurant simulator
seatGuests();
showMenu();
takeDrinksOrder();
serveDrinks();
takeStartersOrder();
serveStarters();
takeMainsOrder();
removeMenus();
clearTable();
serveMains();
checkInWithCustomer(); //opportunity to order more drinks
clearTable();
showMenu();
takeDessertsOrder();
removeMenus();
serveDesserts();
clearTable();
checkInWithCustomer(); //opportunity to order more drinks
bringBill();
takePayment();
clearTable();
From the above example, you will note a number of things:
- careful naming of each subroutine makes the program easier to follow
- each subroutine completes a single task
- the same subroutine can be called from several different points in the program (e.g. showMenu, clearTable and checkInWithCustomer)
- to run the subroutine, you simply state its name followed by brackets.
- one subroutine could actually make use of other subroutines, for example checkInWithCustomer could result in takeDrinksOrder and serveDrinks being run
Parameters
You may have noticed that each time we ran a subroutine (we describe this as calling the subroutine), a pair of empty brackets followed the subroutine name. These brackets do not have to be empty, and in fact could contain a value or values to be used by the subroutine. This value can then change the behaviour of the subroutine. These values are stored in a kind of temporary and automatically created variable called a parameter. Using our restaurant example, rather than showing the whole menu we might want the ability to only show one section of the menu, say the starters sections, or the dessert section.
// the parameter here is called section
public void showMenu(char section)
{
if (section=='s'){
System.out.println("Starters:\nBruschetta, Soup, Mozarella & tomatoes");
}
else if (section=='d'){
System.out.println("Dessert:\nIce cream, Sorbet, Tiramasu");
}
else{
System.out.println("Mains:\nPasta, Pizza, Steak");
}
}
To call the subroutine, this time we have to put something inside the brackets of the correct datatype. The thing we put inside the bracket is called the argument. For our example, the subroutine is expecting a char, and any other datatype would cause an error message to be output by the compiler. When the subroutine is called, the argument value is passed into the subroutine which stores it in a parameter, in this case called section.
showMenu('s'); // calls showMenu, passing 's' to section
showMenu('d'); // calls showMenu, passing 'd' to section
Return values
Earlier we said that there were two types of subroutines: procedures and functions. The only difference between the two is that a function returns a value and a procedure doesn’t. If you think of a subroutine as having inputs and outputs, parameters are inputs to the subroutine and return values are outputs from the subroutine. To return a value from a subroutine, you use the keyword return followed by the value you want to return. In most languages return will also exit the subroutine even if there are lines of code that follow the return statement. In fact if you try the following in Java, you will get an error saying that the second line inside splitBill is unreachable.
public double splitBill(int people, double total){
return total/(double)people;
System.out.println("This line will never run");
}
Note the syntax when declaring the subroutine requires you to state the datatype of the return value. In this case the function splitBill returns a value which is a double (probably overkill as a float would do, but the Java compiler complains when casting the integer into a float that precision may be lost when doing the divide).
Note that when calling a function as opposed to a procedure, the call must either store the returned value or use it as an argument to another function/procedure. Using the function from above as an example,
double individualCost;
individualCost=splitBill(3, 137.33); // store value
// if you just want to print the value without storing it
// simply put the function call inside the brackets of a
// print statement, the return value will be used as an
// argument for the print statement.
System.out.print( splitBill(3,137.33) );
Pass by value or reference
Some languages allow you to choose whether parameters being passed to a section of code are passed by value, or by reference.
When passing by value, you are passing a copy of data from the calling code to the receiving code. This means the original data is left intact and cannot be changed. When passing by reference, you are passing the address of where the data is being stored and therefore it is possible for the data being passed to change.
In Java, primitives are always passed by value, and objects (including arrays) always by reference.
Variable scope
Within any program, variables are said to have a scope. This essentially relates to where in the program a variable can be “seen”, or more accurately speaking, accessed. Local variables can only be accessed within the block that they are declared. For example a variable declared in a subroutine can only be accessed inside that subroutine. An even smaller scope might be a variable declared inside a loop, it will not be visible outside the loop, and in fact will be destroyed when the loop is terminated.
A variable that can be seen anywhere in the program is known as a global variable. The problem with global variables is that they could be modified by any part of the code, so maintaining such code is difficult. Also strange bugs can occur if using multithreaded code where one thread is updating a variable’s contents and another thread is trying to read that same variable’s value. For this reason, it is considered bad practice to use global variables in programs unless they are constants (the contents of which obviously never get changed so there is no issue).
Java programs can be written so that the scope of a variable is global to an entire class or global to every class in the entire program (which is usually made up of many classes). The scope is controlled by the keywords public and private.
Object Oriented Programming
In object oriented languages like Java and C++, programs are designed around objects. Objects contain data (formally called attributes) and program code (called methods).
You can think of a method as a function in Java if it returns a value, or a procedure if it doesn’t. Methods in Java that don’t return a value, are expected to return a value of void.
As many programmers learned procedural languages before they learned object oriented languages they often use the term function when they really mean method. In the UK exams system, you will commonly see questions referring to subroutines, procedures or functions. Only in questions specifically about Object Oriented Programming will you see the word method mentioned. Make sure you are aware of how these terms are all related.
Activities
Here is a skeleton program that you can use to get you started on the activities below:
public class FuncsProg{
public int addOne(int numb){
...
}
public int addTwoNumbers(int a, int b){
...
}
}
- Create a function that accepts an integer and returns the same integer with 1 added to it.
- Create a function that accepts two integers, a and b, returns the sum of the two numbers.
- Create a procedure that accepts no parameters but prints “hello” on the screen when called.
- Create a procedure that accepts a string (e.g. a person’s name) and prints “hello” followed by the string (i.e. the person’s name) when called.
- Create a function that accepts an integer and returns the integer multiplied by itself.
- Create a function that accepts a number and returns either true or false depending on whether the number is a factor of 7.
- Create a function that accepts 3 numbers and returns the average of the 3 numbers.
- Create a function that accepts two integers a and p, and returns a multiplied by itself p-1 times.
Knowledge check
- Why are subroutines useful in a program?
- What distinguishes a function from a procedure?
- What is a global variable?
- What distinguishes a global variable from a local variable?
- What is the difference between passing by value, and passing by reference?