Module 4: Methods: Parameters and Return Values


Objectives

 

By the end of this module, for simple programs, you will be able to:

 

4.0 Audio:
 


4.0    Why we need methods: via an example

 

Before we get into the details of methods (yes, that's jargon), we're going to get a "high altitude" view:

  • We'll glance over a fairly long program with no methods.

  • We'll briefly look at the same thing but written with methods.

  • The idea now, before getting into details, is to get a sense of why it's useful to define and use our own methods.
 

We'll use this application as our example:

  • We've already encountered the Klingon language.

  • The general perception is that it's a more guttural language than English.

  • To quantify this, we will look through all the words and count the number of vowels, and the number of consonants.

  • And we'll look at the fraction of letters that are vowels.

  • The more guttural, the lower the fraction.
 

4.1 Exercise: Download, compile and execute VowelDensity.java to see these fractions being computed for each of English and Klingon. You will also need WordTool.java, words.txt, and klingonwords.txt. Try to read the code to see if you can make sense of parts of it.
 

We'll now list the whole program in its entirety:


public class VowelDensity {

    public static void main (String[] argv)
    {
	char[] vowels = {'a', 'e', 'i', 'o', 'u'};

	// First, English.
	String[] englishWords = WordTool.getUnixWords ();
	// Set up counters:
	int numVowels = 0;
	int numConsonants = 0;

	// Loop through all English words.
	for (int i=0; i<englishWords.length; i++) {
            // For each word, we'll count the number of vowels:
	    int v = 0;
	    for (int j=0; j<englishWords[i].length(); j++) {
		// Compare the j-th letter with all vowels
		for (int k=0; k<vowels.length; k++) {
		    if ( vowels[k] == englishWords[i].charAt(j) ) {
			v++;
		    }
		}
	    }
	    numVowels = numVowels + v;
	    numConsonants = numConsonants + (englishWords[i].length() - v);
	    // Note: if the word length is N, there are N-v consonants.
	}
	
	double englishFraction  = (double) numVowels / (double) (numConsonants + numVowels);
	System.out.println ("Fraction for English: " + englishFraction);

	// Next, Klingon (without comments embedded)
	String[] klingonWords = WordTool.getKlingonWords ();
	numVowels = 0;
	numConsonants = 0;
	for (int i=0; i<klingonWords.length; i++) {
	    int v = 0;
	    for (int j=0; j<klingonWords[i].length(); j++) {
		for (int k=0; k<vowels.length; k++) {
		    if ( vowels[k] == klingonWords[i].charAt(j) ) {
			v++;
		    }
		}		    
	    }
	    numVowels = numVowels + v;
	    numConsonants = numConsonants + (klingonWords[i].length() - v);
	}

	double klingonFraction  = (double) numVowels / (double) (numConsonants + numVowels);
	System.out.println ("Fraction for Klingon: " + klingonFraction);

	// A 3rd language?
        // ...
    }

}
     

So, yes, it's long. But let's point out a few things:

 

4.2 Exercise: How many more lines would need to be added to the above code to add a 3rd language?
 

Now let's look at the same application written with methods:


public class VowelDensity2 {

    public static void main (String[] argv)
    {
	// First, English.
	String[] englishWords = WordTool.getUnixWords ();
	double englishFraction = processWords (englishWords);
	System.out.println ("Fraction for English: " + englishFraction);

	// Next, Klingon
	String[] klingonWords = WordTool.getKlingonWords ();
	double klingonFraction = processWords (klingonWords);
	System.out.println ("Fraction for Klingon: " + klingonFraction);

        // A 3rd language?
        // ...
    }

    static double processWords (String[] words)
    {
	// This method will go through the array of words and update
	// the vowel/consonant counts.
	int numVowels = 0;
	int numConsonants = 0;
	for (int i=0; i<words.length; i++) {
	    int v = vowelCount (words[i]);      // This method does the work.
	    numVowels = numVowels + v;
	    numConsonants = numConsonants + (words[i].length() - v);
	}
	
	double fraction  = (double) numVowels / (double) (numConsonants + numVowels);
	return fraction;
    }

    static int vowelCount (String w)
    {
	int numV = 0;
	char[] letters = w.toCharArray ();
	for (int j=0; j<letters.length; j++) {
	    if ( isVowel(letters[j]) ) {
		// if the j-th letter is a vowel, update the count.
		numV++;
	    }
	}
	return numV;
    }

    static boolean isVowel (char c)
    {
        // See if c is a vowel.
	char[] vowels = {'a', 'e', 'i', 'o', 'u'};
	for (int k=0; k<vowels.length; k++) {
	    if (c == vowels[k]) {
		return true;
	    }
	}
	return false;
    }

}
     

Without understanding the details, let's point out a few things:

 

4.3 Exercise: How many more lines would need to be added to the above code to add a 3rd language?
 

Why methods are useful:

 


4.1    Methods: what we've already seen

 

Before continuing, it is essential to review methods as described in Module 3 of Unit-0.

What to review:

 

4.4 Exercise: Review that module. Now.
 


4.2    Method parameters

 

To see how methods work, let's start with a simple example:

 

4.5 Exercise: Edit, compile and execute the above program. Then, just before the declaration of j but after the first invocation of incrementAndPrint in main, print the value of i.
 

Let's examine a few details:

  • First, method names are like variable names: usually a collection of letters and numbers.
           ⇒ e.g., incrementAndPrint.

  • The capitalization inside the name (incrementAndPrint) has no effect on the compilation/execution
           ⇒ It's merely for readability.

  • Next, let's distinguish between invocation

    and definition:

  • We'll focus on as few aspects of the definition.

    When you read this, say to yourself

    • "The method takes one parameter, an integer."
    • "The method returns nothing" (void).

  • Next, let's examine the flow of execution with the first invocation:

  • At the next invocation:

  • What's important to remember: the value in j gets copied into the parameter variable k.
           ⇒ Thus, the value in j is not affected inside incrementAndPrint.

  • It's possible to use the same name for the parameter variables, even though they are different:

    Here, we say that the scope of the variables i and j in the method incrementAndPrint is limited to all the code inside the method.

 

4.6 Video:

 

4.7 Exercise: Fill in the code in the method below so that the output of the program is:

  *****
  ***
  *
  

 

4.8 Audio:
 


4.3    Multiple parameters

 

Remember Pythagoras? We know his famous result:

A Pythagorean triple is any group of three integers like 3,4,5 where the squares of the first two add up to the square of the third: 32 + 42 = 52.

We'll now write code to check whether a trio of numbers is indeed a Pythagorean triple:

  • Here, the method checkPythagoreanTriple is defined with three parameter variables.
           ⇒ All happen to identically be int's here.

  • The values at the invocation are given to the method in the order the parameters are declared:

    • The first value in the invocation goes to the first parameter variable.
    • The second value in the invocation goes to the second parameter variable. And so on.

  • A method can be invoked in various ways:

 

 

4.9 Exercise: Fill in the code in the method below so that the output of the program is 3 and 5, respectively.

 

4.10 Video:

 


4.4    Return values

 

So far, we've written methods that take values, do things and print.

We get a whole new level of programming power, when methods can compute something and return something to the invoker.

Here's an example:

  • First, recall the meaning of power:
    • "2 raised to the power 5" or 25 means 2 × 2 × 2 × 2 × 2
    • In this case, it evaluates to 32.

  • To see how the program works, let's simplify the program a little:

    • Initially, a has the value 0.
    • Then, the next thing to execute is the invocation to power.
    • Only after power completes execution, does the assignment to a occur.

  • After power completes execution, the return value (8 in this case), gets assigned to a.

 

4.11 Exercise: In module4.pdf, trace the entire execution of the above program step by step, including every iteration of the for-loop.
 

4.12 Exercise: Fill in the code in the method below so that the output of the program is 3 and 5, respectively.

 

4.13 Audio:
 


4.5    Using method calls in expressions

 

Methods that return values can be used and combined in expressions, in powerful ways.

For example:

  • In the first statement, power gets called twice:
    • The first time, it is with parameter values 2 and 3.
    • The second time with parameters 2 and 4.
    • The resulting expression is evaluated and the final result stored in a

 

4.14 Exercise: Add the statement System.out.println ("power of " + x + " to " + k) as the first line inside the power method in ExpressionExample.java. Then, in module4.pdf trace through the second expression above, when b is assigned a value.
 

4.15 Exercise: In an earlier exercise, you wrote a method to find the smallest among three values. Suppose you had to find the smallest among five variables. Invoke the the method (with the three variables) twice to find the smallest among five. Fill in your code below:

 

4.16 Video:

 


4.6    Methods and arrays

 

When working with arrays, it's often useful to have some computations delegated to methods.

For example:

 

4.17 Exercise: Modify the above program to compute and print the mean of the array's elements.
 

A method can return an array:

 

4.18 Exercise: In module4.pdf, trace through the steps of execution.
 

Let's point out a few things:

  • Notice how the return type is declared in the method definition:

  • This means that, inside the method, we need to return the same type of variable:

  • Each call to makeConsecutiveIntegers creates a fresh array:

    • The size-5 array is "alive" until the second call to makeConsecutiveIntegers.
    • After the second call returns, the variable A refers to the newly return (3-element) array.

  • If we had wanted to keep both arrays around, we'd write:

  • We can re-use a variable name inside a method:

    Here, the scope of A inside makeConsecutiveIntegers is limited to all the code inside the method.

    The two A variables in the program are really different variables.

 

4.19 Exercise: In FindLargestInArray.java, fill in the code needed to return the largest element of an array

public class FindLargestInArray {

    public static void main (String[] argv)
    {
	double[] someNumbers = {2.718, 3.141, 1.414, 1.618};
	double largest = findLargest (someNumbers);
	System.out.println (largest);
    }

    // Write your method here:

}
     
 

4.20 Video:

 


4.7    Multiple returns in a method

 

A method can have many points of return.

When a return is executed, then execution returns to the invoking code.

Think of multiple return's as multiple doors to leave a method.

Consider this example:

  • Observe that the min method has two return's.

  • Only one return is executed in any one invocation of a method, e.g.

 

4.21 Exercise: In module4.pdf, trace the execution of the above program. Why does it work (that is, why does it find the minimum element in the array)? How often is the first return in min executed? How often is the second one executed?
 

Here's another example with multiple return's

public class NegativeElement {

    public static void main (String[] argv)
    {
	double[] data = {4.0, 5.0, -2.0, 1.0, 3.0};
	boolean hasNegative = hasNegativeElement (data);
	if (hasNegative) {
	    System.out.println ("Yeah");
	}
	else {
	    System.out.println ("Nah");
	}
    }

    static boolean hasNegativeElement (double[] A)
    {
	for (int i=0; i<A.length; i++) {
	    if (A[i] < 0) {
		return true;
	    }
	}
	return false;
    }

}
     
 

4.22 Exercise: In module4.pdf, trace the execution of the above program. How often is the second return in min executed?
 

Let's point out a few things regarding the above program:

  • First, we could "compact" the code in main by writing:
    	double[] data = {4.0, 5.0, -2.0, 1.0, 3.0};
    	if ( hasNegativeElement(data) ) {
    	    System.out.println ("Yeah");
    	}
    	else {
    	    System.out.println ("Nah");
    	}
         

  • This is possible because the return value of hasNegativeElement is Boolean, i.e., will result in true or false.
 

4.23 Exercise: In MyNegativeElement.java, rewrite the hasNegativeElement method so that there is only one return in it.
 

4.24 Video:

 


4.8    Call-by-value vs call-by-reference

 

Consider the following program:

 

4.25 Exercise: Before executing the program, guess the output. Then, edit, compile and execute to see what the output is. Use SwapExample.java as your file name.
 

To explain what happened, let's simplify a little:

  • First, consider this simple program:

  • Here, the value in a is copied into the parameter variable x.

  • This type of parameter behavior is called call-by-value:
           ⇒ The "value" is what's passed in a method "call"

  • Call-by-value applies to parameters of basic types like int and double.

  • Arrays, however, are treated differently.

  • Consider the same example for arrays:

  • Here, a "reference" (as it's called) is passed:

    • A reference allows a method to modify the original.
    • Yes, it's a little strange.

  • This parameter behavior is called call-by-reference
           ⇒ It applies to arrays, and more generally objects.

  • We will learn about objects (which are a special type of Java construct) later.
 

Finally, let's look at this program:

  • Here, we are NOT passing the array itself.

  • We are passing a single element of the array, an integer.

  • Because it's an integer, it gets copied into the parameter variable.

  • Thus, a change to x has no impact on the array.
 

4.26 Video:

 


4.9    Communicating via global variables

 

Consider this task: We're going to scan the letters in a string and do two things:

  1. Count the number of spaces (an integer)
  2. See whether or not there's an apostrophe (boolean).
And we'd like to create a single method that can "do this job".

We could start out with a method like:

     static int analyze (String s)
     {
	char[] letters = s.toCharArray ();
        int numSpaces = 0;
	boolean hasApostrophe = false;
	for (int i=0; i<letters.length; i++) {
	    if (letters[i] == ' ') {
		numSpaces++;
	    }
	    if (letters[i] == '\'') {
		hasApostrophe = true;
	    }
	}
        // return numSpaces or hasApostrophe?
     
     }
     
But do we return the integer or the boolean?
  • In Java, you can only return one type of thing.

  • So either we declare
         static int analyze (String s)
         {
             // return an int from somewhere in the code.
         }
         
    or
         static boolean analyze (String s)
         {
             // return a boolean from somewhere in the code.
         }
         
 

There are two ways to solve the problem of having a method "return many things":

  1. Put the many things in an object and return the object.

  2. Put the values to be returned in a place that's accessible across all methods.
The former will have to wait until we dive into the more complex topic of objects.

The second approach is easy to work with:

  • First, let's remember that we have thus far defined variables inside methods, like:
    
         static void analyze (String s)
         {
            int numSpaces = 0;
    	boolean hasApostrophe = false;
    
            // ... (other code) 
    
         }
         
  • These variables are available for use only inside the method where they are definied.

  • The following would NOT compile, for example:
    
         static void analyze (String s)
         {
            int numSpaces = 0;
            boolean hasApostrophe = false;
    
            // ... (other code) 
    
         }
    
         static void someOtherMethod ()
         {
            // ... 
    
            numSpaces = numSpaces + 1;         // Will not compile: cannot access the variable here... 
    
    
            // ... 
    
         }
         
  • However, what DOES work is to have variables declared outside methods where they can be shared by all methods, as in.
    
    public class StringAnalyzer {
     
         static int numSpaces;
         static boolean hasApostrophe;
    
    
         static void analyze (String s)
         {
            // We can access the variables here: 
            numSpaces = 0;
            hasApostrophe = false;
    
            // ... 
    
         }
    
         static void someOtherMethod ()
         {
            // ... 
    
            // And here: 
            numSpaces = numSpaces + 1;         
    
            // ... 
    
         }
    }
    
         
  • Such shared variables are called global variables because they are global to all the methods.

  • One can initialize and set values for global variables in the same way we've done for other kinds of variables, as in:
    
    public class StringAnalyzer {
     
         static int a = 1;
         static double b = 2.3;
         static int[] A = {1,2,3,4,5};
         static double[] B = new double [5];
    
    
         static void someMethod (String s)
         {
            // ... 
         }
    
    }
    
         
  • What we cannot do outside of methods is write regular code with for-loops, conditionals etc. For example, the following will NOT compile:
    
    public class StringAnalyzer {
     
         // This is fine to have outside
         // methods because it's a variable declaration
         static int a = 1;
    
         // This is NOT:
         a = a + 1;
         for (int i=0; i<5; i++) {
             System.out.println (i);
         }
    
    
         static void someMethod (String s)
         {
            // ... 
         }
    
    }
    
         
 

4.27 Exercise: In StringAnalyzer.java complete the full program and test with the following:

     public static void main (String[] argv)
     {
         String testString = "hey, aren't globals cool?";
         analyze (testString);
         System.out.println ("# spaces=" + numSpaces + " hasApostrophe=" + hasApostrophe);
     }
     
 

4.28 Audio:
 


4.10    null

 

One of the most useful, intriguing and slightly forbidding reserved words is : null.

Forbidding, because it often occurs in a negative context such as when an error occurs or when you seek to prevent them.

null is NOT the same thing as zero, nor does it refer to "nothing."
 

Let's first distinguish between basic types and more complex types:

  • The basic types are things like int's char's and double's.

  • The more complex types are like arrays.

  • Once we understand objects, we'll see that Strings's are also the more complex kind. In fact, we have a hint of this because of methods inside a string like length() and charAt().

  • All variables of the more complex types need to be properly initialized, most often with the new operator, as in
         int[] A = new int [5];
         
  • It's when they are not initialized that null comes in.

  • The default value for these types of variables (prior to being initialized) is null.

  • And it's when we forget to initialize that we get the infamous null-pointer exception.
 

Let's look at an example:

public class NullExample {

    static int[] A;


    public static void main (String[] argv)
    {
	print (A);
    }

    static void print (int[] B)
    {
	for (int i=0; i<B.length; i++) {
	    System.out.println (B[i]);
	}
    }

}
     
Looks harmless, right?
 

4.29 Exercise: Write up, compile and execute. What you will see is an error (exception) printed to the screen: the null pointer exception.
 

Let's see what happened:

  • Look back at the program and notice that the array was never initialized.

  • This means that the variable A has the value null in it.

  • So, null gets copied into the parameter variable B when print gets called.

  • Then, in the for-loop, when we try to access B.length there is no real array.

  • This is what Java flags as a null-pointer exception.
 

Our purpose in introducing this reserved word here is to give you a "heads up". We'll deal with this more intensively later.

Lastly, you might be wondering what the word "pointer" is doing here? That, as it will turn out, is a really fundamental and powerful concept.
 


4.11    Methods in String

 

Now that we know a little more about methods, let's go back to String for a moment:

  • Recall the length() method inside a string:
         String s = "Hola";
         int k = s.length ();
         
    When executed length() method inside the string "Hola" returns an integer value, in this case 4

  • Similarly, in this example,
         String s = "Ciao";
         char c = s.charAt (3);
         
    the charAt() method inside the string "Ciao" returns the letter 'o'.
 

There are many useful methods in String. Here are some useful ones:

  • We can check equality using a method:
         String s = "Ciao";
         String r = "Halo";
         if ( s.equals(r) ) {
             System.out.println ("They're equal");
         }
         
    (Nothing gets printed)
  • Just as useful:
         String s = "Hujambo";
         String r = "hujambo";
         if ( s.equalsIgnoreCase(r) ) {
             System.out.println ("They're equal");
         }
         
    (They are equal, ignoring whether upper or lower case).

  • Some other useful ones:
         String s = "Ni Hau";
         if ( s.startsWith("Ni") ) {
             System.out.println ("This string begins with \"Ni\"");
         }
    
         String r = "Konnichiwa";
         boolean rhymesWithWa = r.endsWith ("wa");
    
         int k = r.indexOf ('h');        // k is 6
         String r2 = r.toLowerCase ();   // Makes a lowercase version.
         String r3 = r.substring (1,3);  // Returns "on"
    
         
 


4.12    Reading and writing

 

First, reading.

Consider this snippet of code:

  • When reading a method call, do not at first chase down the method to look at its body.

  • Instead, merely ask:
    • What does the method take as parameters?
    • What does the method return?

  • Here, we can tell that the parameters must be double's:

    Although: we probably don't know the parameter variable names.

  • Similarly, we can tell what the return type must be:

 

Let's look at a method declaration:

  • Here, without knowing anything about how the method works, we can see how it needs to be called, e.g.,

  • Of course, the method code now must return an array:

 

Next, writing.

  • The following examples show various writing styles.

  • Most commonly used: one or two spaces (in the Math.sqrt examples above).

  • Sometimes, spaces between parameters makes the program more readable, as in the last call to sum above.

  • There are also two fundamentally different styles in writing method definitions, e.g.,

  • For the most part, we will prefer the former.

  • There are also different styles with regard to the opening brace:

  • Again, we will prefer the former.
 

4.30 Exercise: Now go back and read through the second Vowel-Density program that we started the module with. Examine how much easier they are to read with your newfound understanding of methods.
 

4.31 Video:

 


4.13    When things go wrong

 

Below, try to identify the errors first by reading, then compile and run to see if you were right. Add a main() method where needed.
 

4.32 Exercise: What is wrong with the program below?

How can you fix it (in Ex4_32.java).
 

4.33 Exercise: Identify the errors in the method below:

And fix it in Ex4_33.java.
 

4.34 Exercise: What is the compiler error in the method below?

Fix the error in Ex4_34.java.
 


4.14    Meta

 

Read the following program carefully:

public class WellDone {

    public static void main (String[] argv) 
    {
        System.out.println ("CONGRATULATIONS!");
    }   

}
     
 

Seriously, you should feel good about coming this far. And should reward yourself.

Let's point out a few things:

  • These four topics
    1. loops,
    2. conditionals,
    3. arrays,
    4. and methods,
    form the core of basic programming.

  • They are hard to master and it's astonishing how challenging it can get when you combine all of these.

  • In the old days, this was more or less ALL there was to a programming language (FORTRAN, if you've heard of it). You spent the rest of your time writing more and more code for complex tasks.

  • Do not fret if you feel "shaky" about these topics now.

  • You get an ever increasing sense of mastery over time, long after you've encountered more advanced topics.

  • For example, when you are doing, say, Unit-8, you can come back and look at some of the exercises here. They will seem quite easy.

  • Also, this is typically a moment when many students encounter self-doubt, as in:
    • "I can't really do this. This is not for me".
    • "Why do I find this so hard?"
    • "I'm clearly not wired for computer stuff".
    Here's the thing ... everyone finds it hard. It's no different an experience in learning a foreign language or a musical instrument. If you are used to the comfortable learning in a definitions/facts type of course, blame that on that type of course. Skill learning is different. So, be aware of self-doubt and do something concrete to ignore it.

  • For the moment, the best thing to do is to "stick with it".

  • Take a break and come back to some things that you didn't quite get. The forthcoming review should also help.
 

4.35 Audio:


On to Review, part I



© 2017, Rahul Simha