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 String
s 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
- Line 3 calls
foo
and passes in the string “Hello”.foo
receives this string into variablestr
.foo
printsstr
. - Line 4 calls
foo
and passes in the string “Thanks”.foo
receives this string into variablestr
.foo
printsstr
. - Line 5 calls
foo
and passes in the string “Goodbye”.foo
receives this string into variablestr
.foo
printsstr
.
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 int
s and lines 5-7 are another max
method that accepts double
s. They can coexist because the different parameter types distinguish the two i.e. a
and b
are int
s in the top one and a
and b
are double
s 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.