(Updated January 20, 2025)
Table of contents
-
Overview
Getting Started
User-Defined Methods
Calling Our Methods
void
MethodsValue-returning Methods
Method Overloading
Parameters Revisited
Quiz
Exercises
Overview
User-defined methods are methods created by the application writer to support a particular feature, to fulfill a specific need that has not already been met through the use of standard classes and their predefined methods, and whenever we may find ourselves reusing code that we were about to copy and paste elsewhere. This concept of creating our methods will be enhanced further as we define methods specifically for manipulating user-defined classes and the objects we will make from those classes.
This chapter predicates the basic concepts of object-oriented design (OOD). Thus far we have used Math
, JOptionPane
, String
, StringBuffer
, Scanner
and wrapper classes (Integer
, Double
, Character
). We have also used the System
class with two specific fields, in
and out
, which are objects of two other classes, InputStream
and PrintStream
.
What is significant about this is you were able to use each of these classes and their methods to do work without any additional knowledge of how the work was done, how strings are stored, or how the System.out
standard output object managed to get the characters to the screen.
Each object we create and store in our reference variables has relevant data and operations (methods) that can manipulate that data. The String
objects that we have used contained both the memory to store the characters that make up the string plus the length of the string, as well as methods like toUpperCase()
and charAt()
. They are combined in the same object, referred to as encapsulation.
However, before we are ready to create user-defined classes, we must first tackle user-defined methods.
Getting Started
Let us reiterate a few details about methods we may or may not recall:
- They represent bits of code that perform a common task.
- They have a name representing the work to be done.
- We can call on them as often as needed.
- Sometimes, we pass additional knowledge to them.
- Sometimes they give back results.
- We can create our own.
The last one is the key to this whole chapter. We can create our own. And it is not that difficult to do so, although we can certainly create some complex methods. Sometimes, solutions are complex.
Let us talk about the mechanics.
- We can call on them as often as needed.
- Sometimes, we pass additional knowledge to them.
- Sometimes they give back results.
Methods are the Java term to represent named, encapsulated work. In other programming languages, they may be known as functions or procedures, but they have the same purpose.
Methods free us from the tedium (and mistake) of copying and pasting the code everywhere we need that work to be performed.
This may sound silly, but some people are afraid to encapsulate the purpose of some code they have written and turn it into a method/function/procedure. Why? Usually, because the idea is new (resistance to change is normal), they are not confident with building it, or they struggle with the mechanics.
So, we will now put together the details of how. Consider this:
int x;
x = 2 + 5;
We are saying x
gets the value of the sum of 2
and 5
.
What if we wrote it this way?
int x;
x = sum(2, 5);
We have not changed the meaning of what we are doing. All that has changed is the how. Instead of using an operator, we are using a method.
But, this method does not yet exist. So we will create it.
public static int sum(int a, int b) {
int t;
t = a + b;
return t;
}
Now, let’s describe in detail what is happening here. The very first line declares the method by name and how it will work: It will be called sum
, take two int
parameters, and pass back an int
result. The return
statement at the end returns the result to the caller.
To understand how this all comes together, we have to consider the idea of abstraction. We do not know what will be sent as values (arguments) for a
and b
. But we do not need to know what will happen in the future. All we need to know is how to access those values when the time comes when the method is invoked.
For this example, we have decided that the first value passed will be a
and the second is b
and the result stored in t
. We could have called them potato
and guava
and assigned them to pickle
and returned the value stored in pickle
.
See?
public static int sum(int potato, int guava) {
int pickle;
pickle = potato + guava;
return pickle;
}
Sure, it is hilarious, and it proves an important point. The method, variable, and parameter names can be anything (except the set of reserved words, of course!), but do your best to pick meaningful names when you can and not ones that detract from the meaning of your code.
User-Defined Methods
Since the start of this book, we have covered some ground on classes, objects, instantiation, methods, wrappers, autoboxing, constructors, and the possible immutability of an object; it is finally time to create our methods.
In the previous section, we delved into the visual aspect of method creation. However, there is still some unfinished business. Why? Well, we defined a method called sum()
and showed how it could be invoked, but we did not finish painting the complete picture of how the whole program looked when it was finished. This is presented now.
public class GettingStarted1 {
/**
* Method to compute a sum
*
* @param a int value to be summed
* @param b another int value to be summed
* @return the sum of a and b
*/
public static int sum(int a, int b) {
int t;
t = a + b;
return t;
}
/**
* Main method thats accepts an array of Strings.
*
* @param args Strings from the command line
*/
public static void main(String[] args) {
int x;
/* sum invoked here. */
x = sum(2, 5);
System.out.println("The sum of 2 and 5 is " + x);
}
}
Example GS1: Fully realized Getting Started example of user-defined methods.
And the slightly more humorous one:
public class GettingStarted2 {
/**
* Method to compute a sum
*
* @param potato int value to be summed
* @param guava another int value to be summed
* @return the sum of potato and guava
*/
public static int sum(int potato, int guava) {
int pickle;
pickle = potato + guava;
return pickle;
}
/**
* Main method thats accepts an array of Strings.
*
* @param args Strings from the command line
*/
public static void main(String[] args) {
int x;
/* sum invoked here. */
x = sum(2, 5);
System.out.println("The sum of 2 and 5 is " + x);
}
}
Example GS2: Fully realized Getting Started example of user-defined methods.
We will build a slightly more involved version of creating methods for specific purposes. The goal here is to move beyond the introductory phase and begin writing many methods of various forms.
Let us look at Example 1, which will demonstrate the use of all the different forms of method design.
public class MethodTypes {
/**
* Simple announcement.
* no parameters, no return
*/
public static void announce() {
System.err.println("The process has begun!");
}
/**
* Prints an error to stderr
* one parameter, no return
*
* @param errstr Error message to be printed
*/
public static void print_error(String errstr) {
System.err.printf("The program encountered a \"%s\" error.", errstr);
}
/**
* Return random number 1-100 inclusive
* no parms, has return
*
* @return int random number
*/
public static int get_random() {
return (int) (Math.random() * 100 + 1);
}
/**
* Raises base to a power.
* 2 parms and return
*
* @param base number to have exponent applied
* @param pwr the power to raise base
* @return
*/
public static int power(int base, int pwr) {
int x, total = 1;
if (pwr == 0)
return 1;
else {
for (x = 1; x <= pwr; x++)
total *= base;
}
return total;
}
public static void main(String[] args) {
announce();
System.out.printf("%d%n", get_random());
System.out.printf("%d\n", power(2, 10));
print_error("something terrible");
}
}
Example 1: Java program demonstrating user-defined methods.
In Example 1, we introduce the four methods you can create. It is easier to reveal all of the details at once. There is nothing to hide here; you have already seen examples of each type. Now, we will take the time to identify each kind.
There are two fundamental types of user-defined methods:
- Value-returning methods.
- Void methods. (Void methods return no value)
Then, it is simply an academic exercise to determine which methods require arguments and which do not. This gives four possible methods.
The main()
method is a void method – we have seen this for quite some time with the void
keyword to the left of main(String[] args)
. The method announce()
and print_error()
are also void
methods, however print_error()
takes arguments while announce()
does not.
Here is sample output generated by Example 1:
The process has begun! 83 1024 The program encountered a "something terrible" error.
Now, we need to clear up some terminology. When discussing any methods (user-defined or from a library), two different terms describe what we put in parentheses. When we write the method and the work it will do, we define required parameters. Remember that not all methods have parameters. When we invoke the method, the values we send are arguments.
- Parameters define what is required by the method.
- Arguments define what we actually sent to the method.
Calling Our Methods
When using one of our methods or predefined methods in a program by name, it is known as a method call. To call a method is to invoke it or make it perform its work. You will likely see many pieces of documentation use the terms call and invoke interchangeably.
Note that when we declare any of the methods shown, they are not called as a result of their mere presence. This is also true of the main()
method; that is, the main()
method was not called until the JRE invoked it as a result of loading our class file which is the bytecode that represents the program we wrote. Remember that the main()
method is the starting point when we run the program.
We explicitly call methods by using their names in the form of a statement. This happens in main()
with the block shown below.
announce();
System.out.printf("%d%n", get_random());
System.out.printf("%d%n", power(2,10));
print_error("something terrible");
Do you see the six methods being invoked? The two printf()
calls have nested method calls. The inner call's return value feeds the required argument(s) for the outer call. So, there are two method invocations on each line.
Nothing would happen concerning computation, input, or output without these method call statements in our code. Remember this detail when writing your methods – if you do not call it, it does not occur.
Finally, we must discuss the static
modifier. Using the static
modifier allows the method to be called on the class. Otherwise, it must be called on an instance of the class.
Recall that pow()
was called on the class like:
p = Math.pow(x, y);
And nextInt()
was called on an instance of the class like:
Scanner kb = new Scanner(System.in);
// ...
x = kb.nextInt();
The Math
class method pow()
is a static
method and therefore can be called on the class. In contrast, nextInt()
is not a static
method and must be invoked on kb
. Recall that kb
is a reference variable that points to an instance of the Scanner
class.
The general rules governing static
and non-static
methods are:
- A
static
method can be called on the class. - A non-
static
method must be called on an instance of a class (object).
static
may not be obvious at first. Since we were shown from the beginning that main()
must be static
, we often simply accept its use. However, when the program is invoked, there is often a misconception that an object is created by the runtime environment, using our class as a blueprint, and then invokes main()
.
What actually happens is the JRE locates the class file and tries to invoke main()
directly (because it expects it to be static
). Therefore, any other methods you write and you want accessible to main()
must also be static
or main()
cannot use them.
Please do not take our word for it. Remove the static
modifier and see what happens!
void
Methods
The void
methods are the easiest to get a handle on since we have been creating one void
method in particular for some time, that is, the main()
method. We have also been using a number of void methods in our programs for output (print
, println
, printf
). The void
methods do not return anything to the caller (which is the opposite of pow()
or nextInt()
where we expect some result). They simply do their work and then control is passed back to wherever they had been called.
Our announce()
and print_error()
are void
methods also have the modifier static
. Recall that this means the methods can be called on the class and is also a requirement for main()
to make use of our user-defined methods. Since main()
is static
, any methods we create in the same class as main()
must also be static
or the compiler will not allow them to be called from main()
.
Let us look at print_error()
in a bit more detail. We have a void
method that requires parameters.
public static void print_error(String errstr)
This indicates that print_error()
is a method that requires exactly one parameter (labeled as errstr
). The parameter is required to be of type String
.
Each time we call our method, we do it something like:
print_error("Something happened.")
We provide the argument to be used. So the value "Something happened."
is the argument and errstr
is the parameter.
The print_error()
method does its job of printing the value of the argument, then ends, and control is returned to the caller.
Value-returning Methods
After our discussion of void
methods, value-returning methods should be a snap. The first step in creating value-returning methods is to turn the void
into some other type. This type can be any primitive type or a class like String
. After that, it is a matter of adding the return
statement to send the results back to the caller. As we have seen in many other examples, one of the greatest benefits of value-returning methods is using them in expressions.
One thing that should be mentioned is that methods do not need to print something. Sure, many of our examples do, but none of the Math
class methods print anything. This is quite common as methods are frequently used to help with computation or to assist in moving data around. And, yes, some are I/O based, which include our prior use of the trio of print methods and the methods of the class JOptionPane
.
You may recall that methods like pow()
, sqrt()
and nextInt()
do not print anything, they simply return the requested information. Of course, that value is often stored in some other variable. We will now look at the mechanism that is involved in getting these results sent back. The value-returning method, get_random()
, from Example 1 is reiterated here.
public static int get_random() {
return (int)(Math.random()*100 + 1);
}
The get_random()
method demonstrates the use of calling another value-returning method to assist in the work to be done. This, too, is quite common in user-defined methods. While we may be creating a new tool that does not exist, it does not preclude the idea of making our code simpler by using other available tools. This method is designed to return a random value in the range 1-100.
get_random()
is clear in its intention to create a random number in the range 1-100, it is not at all obvious by its name.The return
statement is the mechanism that sends the value back to the caller. All methods eventually return, but only value-returning methods contain a statement expressing precisely what is to be sent back.
void
methods do not have a return statement to send back results, the return
statement can still be used a means of leaving the method immediately as long as there is no attempt to send back a value.Another concept that get_random()
demonstrates is the idea of performing the calculations, including additional method calls, on the same line as the return
statement. Remember that expressions do not have to be after an assignment operator.
To clarify, the get_random()
method could have been written like the following:
public static int get_random() {
int r;
r = (int)(Math.random()*100 + 1);
return r;
}
Note the declaration of the local variable, r
, the assignment of the random number into r
, and then the return
statement indicating that the value in r
is to be returned. It is very reminiscent of the code in Getting Started. However, on a certain level, it is very unnecessary. Much like in mathematics and even in real life, we identify steps and/or representations that are simply not necessary, so we do not perform them. We refine or reduce the process to its simplest form. That has been done to get_random()
.
Now, on to power()
!
public static int power(int base, int pwr) {
int x, total=1;
if ( pwr == 0 )
return 1;
else {
for ( x = 1; x <= pwr; x++ )
total *= base;
}
return total;
}
This is the one method with the most code. It is attempting to calculate a value raised to an exponent by performing a loop. The takeaways in this code are not new, and we will reiterate them here:
- The parameters (
base
andpwr
) are accessible to the code as regular variables. They hold the contents of what was passed. - There are local variables (
x
andtotal
) just like we do inmain()
. - There are two
return
statements. This is actually quite common. - One
return
is conditional while the other is unconditional.
power()
method is also incomplete in its implementation. It does not handle negative exponents, nor does it handle any errors resulting in their use. The example is intentionally lacking in some features for simplicity.Do not let the two return
statements trip you up. You can have as many as needed to keep the code simple. We could have assigned total
the value of 1 and fallen through the if
test to the final return
. Generally, when writing methods we want to leave as soon as we realize the work is done. Hence the second return
inside the if
.
Regardless of your code arrangement, a return
statement MUST always be reachable. The final return
could also have been moved into the else
clause. As long as there is a reachable return
statement for every conditional possibility, the compiler will accept the method as complete.
Method Overloading
The purpose of method overloading is to have more than one method with the same name sharing a common goal. The differences between each method are within the list of parameters. Before we look at any code, we will note the requirements for method overloading.
- Overloaded methods all have the same name.
- Overloaded methods must differ in at least:
- The number of parameters.
- The type of parameters.
The compiler tells the difference between them by using the methods signature. The signature of a method does not consider the return type. The name of the method and the argument types are all the compiler needs to decide.
So, if we had two methods:
public static int base_convert(String v) {
...
}
public static int base_convert(String v, int b) {
...
}
We would say these two methods differ in number because one method has one parameter and the other has two. Their signatures are:
base_convert(String)
and
base_convert(String, int)
Alternatively, consider the following:
public static int min(int a, int b) {
...
}
public static double min(double a, double b) {
...
}
We would say these two methods differ in type because one method takes two int
s while the other takes two double
s. Their signatures are:
min(int, int)
and
min(double, double)
Remember that the method signatures must be different for the compiler to choose the correct overloaded method to execute.
The code in Example 2 takes the majority of code from Example 1 and overloads the print_error()
and get_random()
methods.
public class MethodOverloading {
/**
* Prints an error to stderr
*
* @param errstr Error message to be printed
*/
public static void print_error(String errstr) {
System.err.printf("The program encountered a \"%s\" error.", errstr);
}
/**
* Prints default unknown error to stderr
*/
public static void print_error() {
System.err.println("The program encountered an unknown error.");
}
/**
* Return random number 1-100 inclusive
*
* @return int random number
*/
public static int get_random() {
return (int) (Math.random() * 100 + 1);
}
/**
* Return random number 1-top inclusive
*
* @param top max value for the random number
* @return int random number
*/
public static int get_random(int top) {
return (int) (Math.random() * top + 1);
}
public static void main(String[] args) {
System.out.printf("%d%n", get_random());
System.out.printf("%d%n", get_random(50));
print_error();
print_error("something terrible");
}
}
Example 2: Program that demonstrates method overloading.
While overloading a method allows us to create many methods with the same name, it also allows us to define some as void methods while others may be value-returning methods. As you can see we have two print_error()
void methods. One takes a String
parameter and the other takes no parameters. The get_random()
value-returning methods allow us to have choices on how we request a random value.
Aside from the number of arguments, there is no difference in how we call the two versions of the method.
Here is the output of Example 2:
19 24 The program encountered an unknown error. The program encountered a "something terrible" error.
Parameters Revisited
As we discussed previously, we can have parameters of any primitive type. However, we can also have reference variables as parameters. Remember, those reference variables contain addresses of objects, not the objects themselves. Consider these rules when declaring and using parameters:
- When passing primitive type variables as arguments, the parameter contains a copy of the data that is in the argument. Nothing will happen to the data in the argument.
- When passing reference variables as arguments, the parameter contains a copy of the reference (a hash value) in the argument. This means that changes made to the parameter are also changed for referenced mutable argument objects. This is because the parameter and the argument point to the same object.
When dealing with String
reference variables as arguments, we do not have this issue. Recall that String
objects are immutable. They cannot be modified.
If your method assigns a new string to a parameter, the argument will not be changed or the object it references. Example 3 demonstrates this phenomenon.
public class StringParam {
/**
* Method demonstrating pass by value.
* And String is immutable, so cannot be changed
*
* @param str
*/
public static void alterString(String str) {
// Creates new instance. Does not replace
// characters of existing object.
str = "String from alterstring() method!";
System.out.println("str = " + str);
}
public static void main(String[] args) {
String s;
s = "String from main() method.";
System.out.println("s = " + s);
alterString(s);
System.out.println("s = " + s);
}
}
Example 3: Program demonstrating how a string assignment alters the formal parameter, not the actual one.
The output of Example 3 is as follows:
s = String from main() method. str = String from alterstring() method! s = String from main() method.
As you can see, the value of s
in the main()
method did not change. The StringBuffer
class can be used in these situations where you intentionally need to change the contents of the String
object.
Keep in mind that you cannot use the assignment operator (=) to initialize your instantiated objects. Example 4 demonstrates how to use StringBuffer
to fix the problem in Example 3.
public class StringBufferParam {
/**
* Method demonstrating pass by value, but this time the
* reference passed is a mutable object!
*
* @param str
*/
public static void alterString(StringBuffer str) {
// Replace characters of existing object.
str.replace(0, str.length(), "String from alterstring() method!");
System.out.println("str = " + str);
}
public static void main(String[] args) {
StringBuffer s;
// Must use constructor
s = new StringBuffer("String from main() method.");
System.out.println("s = " + s);
alterString(s);
System.out.println("s = " + s);
}
}
Example 4: Program to use StringBuffer
to fix the shortcomings of Example 3.
In Example 4, we use the replace()
method of the StringBuffer
class and specify the beginning, end, and new value of the string. We will effectively replace the entire string contents by specifying zero as the first position and the string's current length as the end position.
The sample output is shown below.
s = String from main() method. str = String from alterstring() method! s = String from alterstring() method!
While this may not be an ideal solution to effecting change on an object, we must be mindful that it is the content of the object that is modified, not the reference. It is the same instance, and where that object lives remains intact.
Quiz
Exercises
- (Easy) Create a method called
sum
that sums twoint
values. - (Easy) Create a method called
concat
that takes twoString
values and returns the concatenation of the first followed by the second. - (Intermediate) Create a two more
sum()
methods (overloaded) that each take twoshort
and two long values. Adjust the return type as appropriate. - (Intermediate) Recreate the standard
String
method,indexOf()
, by creating a method that takes aString
and achar
to be sought within theString
. Return the position of the first occurrence or-1
if not found. - (Intermediate) Modify #4 to create
rindexOf()
, still taking aString
and achar
to be sought within theString
from the right end of the string. Return the first position or-1
if not found. - (Advanced) Write a method that takes a
String
, a sourcechar
and replacementchar
. Return theString
with the characters replaced. - (Expert) Write a pair of overloaded methods that convert a String to an int. You will simulate the
Integer
methodsparseInt(String)
andparseInt(String, int)
. The former assumes a base 10 number, while the latter provides a radix. Return the converted value as anint
. - (Expert) Write a method (or methods) that will take dimensions as parameters
width
andheight
. With these values, create a dimension grid using the hyphen, pipe, and space characters. A 5 x 8 grid would look something like this:--------------------- | | | | | | --------------------- | | | | | | --------------------- | | | | | | --------------------- | | | | | | --------------------- | | | | | | --------------------- | | | | | | --------------------- | | | | | | --------------------- | | | | | | ---------------------