Methods revisited
This figure probably roughly sums up where our understanding of methods is at the moment:
As we can see in the previous figure, there are still a couple of question marks around methods. We will completely take the lid off of methods and see how they work, and what exactly the other parts of the method are doing for us later in the chapter. In Chapter 9, Object-Oriented Programming, we will clear up the last few parts of the mystery of methods.
So, what exactly are Java methods? A method is a collection of variables, expressions, and control flow statements bundled together inside an opening curly brace and closing curly brace preceded by a name. We have already been using lots of methods, but we just haven't looked very closely at them yet.
Let's start with the method structure.
The method structure
The first part of a method that we write is called the signature. Here is a hypothetical method signature:
public boolean addContact(boolean isFriend, String name)
If we add an opening and closing pair of curly braces, {}
, with some code that the method performs, then we have a complete method—a definition. Here is another made up, yet syntactically correct, method:
private void setCoordinates(int x, int y){ // code to set coordinates goes here }
As we have seen, we could then use our new method from another part of our code like this:
// I like it here setCoordinates(4,6); // now I am going off to setCoordinates method // Phew, I'm back again - code continues here
At the point where we call setCoordinates
, our program's execution would branch out to the code that is contained within that method. The method would execute all the statements inside it step by step until it reaches the end and returns the control to the code that called it, or sooner if it hits a return
statement. Then, the code would continue running from the first line after the method call.
Here is another example of a method complete with the code to make the method return to the code that called it:
int addAToB(int a, int b){ int answer = a + b; return answer; }
The call to use the preceding method could look like this:
int myAnswer = addAToB(2, 4);
Clearly, we don't need to write methods to add two int
variables together but, the example helps us see a little more into the workings of methods. First, we pass in the values 2
and 4
. In the signature, the value 2
is assigned to int a
and the value 4
is assigned to int b
.
Within the method body, the variables a
and b
are added together and used to initialize the new int answer
variable. The return answer
line returns the value stored in answer
to the calling code, causing myAnswer
to be initialized with the value 6
.
Note that each of the method signatures in the preceding examples varies a little. The reason for this is that the Java method signature is quite flexible and allows us to build exactly the methods we require.
Exactly how the method signature defines how the method must be called and how the method must return a value deserves further discussion. Let's give each part of the signature a name, so we can break it into chunks and learn about the parts.
Tip
Here is a signature of a method with its parts labeled up and ready for discussion. Also, have a look at the table to further identify which part of the signature is which. This will make the rest of our discussions on methods straightforward:
modifier
| return type
| name of method (parameters)
And here are a few examples that we have used so far so you can clearly identify the part of the signature under discussion:
Modifiers
In our previous examples, we only used a modifier in a couple of examples, partly because the method doesn't have to use the modifier. The modifier is a way of specifying which code can use (call) your method using modifiers such as public
and private
. Actually, regular variables can have modifiers too, for example:
// Most code can see me public int a; // Code in other classes can't see me private String secret = "Shhh, I am private";
Modifiers (for methods and variables) are an essential Java topic, but they are best dealt with when we are discussing the other vital Java topic that we have skirted around so many times already—classes. We will discuss them in the next chapter.
Note
As we can see from our example methods and the fact that all the code we have written so far works just fine, modifiers are not necessary to facilitate our learning so far, although they will be a big part of our future learning from Chapter 9, Object-Oriented Programming onwards.
Return types
Next up is the return type. Like a modifier, a return type is optional. So, let's look a bit closer at it. We have seen that our methods do stuff. But what if we need the results from what they have done? The simplest example of a return type we have seen so far is:
int addAToB(int a, int b){
int answer = a + b;
return answer;
}
Here, the return type in the signature is highlighted, so the return type is int
. The addAToB
method sends back (returns) to the code that called it a value that will fit in an int
variable.
The return type can be of any Java type that we have seen so far. The method does not have to return a value at all, however. In this case, the signature must use the void
keyword as the return type. When the void
keyword is used, the method body must not attempt to return a value, as this will cause a compiler error. It can, however, use the return
keyword without a value. Here are some combinations of the return type and the use of the return
keyword that are valid:
void doSomething(){ // our code // I'm done going back to calling code here // no return is necessary }
Another combination of them is as follows:
void doSomethingElse(){ // our code // I can do this as long as I don't try and add a value return; }
The following code is yet another combination:
void doYetAnotherThing(){ // some code if(someCondition){ // if someCondition is true returning to calling code // before the end of the method body return; } // More code that might or might not get executed return; /* As I'm at the bottom of the method body and the return type is void, I'm really not necessary but I suppose I make it clear that the method is over. */ }
And in this final example we return a String:
String joinTogether(String firstName, String lastName){ return firstName + lastName; }
We could call each of the preceding methods, in turn, like this:
// OK time to call some methods doSomething(); doSomethingElse(); doYetAnotherThing(); String fullName = joinTogether("Alan ","Turing") // fullName now = Alan Turing // continue with code from here
The preceding code would execute all the code in each method in turn.
The name of a method
The method name when we design our own methods is arbitrary. But it is a convention to use verbs that clearly explain what the method will do. Also, use the convention of the first letter of the first word of the name being lower case and the first letter of subsequent words being upper case. This is called camel case as we learned while learning about variable names, for example:
XGHHY78802c(){ // code here }
The preceding method is perfectly legal and will work; however, let's take a look at a much clearer example that uses the conventions:
doSomeVerySpecificTask(){ // code here } getMyFriendsList(){ // code here } startNewMessage(){ // code here }
Now that this is much clearer, let's have a look at the parameters in methods.
Parameters
We know that a method can return a result to the calling code. But what if we need to share some data values from the calling code with the method? Parameters allow us to share values with the method. We have actually already seen an example with parameters when we looked at return types. We will look at the same example but a little more closely at the parameters:
int addAToB(int a, int b){ int answer = a + b; return answer; }
In the preceding code, the parameters are highlighted. Parameters are contained in the parentheses (parameters go here)
immediately after the method name. Note that in the first line of the method body, we use a + b
as if they are already declared and initialized variables. This is because they are. The parameters of the method signature is their declaration, and the code that calls the method, initializes them as highlighted in the next line of code:
int returnedAnswer = addAToB(10,5);
Also, as we have partly seen in the previous examples, we don't have to use just int
in our parameters, we can use any Java type, including types that we design ourselves. What's more is that we can mix and match types as well. We can also use as many parameters as necessary to solve our problem. This example might help:
void addToAddressBook(char firstInitial, String lastName, String city, int age){ /* all the parameters are now living breathing, declared and initialized variables. The code to add details to address book goes here. */ }
Now, it's time to get serious about our bodies (method bodies, obviously).
Working in the method body
The body is the part that we have been kind of avoiding with comments such as:
// code here
Or comments like:
// some code
But actually, we know exactly what to do in the body already. Any Java syntax that we have learned so far will work in the body of a method. In fact, if we think back, all the code we have written so far has been in a method.
The best thing we can do next is write a few methods that actually do something in the body.
Using methods – demo apps
Here, we will quickly build two apps to explore methods a bit further. First, we will look into the fundamentals with the Real World Methods
app and then we will glimpse at a new topic, method overloading, in action with the Exploring Method Overloading
app.
As usual, you can open the ready-typed code files in the usual way. The next two examples on methods can be found in the download bundle in the chapter 8
folder and the Real World Methods
and Exploring Method Overloading
subfolders.
Real-world methods
First, let's make ourselves some simple working methods complete with the return types parameters and fully functioning bodies.
To get started, create a new Android project called Real World Methods
, use a Blank Activity template, and leave all the other settings at their default. Switch to the MainActivity.java
file by clicking on the MainActivity.java tab above the editor, and then we can start coding.
First, add these three methods to MainActivity
. Add them just after the closing curly brace, }
, of the onCreate
method:
String joinThese(String a, String b, String c){ return a + b + c; } float getAreaCircle(float radius){ return 3.141f * radius * radius; } void changeA(int a){ a++; }
The first method that we added is called joinThese
. It will return a String
variable and requires three String
variables passed into it. In the method body, there is only one line of code. The return a + b + c
code will concatenate the three Strings that are passed into it and return the joined Strings as the result.
The next method getAreaCircle
takes a float
variable as an argument and then returns a float
variable too. The body of the method simply uses the formula for the area of a circle to incorporate the passed-in radius and then returns the answer to the calling code. The odd-looking f
at the end of 3.141
is to let the compiler know that the number is of the type float
. Any floating point number is assumed to be of the type double
unless it has the trailing f
.
The third and final method is the simplest of all the methods. Note that it doesn't return anything; it has a void
return type. We have included this method to make clear an important point that we want to remember about methods. But, let's see it in action before we talk about it.
Now in onCreate
, after the call to setContentView
, add this code that calls our methods:
String joinedString = joinThese("Methods ", "are ", "cool "); Log.i("joinedString = ","" + joinedString); float area = getAreaCircle(5f); Log.i("area = ","" + area); int a = 0; changeA(a); Log.i("a = ","" + a);
Run the app and observe the output in the logcat window, which is shown next for your convenience:
joinedString =﹕ Methods are cool area =﹕ 78.525 a =﹕ 0
In the logcat output, the first thing we can see is the value of the String: joinedString
. As expected, it is the concatenation of the three words we passed into the joinThese
method.
Next, we can see that getAreaCircle
has indeed calculated and returned the area of a circle based on the length of the radius passed in.
The final line of output is perhaps the most interesting: a=: 0
. In the onCreate
method, we declared and initialized int a
to 0 and then we called changeA
. In the body of changeA
, we incremented a
with the a++
code. Yet back in onCreate
, we see that when we use Log
to print the value of a
on the logcat window, it is still 0
.
So, when we passed in a
to the changeA
method, we were actually passing the value stored in a
and not the actual variable a
. This is referred to as passing by value in Java.
Tip
When we declare a variable in a method, it can only be seen in that method. When we declare a variable in another method, even if it has the exact same name, it is not the same variable. A variable only has scope within the method that it was declared in.
With all primitive variables, this is how passing them to methods works. With reference variables, it works slightly different, and we will see how it works in the next chapter.
Note
I have talked about this scope concept with a number of people who are new to Java. To some, it seems blindingly obvious, even natural. To others, however, it is a cause of constant confusion. Should you fall into the latter category, don't worry because we will talk a bit more about this later in the chapter, and in subsequent chapters, we will go into great detail exploring scope and make sure that it is no longer an issue.
Let's look at another practical example of methods and learn something new at the same time.
Exploring method overloading
As you can see, methods are really quite diverse and deep as a topic. But hopefully, by taking them a step at a time, we will see that they are not daunting in any way. We will also be returning to methods in the next chapter. For now, let's create a new project to explore method overloading.
Create a new blank project called Exploring Method Overloading
, and then we will get on with writing three methods, but with a slight twist.
As we will now see, we can create more than one method with the same name provided that the parameters are different. The code in this project is very simple. It is how it works that might appear slightly curious until we analyze it after.
In the first method, we will simply call it printStuff
and pass in an int
variable via a parameter that is to be printed. Insert this method after the closing brace, }
, of onCreate
but before the closing brace, }
, of MainActivity
. Remember to import the Log
class in the usual way:
void printStuff(int myInt){ Log.i("info", "This is the int only version"); Log.i("info", "myInt = "+ myInt); }
In this second method, we will also call it printStuff
but pass in a String
variable that is to be printed. Insert this method after the closing brace, }
, of onCreate
but before the closing brace, }
, of MainActivity
:
void printStuff(String myString){ Log.i("info", "This is the String only version"); Log.i("info", "myString = "+ myString); }
In this third method, we will call it printStuff
but pass in a String
variable and int
that is to be printed. Insert this method after the closing brace, }
, of onCreate
but before the closing brace, }
, of MainActivity
:
void printStuff(int myInt, String myString){ Log.i("info", "This is the combined int and String version"); Log.i("info", "myInt = "+ myInt); Log.i("info", "myString = "+ myString); }
Now, insert this code just before the closing brace, }
, of the onCreate
method to call the methods and print some values on the Android console:
// Declare and initialize a String and an int int anInt = 10; String aString = "I am a string"; // Now call the different versions of printStuff // The name stays the same, only the parameters vary printStuff(anInt); printStuff(aString); printStuff(anInt, aString);
Now, we can run the app on the emulator or a real device. Here is the console output:
info﹕ This is the int only version info﹕ myInt = 10 info﹕ This is the String only version info﹕ myString = I am a string info﹕ This is the combined int and String version info﹕ myInt = 10 info﹕ myString = I am a string
As you can see, Java has treated three methods with the same name as totally different methods. This, as we have just demonstrated, can be really useful. It is called method overloading.
Note
Method overloading and overriding confusion
Overloading is when we have more than one method with the same name but different parameters.
Overriding is when we essentially replace a method with the same name and the same parameter list.
We know enough about overloading and overriding to complete this book, but if you are brave and your mind is wandering, yes you can override an overloaded method; however, that is something that we've kept for another time.
This is how it all works. In each of the steps in which we wrote code, we created a method called printStuff
. But each printStuff
method has different parameters, so each is actually a different method that can be called individually:
void printStuff(int myInt){ ... } void printStuff(String myString){ ... } void printStuff(int myInt, String myString){ ... }
The body of each of the methods is simple and just prints out the passed-in parameters and confirms which version of the method is being called currently.
The next important part of our code is when we make it plain, which we mean to call by using the appropriate arguments that match the parameters in the signature. Then, we call each method in turn, using the appropriate parameters so that the compiler knows the exact method required:
printStuff(anInt); printStuff(aString); printStuff(anInt, aString);
We now know all we need to about methods, so let's take a quick second look at the relationship between methods and variables. Then, we'll get our heads around this scope phenomenon a bit more.