Introduction

Methods, or functions, are discrete units of code. They enable us to break up large, linear programs into manageable and reusable chunks. Up to this point we’ve been writing all our code in the main method. This is fine for small programs but as they get larger, they become increasingly difficult to understand and maintain. Imagine digging through a program ten thousand lines long and trying to figure out how it all comes together, let alone trying to make changes to it. Methods can help ease the mental load by abstracting away certain details, allowing us to focus on the broader picture.

Two Methods

The following program shows a class that contains both the main method and an additional method:

public class TwoMethods {
    public static void main(String[] args) {
        // code goes here
    }

    public static void foo() {
        // code goes here
    }
}

Lines 2-4 is the familiar main method and lines 6-8 is another method named foo. As you can see, there isn’t much difference between the two. They both start with public static void (which you can simply ignore for the moment) and they both have a body to write code in. The only two differences are that one’s called main and the other’s called foo, and that main’s parentheses contain the code String[] args whereas foo’s parentheses are empty.

Note
The word “foo” is a common placeholder name for methods and other entities that serve no real purpose (other names include bar, baz, fuzz, buzz etc).

Both main and foo will execute code in exactly the same way, from top-to-bottom. Let’s place a couple print statements in each method.

public class TwoMethods {
    public static void main(String[] args) {
        System.out.println(1);
        System.out.println(2);
    }

    public static void foo() {
        System.out.println(3);
        System.out.println(4);
    }
}
[Run]
1
2

On running the program only main runs so only 1 and 2 are printed. As mentioned back in chapter 1, this is because only the main method is invoked/called automatically. Any other methods must be manually invoked. In programming, methods call other methods, so in order for foo to run, it must be called from within main at some point. Calling a method is as simple as typing its name plus parentheses (plus a semicolon). For example:

public class TwoMethods {
    public static void main(String[] args) {
        System.out.println(1);
        foo();
        System.out.println(2);
    }

    public static void foo() {
        System.out.println(3);
        System.out.println(4);
    }
}

On line 3, 1 is printed. On line 4, foo is called. Because this call to foo is inside main, we say that main calls foo. main must now wait for foo to complete. Meanwhile, foo takes control and starts running. Line 9 prints 3. Line 10 prints 4. Line 11 is the closing curly bracket, which means the end of foo, therefore foo returns and main continues from where it left off (line 4). Line 5 prints 2. Line 6 is the end of main and thus the end of the program.

You can write as many methods as you need and those methods can be called as many times as needed, and not only from main. Any method can call other methods, and those methods can call other methods, and so on, resulting in a chain of calls. Also, methods can sit inside the class in any order and this doesn’t change how the program runs. The next program, for example, contains three methods: bar, foo, and main, in that order. main still runs first, like always. Also, the keyword public has been omitted from bar and foo as it’s not strictly necessary (this is because public is an access modifier, and access modifiers don’t apply unless working with multiple classes so we can remove it for now). Feel free to go through the next program manually and jot down what you think the output will be:

public class ThreeMethods {
    static void bar() {
        int x = 5;
        System.out.println(3 * x);
    }

    static void foo() {
        bar();
        System.out.println("Hello");
        bar();
    }

    public static void main(String[] args) {
        System.out.println(1);
        foo();
        foo();
        System.out.println(2);
        bar();
    }
}
[Run]
1
15
Hello
15
15
Hello
15
2
15

The program begins in main (line 13). First, on line 14, main prints 1. Then, on line 15, main calls foo. The first thing foo does is call bar (line 8). In bar, on line 3, variable x is declared and set to 5. Then line 4 prints 3 * x (15). bar returns to foo, back to line 8. On line 9, foo prints Hello. On line 10, foo calls bar again. In bar, x is declared and 3 * x is printed again (15). bar returns (to line 10). foo returns (to line 15). main calls foo again (line 16) and the whole process essentially repeats… so line 8 calls bar. Line 3 declares x. Line 4 prints 3 * x (15). bar returns (to line 8). Line 9 prints Hello. Line 10 calls bar. Line 3 declares x. Line 4 prints 3 * x (15). bar returns (to line 10). foo returns (to line 16). Line 17 prints 2. Line 18 calls bar. Line 3 declares x. Line 4 prints 3 * x (15). bar returns (to line 18). main returns and the program ends. Note that every time a method is called, it starts afresh each time. This means that any variables declared in methods are discarded and forgotten about when the method ends (such as variable x on line 3). Every method run is independent and unaware of every other one.

Class Variables

Methods aren’t the only thing that can exist at the class level, variables can too. This means there are two places you can declare a variable, either in a method or in the class. So far, we’ve only declared variables in methods. These are called local variables because they are localised to the method in which they are declared. Variables declared in the class are called class variables (or static fields) and are accessible by all methods in the class. Whereas a local variable only exists until the end of the method it is declared in, a class variable exists for the duration of the program. The following program demonstrates a class variable that is used by methods main and foo:

public class ClassVars {
    static int x = 5;

    public static void main(String[] args) {
        System.out.println(x);
        foo();
        System.out.println(x);
    }

    static void foo() {
        System.out.println(x);
        x = 20;
    }
}
[Run]
5
5
20

On line 2 is a class variable called x containing 5. It needs to be static just like the methods are. This variable can be used inside any methods in that same class i.e. main and foo. First, on line 5, x (5) is printed. On line 6, foo is called. In foo, line 11 prints x (5). Line 12 sets x to 20. Back in main, line 6 prints x (20). The program ends.

No, I’m x!

Two variables in the same scope cannot have the same name but what if a class variable and a local variable have the same name?

public class ClassVars {
    static int x = 5;

    public static void main(String[] args) {
       int x = 10;
       System.out.println(x);
    }
}

In this program, line 2 declares a class variable called x and line 5 declares a local variable called x. So then, which x is line 6 referring to? It will, in fact, print the local variable x because local variables take precedence over class variables. Therefore, line 6 will print 10, not 5. What if you wanted to access the other x, though? In that case, you have to qualify the variable with the name of the class, so line 6 would look like this:

System.out.println(ClassVars.x);

Since the name of the class is ClassVars, you write ClassVars.x to specifically refer to the class variable x on line 2. Again, you only have to do this if there’s a naming conflict.

Passing Data

Often, methods require that data be passed to them when called. We do this all the time with the various print methods, passing them Strings to be printed. Data passing is useful because the same method can be called many times yet produce different results depending on the data it receives. Passing data is done via the method parentheses; the method that needs to receive data declares a variable in the parentheses for the data to go in. Data can then be placed in that variable when the method is called, via the call’s parentheses. Let’s go through an example. In the following program, there is no data passing and foo will do the same thing every time it’s called.

public class DataPassing {
    public static void main(String[] args) {
        foo();
    }

    public static void foo() {
        System.out.println("Hello");
    }
}
[Run]
Hello

All that happens is main calls foo (line 3) and then foo prints “Hello” (line 7). foo’s behaviour never changes. In order to tell foo what to print, we need to declare a variable in its parentheses and then we can pass some data into that variable via the call.

public class DataPassing {
    public static void main(String[] args) {
        foo("Goodbye");
    }

    public static void foo(String str) {
        System.out.println(str);
    }
}
[Run]
Goodbye

On line 6, foo’s parentheses are no longer empty and contain a variable called str (of type String). On line 3, the call’s parentheses contain the string “Goodbye”. Upon calling, the string “Goodbye” gets stored in variable str. foo can use str like a normal variable, like on line 7, where str is simply printed. foo can now be called multiple times with different strings.

public class DataPassing {
    public static void main(String[] args) {
        foo("Hello");
        foo("Thanks");
        foo("Goodbye");
    }

    public static void foo(String str) {
        System.out.println(str);
    }
}
[Run]
Hello
Thanks
Goodbye
  1. Line 3 calls foo and passes in the string “Hello”. foo receives this string into variable str. foo prints str.
  2. Line 4 calls foo and passes in the string “Thanks”. foo receives this string into variable str. foo prints str.
  3. Line 5 calls foo and passes in the string “Goodbye”. foo receives this string into variable str. foo prints str.

As mentioned before, the values that are passed to methods are called arguments. The variables in the parentheses that receive the values are called parameters. In the previous example, method foo has one parameter (str) and, in main, foo is called three times, each time passing in a different argument. This is actually more than just a teaching example. We are often printing text, and typing System.out.println each time is quite the monotonous task. Instead, we can use foo since it encapsulates the behaviour of System.out.println and is more concise to write. We should definitely change the name, though, to something descriptive like println:

public class DataPassing {
    public static void main(String[] args) {
        println("Howdy");
    }

    public static void println(String s) {
        System.out.println(s);
    }
}

All I’ve done is changed the name of the method from foo to println (and the call to match). Now we have our very own method for printing, which we can include in any of our programs. So, anytime we want to print a string, instead of typing something like:

System.out.println(“bits and bytes”);

we can simply type:

println(“bits and bytes”);

Sure, our println method is basically a glorified middleman since all it does is hand over the task of printing to the actual println method, but it does make our life easier by reducing the amount of typing we have to do. There is a small problem in that we can only pass String data to our println method. We’ll look at how to solve this further on the chapter.

Sometimes a method needs more than one piece of data. A method can declare multiple parameters of various types. You just separate them by commas. For example:

public class MultipleParameters {
    public static void main(String[] args) {
        foo("Howdy", 5, 2.5);
    }

    public static void foo(String str, int a, double b) {
        System.out.println(str);
        System.out.println(a);
        System.out.println(b);
    }
}

On line 6, foo now has three parameters—str, a, and b—and that means we have to pass three arguments whenever we call it. The arguments must be the same type as the parameters. In main, we call foo and pass “Howdy”, 5, and 2.5. In foo, “Howdy” goes into str, 5 goes into a, and 2.5 goes into b. On line 7, 8, and 9, each variable is printed so we get:

[Run]
Howdy
5
2.5

Returning Data

Methods can return data when they have finished their task. To return simply means to go back to where the method was called from. As we’ve seen, methods return automatically once they reach the end but they don’t return any data when this happens. You can manually return from a method by using the return keyword, either with or without data, at any point in the method’s execution, for example:

public static void foo() {
    System.out.println("Oh, phooey!");
    return;
}

If this method is called, then line 2 prints some text and then line 3 causes the method to return (back to the call). In this case, however, the return is pointless because the method returns at the end anyway. IntelliJ IDEA even notifies you of this fact if you hover over the return keyword:

Notice that it specifically says this is unnecessary in a void method. This is referring to the word void in the method header. This is called the return type. It tells you what type of data the method returns. The void keyword signifies that the method returns no data—it simply returns. Let’s look at a method that returns the integer 10:

public static int foo() {
    return 10;
}

As you can see on line 3, it’s as simple as writing return 10. Notice the return type has also changed from void to int, since 10 is an int. If we wanted to return 10.5, then we’d need to change the return type to double:

public static double foo() {
    return 10.5;
}

The way we obtain the return value is by setting a variable equal to the call statement (something we’ve done many times in previous chapters):

public class ReturnExample1 {
    public static void main(String[] args) {
        int r = foo();
        System.out.println(r);
    }

    public static int foo() {
        return 10;
    }
}
[Run]
10

Line 3 calls foo. In foo, line 8 returns 10. Back on line 3, 10 is placed in variable r. Line 4 prints r (10).

Recreating Existing Methods

To really get a grasp of the usefulness of data passing, let’s emulate a couple of the methods found in the Math class such as max and abs. This is where we need to think somewhat more abstractly because we won’t be dealing with concrete numbers, but variables of any possible value.

Method: max

Here is an implementation of the max method, which takes two numbers and returns the largest one:

public class MaxExample {
    public static void main(String[] args) {
        int n1 = max(57, 36);
        int n2 = max(-40, -9);
        int n3 = max(12, 12);
        System.out.printf("%d, %d, %d", n1, n2, n3);
    }

    public static int max(int a, int b) {
        if (a > b) {
            return a;
        }
        else {
            return b;
        }
    }
}
[Run]
57, -9, 12

Lines 9-16 is the max method. It has two parameters, a and b, that the caller will pass arguments to (the ‘caller’ refers to the source of the call). In max, the if statement says: if a is greater than b, then return a; else return b. In main, we call max three times and store the return values in n1, n2, and n3. Then we print out these variables on line 6 to check it gave the correct results. On line 3 we expect 57 to be returned, on line 4 we expect -9, and on line 5 we expect 12. As we can see from the output, we do get those values. Let’s walk through its execution just so there’s no confusion. Line 3 calls max passing values 57 and 36. In max, these two values go into variables a and b. Line 10 checks if a (57) is greater than b (36). This is true so line 11 returns a (57). Therefore, back on line 3, n1 = 57. Line 4 calls max passing -40 and -9. In max, the if statement checks if a (-40) is greater than b (-9). This is false so the else block returns b (-9). Back in main, n2 = -9. Line 5 calls max passing in 12 and 12. In max, the if statement checks if a (12) is greater than b (12). This is false so the else block returns b. (Ultimately, since a and b are the same number, it wouldn’t matter which one max returns.) Back in main, n3 = 12. Line 6 prints n1, n2, and n3, resulting in the output above.

There are a few things we can do to reduce the size of the max method. One is to remove the curly brackets of the if statement:

public static int max(int a, int b) {
    if (a > b)
        return a;
    else
        return b;
}

It’s important to know that when you remove the curly brackets from a block, Java associates only the first statement with the block. Further statements are not considered part of the block nor the if statement in general. For example:

public static void foo() {
    if (false) 
        System.out.println("red");
        System.out.println("green");
        System.out.println("blue");
}

Only the first print statement is associated with the if block, and since the condition is false, “red” is not printed. The other two are not part of the if statement at all and are executed regardless. It is identical to this:

public static void foo() {
    if (false) {
        System.out.println("red ");
    }
    System.out.println("green");
    System.out.println("blue");
}

To reduce the max method even further, we can use the ternary operator for a single line of code:

public static int max(int a, int b) {
    return a > b ? a : b;
}

Here, a > b is checked. If true, a is the result. If false, b is the result. The result is subsequently returned. For example, if we were to call max(3, 6), then line 2 would look like this:

return 3 > 6 ? 3 : 6;

The condition 3 > 6 is false so 6 is the result, which results in return 6;, hence 6 is returned.

Method: abs

The absolute value of a number is the number in its positive form. For example, the absolute value of 5 is 5 and the absolute value of -5 is 5. Basically, positives stay positive, and negatives turn positive. For the abs method, we pass it a number and it will return the absolute value. The long-winded way is to use an if statement.

public static int abs(int a) {
    if (a < 0) {
        return -a;
    }
    else {
        return a;
    }
}

This says if a is less than 0 (meaning a is negative) then return -a. This is because the negative of a negative is a positive, thus we get the absolute value. Else (meaning a is positive), just return a without altering it.

Here is the same method using the ternary operator:

public static int abs(int a) {
    return a < 0 ? -a : a;
}

Admittedly, you could put the if statement all on one line for a similar style.

public static int abs(int a) {
    if (a < 0) { return -a; } else { return a; }
}

Though you don’t really see this style of coding that much. Personally, I find the ternary operator looks cleaner.

Method Overloading

Method overloading occurs when two or more methods have the same name but the parameter list differs in some way, either by the number of parameters or their type, or both. This is useful when you have a different number and/or types of arguments that you want to pass to a method. Take the max method from before. It can only accept int values because its parameters, a and b, are both int variables. But what if you want to pass double values instead? In that case, you have to overload the max method. In other words, you simply have to write the method again but with different types of parameters e.g.

public static int max(int a, int b) {
    return a > b ? a : b;
}

public static double max(double a, double b) {
    return a > b ? a : b;
}

Lines 1-3 are the original max method that accepts ints and lines 5-7 are another max method that accepts doubles. They can coexist because the different parameter types distinguish the two i.e. a and b are ints in the top one and a and b are doubles in the bottom one. Therefore, max(4, 7) would result in the top one being called and max(4.9, 7.5) would result in the bottom one being called. Also, notice that both methods have different return types. This has nothing to do with method overloading—it’s simply the most logical type for each method to return.

Leave a Reply

Your email address will not be published. Required fields are marked *