Chapter 9: Learning Java Methods

As we are starting to get comfortable with Java programming, in this chapter we will take a closer look at methods because although we know that you can call them to make them execute their code, there is more to them than we have discussed so far.

In this chapter, we will look at the following:

  • Method structure
  • Method overloading versus overriding
  • A method demo mini-app
  • How methods affect our variables
  • Method recursion

First, let's go through a quick method recap.

Technical requirements

You can find the code files present in this chapter on GitHub at https://github.com/PacktPublishing/Android-Programming-for-Beginners-Third-Edition/tree/main/chapter%2009.

Methods revisited

This figure sums up where our understanding of methods is at the moment:

Figure 9.1 – Understanding methods

Figure 9.1 – Understanding methods

As we can see in the figure, there are still a couple of question marks around methods. We will totally take the lid off methods and see how they work and what exactly the other parts of the method are doing later in the chapter. In Chapter 10, Object-Oriented Programming, and Chapter 11, More Object-Oriented Programming, we will clear up the last few parts of the mystery of methods while discussing object-oriented programming.

What exactly are Java methods?

A method is a collection of variables, expressions, and control flow statements bundled together inside an opening and closing curly brace preceded by a signature. 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.

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 the setCoordinates method, our program's execution would branch to the code contained within that method. The method would execute all the statements inside it, step by step, until it reaches the end, and then return 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 method 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 variable, int answer. The line return answer returns the value stored in answer to the calling code, causing myAnswer to be initialized with the value 6.

Notice that each of the method signatures in the preceding examples varies a little. The reason for this is the Java method signature is quite flexible, allowing us to build exactly the methods we need.

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 up into pieces and learn about the parts.

The following bolded text is a method signature with its parts labeled up ready for discussion. Also, have a look at the table that follows the bolded text to further clarify which part of the signature is which. This will make the rest of our discussion on methods straightforward:

modifier | return-type | name of method (parameters)

Modifier

In our earlier examples, we only used a modifier in a couple of examples, partly because the method doesn't have to use a modifier. The modifier is a way of specifying what code can use (call) your method, by using modifiers such as public and private.

Variables can have modifiers too, as follows:

// 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 we have skirted around a few times already – classes. We will do so in the next chapter.

Return type

Next in the table is the return type. Like a modifier, a return type is optional. So, let's look at it a bit closer. We have seen that our methods do anything we can code in Java. But what if we need the results from what a method has done? The simplest example of a return type we have seen so far is as follows:

int addAToB(int a, int b){

   int answer = a + b;

   return answer;

}

Here, the return type in the signature is highlighted. The return type is an 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 any Java type we have seen so far. However, the method does not have to return a value at all. When the method does not return a value, the signature must use the void keyword as the return type. When the void keyword is used, the method body must not try 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 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 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.

   */

}

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.

Name of the method

Next in the table is the name of the method. When we design our own methods, the method name is arbitrary. But it is the 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 lowercase and the first letter of any later words being uppercase. This is called camel case, as we learned while learning about variable names. Consider this next example:

void XGHHY78802c(){

   // code here

}

The preceding method is perfectly legitimate and will work; however, let's look at three much clearer examples that use these conventions:

void doSomeVerySpecificTask(){

   // code here

}

void getMyFriendsList(){

   // code here

}

void startNewMessage(){

   // code here

}

This is much clearer as the names make it obvious what the methods will do.

Let's have a look at the parameters in methods.

Parameters

The final topic in the table is 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 send values into the called method. We have 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;

}

Here, the parameters are highlighted. Parameters are contained in parentheses, (parameters go here), immediately after the method name.

Notice that in the first line of the method body, we use a + b as if they are already declared and initialized variables. That is because they are. The parameters of the method signature are 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 earlier examples, we don't have to just use int in our parameters. We can use any Java type, including types we design ourselves.

What is more, we can mix and match types as well. We can also use as many parameters as is necessary to solve our problem. An 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.

   */

}

The preceding example would have four declared and initialized variables that are ready to use.

Now we will look at the method body – what goes inside the method.

The body

In our previous examples, we have been pseudo-coding our method bodies with comments such as the following:

// code here

The following has also been used:

// some code

The addToAddressBook method has also been used:

/*

             all the parameters are now living, breathing,

             declared and initialized variables.

             

             The code to add details to address book goes

             here.

      */

But we know exactly what to do in the body already. Any Java syntax we have learned about so far will work in the body of a method. In fact, if we think back, all the code we have written in this book so far has been in a method.

The best thing we can do next is to write a few methods that do something in the body.

Using method demo apps

Here we will quickly build two apps to explore methods a bit further. First, we will explore the fundamentals with the Real World Methods app, and then we will glimpse a new topic, method overloading, with the Exploring Method Overloading app.

As usual, you can open the ready-typed code files in the usual way. The next two examples of methods can be found in the download bundle in the Chapter 9 folder and the Real World Methods and Exploring Method Overloading sub-folders.

Real-world methods

First, let's make ourselves some simple working methods complete with return type parameters and fully functioning bodies.

To get started, create a new Android project called Real World Methods, use the Empty Activity template, and leave all the other settings at their default. Switch to the MainActivity.java file by left-clicking the MainActivity.java tab above the editor and we can start coding.

First, add these three methods to the MainActivity class. 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 we added is called joinThese. It will return a String value and needs 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, named 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, incorporating the passed-in radius, and then returns the answer to the calling code. The odd-looking f on the end of 3.141 is to let the compiler know that the number is of the float type. Any floating-point number is assumed to be of the double type unless it has the trailing f.

The third and final method is the simplest of all the methods. Notice 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 the onCreate method, after the call to the setContentView method, add this code, which calls our three new methods and then outputs some text to the logcat window:

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 see the output in the logcat window, which is provided here 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 joinedString string. 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 fact that the a variable still holds the value 0 even after it has been passed into the changeA method deserves a separate discussion.

Discovering variable scope

The final line of output is most interesting: a=: 0. In the onCreate method, we declared and initialized int a to 0, and then we called the changeA method. In the body of changeA, we incremented a with the code a++. Yet, back in the onCreate method, we see that when we use the Log.i method to print the value of a to 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 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 it was declared.

With all primitive variables, this is how passing them to methods works. With reference variables, it works slightly differently and we will see how in the next chapter.

Important Note

I have talked about this scope concept with a number of people 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 this chapter, and in future chapters, we will go into greater depth with exploring scope and make sure 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 we are beginning to realize, methods are quite 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 the topic of method overloading.

Create a new Empty Activity template project called Exploring Method Overloading, then we will get on with writing three methods, but with a slight twist.

As we will soon see, we can create more than one method with the same name provided that the parameters are different. The code in this project is 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 to be printed.

Insert this method after the closing } of the onCreate method but before the closing } of the MainActivity class. 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 to be printed. Insert this method after the closing } of the onCreate method but before the closing } of the MainActivity class:

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 yet again but pass in a String variable and an int value to be printed. Insert this method after the closing } of onCreate but before the closing } of the MainActivity class:

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);

}

Finally, insert this code just before the closing } of the onCreate method to call the methods and print some values to the logcat window:

// 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 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 different methods. This, as we have just shown, can be useful. It is called method overloading.

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 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, but that is something for another time.

This is how it all works. In each of the steps where 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 trivial and just prints out the passed-in parameters and confirms which version of the method is being called.

The next important part of our code is when we make it plain which version of the method we mean to call by using the specific arguments that match the parameters in the signature. In the final step, we call each method in turn, using the matching parameters so that Java knows the exact method required:

printStuff(anInt);

printStuff(aString);

printStuff(anInt, aString);

We now know all we need to know 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.

Scope and variables revisited

You might remember in the Real World Methods project the slightly disturbing anomaly was that variables in one method were not apparently the same as those from another even if they do have the same name. If you declare a variable in a method, whether that is one of the life cycle methods or one of our own methods, it can only be used within that method.

It is no use if we do this in onCreate:

int a = 0;

And then, following that, we try to do this in onPause or some other method:

a++;

We will get an error because a is only visible within the method it was declared. At first, this might seem like a problem, but surprisingly, it is actually an especially useful feature of Java.

I have already mentioned that the term used to describe this is scope. A variable is said to be in scope when it is usable and out of scope when it is not. The topic of scope is best discussed along with classes, and we will do so in Chapter 10, Object-Oriented Programming, and Chapter 11, More Object-Oriented Programming, but as a sneak look at what lies ahead, you might like to know that a class can have its very own variables and when it does, they have scope for the whole class; that is, all its methods can "see" and use them. We call them member variables or fields.

To declare a member variable, you simply use the usual syntax after the start of the class, outside of any method declared in the class. Say our app started like this:

public class MainActivity extends AppCompatActivity {

   

   int mSomeVariable = 0;

   // Rest of code and methods follow as usual

   // ...

We could use mSomeVariable anywhere, inside any method in this class. Our new variable, mSomeVariable, has class scope. We append an m to the variable name simply to remind us when we see it that it is a member variable. This is not required by the compiler, but it is a useful convention.

Let's look at one more method topic before we move on to classes.

Method recursion

Method recursion is when a method calls itself. This might at first seem like something that happens by mistake but is an efficient technique for solving some programming problems.

Here is some code that shows a recursive method in its most basic form:

void recursiveMethod() {

     recursiveMethod();

}

If we call the recursiveMethod method, its only line of code will then call itself, which will then call itself, which will then call itself, and so on. This process will go on forever until the app crashes, giving the following error in Logcat:

java.lang.StackOverflowError: stack size 8192KB

When the method is called, its instructions are moved to an area of the processor called the stack, and when it returns, its instructions are removed. If the method never returns and yet more and more copies of the instructions are added, eventually the stack will run out of memory (or overflow) and we get StackOverflowError.

We can attempt to visualize the first four method calls using the next screenshot. Also, in the next screenshot, I have crossed out the call to the method to show that if we were able to, at some point, prevent the method call, eventually all the methods would return and be cleared from the stack:

Figure 9.2 – Method calls

Figure 9.2 – Method calls

To make our recursive methods worthwhile, we need to enhance two aspects. We will look at the second aspect shortly. First and most obviously, we need to give it a purpose. How about we ask our recursive method to sum (add up) the values of numbers in a range from 0 to a given target value, say 10, 100, or more? Let's modify the preceding method by giving it this new purpose and renaming it accordingly. We will also add a variable with class scope (outside the method) called answer:

int answer = 0;

void computeSum(int target) {

answer += target;

     computeSum(target-1);

}

Now we have a method called computeSum that takes an int as a parameter. If we wanted to compute the sum of all the digits between 0 and 10, we could call the method like this:

computeSum(10);

Here are the values of the answer variable at each function call:

First call of computeSum: answer = 10

Second call of computeSum: answer = 19

Tenth call of computeSum: answer = 55

Apparent success – until you realize that the method continues to call itself beyond the target variable reaching 0. In fact, we still have the same problem as our first recursive method and after tens of thousands of method calls, the app will crash with StackOverflowError again.

What we need is a way to stop the method calling itself once target is equal to 0. The way we solve this problem is to check whether the value of target is 0 and if it is, we quit calling the method. Have a look at the additional highlighted code shown next:

void computeSum(int target) {

     answer += target;

     if(target > 0) {

          Log.d("target = ", "" + target);

          computeSum(target - 1);

     }

     Log.d("answer = ", "" + answer);

We have used an if statement to check whether the target variable is greater than 0. We also have an additional Log.d code to output the value of answer when the method has been called for the last time. See whether you can work out what is happening before reading the explanation following the output.

The output of calling computeSum(10) would be as follows:

target =: 10

target =: 9

target =: 8

target =: 7

target =: 6

target =: 5

target =: 4

target =: 3

target =: 2

target =: 1

answer =: 55

if(target > 0) tells the code to first check whether the target variable is above 0. If it is, only then does it call the method again and pass in the value of target – 1. If it isn't, then it stops the whole process.

Important Note

We won't be using method recursion in this book, but it is an interesting concept to understand.

We know more than enough about methods to complete all the projects in the book. Let's have a quick recap with some questions and answers.

Questions

  1. What is wrong with this method definition?

    doSomething(){

       // Do something here

    }

    No return type is declared. You do not have to return a value from a method, but its return type must be void in this case. This is how the method should look:

    void doSomething(){

       // Do something here

    }

  2. What is wrong with this method definition?

    float getBalance(){

       String customerName = "Linus Torvalds";

       float balance = 429.66f;

       return userName;

    }

    The method returns a string (userName) but the signature states that it must return a float type. With a method name like getBalance, this code is what was likely intended:

    float getBalance(){

       String customerName = "Linus Torvalds";

       float balance = 429.66f;

       return balance;

    }

  3. When do we call the onCreate method? (Trick question alert!)

    We don't. Android decides when to call the onCreate method, as well as all the other methods that make up the lifecycle of an Activity. We just override the ones that are useful to us. We do, however, call super.onCreate so that our overridden version and the original version both get executed.

    Important Note

    For the sake of complete disclosure, it is technically possible to call the lifecycle methods from our code, but we will never need to in the context of this book. It is best to leave these things to Android.

Summary

In the first five chapters, we got quite proficient with a whole array of widgets and other UI elements. We also built a broad selection of UI layouts. In this chapter and the previous three, we have explored Java and the Android activity lifecycle in quite significant depth, especially considering how quickly we have done it.

We have, to a small extent, created interaction between our Java code and our UI. We have called our methods by setting the onClick attribute and we have loaded our UI layouts using the setContentView method. We haven't, however, really made a proper connection between our UI and our Java code.

What we really need to do now is to bring these things together, so we can begin to display and manipulate our data using the Android UI. To achieve this, we need to understand a bit more about classes.

Classes have been lurking in our code since Chapter 1, Beginning Android and Java, and we have even used them a bit. Up until now, however, we haven't tackled them properly other than constantly referring to Chapter 10, Object-Oriented Programming. In the next chapter, Chapter 10, Object-Oriented Programming, we will quickly get to grips with classes, and then we can finally start to build apps where the UI designs and our Java code work in perfect harmony.

Further reading

We have learned enough Java to proceed with the book. It is always beneficial, however, to see more examples of Java in action and to go beyond the minimum necessary knowledge to proceed. If you want a good source to learn Java in greater depth, then the official Oracle website is good. Note that you do not need to study this website to continue with this book. Also, note that the tutorials on the Oracle website are not set in an Android context. The site is a useful resource to bookmark and browse all the same:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.230.82