Computing Concepts with Java Essentials
Laboratory Notebook
Chapter 5 - Functions

Cay S. Horstmann
Geof Pawlicki

Your name:
Your email address:
Your student ID number:

Once this form has been customized for your institution, you can use this button to send your lab work. Be sure to read the instructions before starting your work.


Lab Objectives

To gain experience in


R1. Functions as Black Boxes

Predictable input will result in predictable output. A function maps element(s) from a problem domain, called parameter(s), into an element from a range of solutions, called the return value.

To treat a function as a "black box", one simply uses it.

For instance, here's a function to compute the volume of a cylinder, say, a beer can, given the height and diameter in millimeters.

/**
 * computes the volume of a cylinder
 * @param height the height in millimeters
 * @param diameter the diameter in millimeters
 * @return volume - in cubic milliliters
*/ 
public static double cylinder_volume(double height, double diameter)
{  . . . 
}

To use this function to measure the volume of a can of Blatz Lite, just supply parameters and get its return.

double v = cylinder_volume(2 * can_depth, 1.5 * can_diameter);

Notice that you can use the function without knowing what's inside. Actually, this function is quite simple

public static double cylinder_volume(double height, double diameter)
{  double volume = Math.PI * diameter * diameter * height / 4; 
   return volume;
}

Suppose, instead of milliliters, an answer in fluid ounces is needed, say for a recipe. Use the following functions, together with cylinder_volume to implement a US measurement version public static double cylinder_volume_oz(double height, double diameter) like this:

Cylindric volume function chart
import ccj.*;

public class Cylinder
{  /**
    * convert inches to millimeters
    * Note: 1 inch = 25.4 millimeters
    * @param inches value in inches to convert to millimeters
    * @return the converted value
    */
   public static double inch_to_mm(double inches)
   {  return inches *  MM_PER_INCH;
   }

   /**
    * convert cubic millimeters to U.S. ounces
    * Note: 1 fluid U.S. ounce = 29.586 milliliters
    * @param mm3 - volume in cubic millimeters
    * @return volume in ounces
    */
   public static double mm_cubed_to_oz(double mm3)
   {  return value_to_convert / MM_CUBED_PER_OZ;
   }

   /**
    * computes the volume of a cylinder
    * @param height the height in millimeters
    * @param diameter the diameter in millimeters
    * @return volume - in cubic milliliters
    */ 
   public static double cylinder_volume(double height, double diameter)
   {  double volume = Math.PI * diameter * diameter * height / 4; 
      return volume;
   }
   
   /**
    * computes the volume of a cylinder
    * @param height the height in inches
    * @param diameter the diameter in inches
    * @return volume - in fl. oz.
   */ 
   public static double cylinder_volume_oz(double height, double diameter)
   {

   }

   public static void main(String[] args)
   {  System.out.println("Please enter the height (in inches)");
      double height = Console.in.readDouble();    
      System.out.println("Please enter the diameter (in inches)");
      double diameter = Console.in.readDouble();
    
      double volume = cylinder_volume_oz(height, diameter);
      System.out.println("The volume is " + volume + " ounces");
   }

   public static final double MM_PER_INCH = 25.4;
   public static final double MM_CUBED_PER_OZ = 29.586;
}

P1. Writing Functions

Productivity Hint 5.1 in the text suggests several enhancements to the future value function. Here is the function from the text.

import ccj.*;

public class Futval
{  /**
    * computes the value of an investment with compound interest, compounded monthly
    * @param initial_balance the initial value of the investment
    * @param p the interest rate as a percent
    * @param nyear the number of years the investment is held
    * @return the balance after nyears years
    */
   public static double futureValue(double initial_balance, double p, int nyear)
   {  double b = initial_balance * Math.pow(1 + p / (12 * 100), 12 * nyear);
      return b;
   }

   public static void main(String[] args)
   {   System.out.print("Please enter the initial investment: ");
       double initial_balance = Console.in.readDouble();
       System.out.print("Please enter the interest rate in percent: ";
       double rate = Console.in.readDouble();
       System.out.print("Please enter the number of years: ";
       double nyears = Console.in.readInt();
       double balance = futureValue(initial_balance, rate, nyears);
       System.out.println("After " + nyears + ", the initial investment of " 
          + initial_balance + " grows to " + balance);
   }
}

Change the futureValue function to compute the value of the investment when there are npayments regular interest payments per year (instead of 12 monthly payments). Supply a main function that calls your changed function.

What is the value of a $2000 investment after 6 years at 12 percent if interest is compounded quarterly?


Change the futureValue function to accept holding an investment for a fractional number of years, fyears. Supply a main function that calls your changed function.

What is the value of a $2000 investment after 6.5 years at 12 percent if interest is compounded weekly (52 weeks/year)?


R2. Function Names

The following code can be used to test a text mode function's interface. It's called a test harness, because any function can be used in it, simply by replacing foo() with the new function call and the desired parameters and returns.

import ccj.*;

public class TestHarness
{
   /** 
    * Test harness for calls to console programs
    * Used to test parameter and return passing.
    * @param n programmer specified parameter
    * @return programmer specified return value 
    */
   public static double foo(int n)
   {  System.out.println("Got to " + "foo" + " with parameter " + n);
      return n * 0.1; /* to simulate function activity */
   }

   public static void main()
   {  int give = 3; /* any test value can be entered here */
      double get = foo(give);
      System.out.println("Returned from " + "foo" + " with " + get);
   }
}

Why are the following functions badly named? Try using them in the test harness. What happened? What names would be better?


R3. Function Comments

Sooner or later, you'll encounter cryptically named and uncommented work like the following.

public static bool cn(Employee e1, Employee e2)
{  return e1.getName() == e2.getName();
}

public static bool cs(Employee e1, Employee f)
{  return e1.getSalary() == f.getSalary();
}

public static bool c4dup(Employee joe, Employee mary)
{  return  cn(joe, mary) && cs(joe, mary);
}

public static void main(String[] args)
{  Employee john;   
   Employee jane;
   . . .
   if(c4dup(john, jane))
      System.out.println("Same");
   else
      System.out.println("Different");
}

What do these functions do ?

Rewrite them with comments and more descriptive function and parameter names. Use:

/**
 * purpose
 * @param
 * @return
 */ 
 


P2. Return Values

In functions with more complicated branching of control, one way to insure a reasonable return value is to gather together all the possibilities and issue only one return statement from the very end of the block statement. Rewrite the pointsOfCompass function as follows:

  1. Introduce an additional variable string directionString
  2. Be more clever about the logic--first compute the major direction (north, east, south, west), then append an east or west if necessary
  3. Return the directionString from the end of the function only
import ccj.*;

public class Compass
{  /**
    * Convert a numeric compass position to it's verbal equivalent
    * @param degrees the compass needle angle in degrees
    * @return the value as a compass direction ("N", "NE", ...) 
    */ 
   String pointsOfCompass(int degrees)
   {  double octant = (degrees % 360) / 45.0 - 0.5;
      
      if (octant >= 7)
         return "North West";
      else if (octant >= 6)
         return "West";
      else if (octant >= 5)
         return "South West";
      else if (octant >= 4)
         return "South";
      else if (octant >= 3)
         return "South East";
      else if (octant >= 2)
         return "East";
      else if (octant >= 1)
         return "North East";
      else if (octant >= 0)
         return "North";
      else
         return "North West";
   }

   public static void main(String[] args)
   {  System.out.print("Please enter the compass heading (in degrees): ");
      int degrees = Console.in.readInt();

      String direction = pointsOfCompass(degrees);
      System.out.println("You are heading " + direction);
   }
}


R4. Parameters

Consider the following functions:

   public static String replace(String s, String a, String b)
   public static double canVolume(double h, double r)

and these variables:

   String greeting = Hello!"
   int a;
   int b;

What is wrong with each of the following function calls ?


R5. Procedures that Modify Parameters

In Java, a function cannot modify contents of any variables that are passed into it. For example, consider this call to the procedure f:

Employee harry = . . .;
int n = . . .;
. . .
f(harry, n)

After the call, harry still refers to the same employee object as before the call, and n still contains the same number as before the call.

The procedure f can modify the state of the employee object, for example, raise it's salary. But it can't replace the object with a different one.

The procedure cannot modify the value of n at all.

  1. In Java, it is not possible to write a procedure increment in Java that achieves the following. Explain.
    int a = 4;
    int b = 2;
    increment(a, b);
    /* now a is 6, b is 2 */
  2. Can you solve this problem by writing a function instead? How?
  3. Is it possible to write a procedure increment in Java that achieves the following? Explain.
    Employee a = new Employee("Aaronsen, Adam", 34000);
    int b = 1000;
    increment(a, b);
    /* now a's salary is 35000, b is 1000 */
  4. Is it possible to write a procedure swap in Java that achieves the following? Explain.
    Employee a = new Employee("Aaronsen, Adam", 34000);
    Employee b = new Employee("Birnbaum, Bruce");
    swap(a, b);
    /* now a is Bruce, b is Adam */

R6. Variable scoping

Generally, we want to encourage you to define a variable when you first need it, but you have to pay attention to the scope. Find what's wrong with this function's variable scoping, then fix it.

/**
 * Select the maximum of three integer values
 * @param i an integer
 * @param j an integer
 * @param k an integer
 */ 
public static int maximum(int i, int j, int k)
{  if (i > j)
   {  int a;
      a = i;
   }              
   else
   {   a = j;
   }
         
   if (k > a)
   {   return k;
   }
   else
   {   return a;
   }
}


P3. Eliminating Global Variables

Global (or static) variables may "work", but the advantages they offer are outweighed by the confusion they can cause. Since all functions can set a global variable, it is often difficult to find the guilty party if the global variable is set to the wrong value.

import ccj.*;

public class Max
{  /**
    * Updates maximum if parameter is larger 
    * @param  a the value to compare against maximum
    * @remark Uses static int maximum  
    */

   public static void set_max(int a)
   {  if (maximum < a) 
      {   maximum = a;
      }
   }

   public static int max3(int i, int j, int k)
   {  maximum = i;
      set_max(j);
      set_max(k);
      return maximum;
   }

   public static void main(String[] args)
   {   System.out.print("Please enter the first integer: ");
       int i = Console.in.readInt();
       System.out.print("Please enter the second integer: ");
       int j = Console.in.readInt();
       System.out.print("Please enter the third integer: ");
       int k = Console.in.readInt();
       int maximum = max3(i, j, k);

       System.out.println("The maximum is " + maximum);
   }

   public static int maximum;
}

Re-write max3() to avoid the use of global variables, and to preserve the logic of the function.


P4. Stepwise refinement

A call-tree diagram can help organize a large number of related functions. Consider drawing a house like this one using the code library's graphics functions.

Cylindric volume function chart

It can be done by organizing calls like these:

drawHouse()
drawWindow()
drawFront()
drawRoof()
drawDoor()

into a call tree like this

drawHouse
    | 
    +---------- drawFront
    |               |
    |               +----------- drawWindow (3 times)
    |               |
    |               +----------- drawDoor
    |
    +---------- drawRoof

Write a program that implements these functions to draw a house. (Hint: draw_window needs a parameter to specify the location of the window.)


P5. Walkthroughs

Modify the intName function of the text book to print numbers in German (or, if you prefer, in another language). Here is how German numbers are formed.

The digit names are as follows:

1 eins
2 zwei
3 drei
4 view
5 fuenf
6 sechs
7 sieben
8 acht
9 neun

The tens are named as follows:

10 zehn
20 zwanzig
30 dreissig
40 vierzig
50 fuenfzig
60 sechzig
70 siebzig
80 achtzig
90 neunzig

As in English, the names of the numbers between 11 and 19 are special.

11 elf
12 zwoelf
13 dreizehn

You can obtain 14 ... 19 from 40 ... 90 by replacing "zig" with "zehn".

14 vierzehn
15 fuenfzehn
16 sechzehn
17 siebzehn
18 achtzehn
19 neunzehn

You also need to know

100 hundert
1000 tausend

So far, this is just as in English, just with different names. However, there is one crazy complication. When forming a number between 21 and 99, the ones come before the tens. For example

23 dreiundzwanzig

is "three and twenty". That is, you first convert the ones, then append the word "und", then convert the tens. This being German, you do not add any spaces at all. For example,

23456 dreiundzwanzigtausendvierhundertsechsundfuenfzig

Write the intName function (and the auxiliary functions that it calls) to produce German number names.

Perform a walkthrough of the German intName with n = 416


P6. Recursion

Consider a function int digits(int) which finds the number of digits needed to represent an integer. For example, digits(125) is 3 because 125 has three digits (1, 2, and 5). The algorithm is defined as:

if n < 10, then digits(n) equals 1. Else, digits(n) equals digits(n / 10) + 1.

(Why? If n is less than 10, one digit is required. Otherwise, n requires one more digit than n/10.)

For example, if called as int num_digits = digits(1457), the following trace results:

digits(1457)
= digits(145) + 1
= digits(14) + 1 + 1
= digits(1) + 1 + 1 + 1
= 1 + 1 + 1 + 1

Do a trace of digits(32767)

Write int digits(int n) to be called by the following main():

public static void main(String[] args)
{  System.out.print("Please enter a number: ");
   int testValue = Console.in.readInt();
     
   int ndigits = digits(testValue);   
   System.out.println("You need " + ndigits + " digits to represent " + testValue 
      + " in decimal");
}

Don't forget to send your answers when you're finished.