Spring Semester 2003


Lecture Notes Sixteen: Milestones. The reading assignment for this is: Big Java - Chapter Seven.
We've reached an important milestone now. Although you may not realize it now,

... it will become apparent in about a week. Meanwhile I think it's reasonably safe to say that we're halfway through now.

I think so. Diagrams like the ones presented in the previous sets of lecture notes are important. They can help you understand what's happening inside a program when it's compiled and run.

You should not expect to use diagrams all the time, always, for each and every program. Their purpose is mostly to help you understand the concepts, the basics, by providing a very detailed picture, as if under a microscope.

Once you understand those, you're all set. And you can think Java without drawing diagrams. You'd be manipulating them in your mind, almost without knowing it.

Today in class we'll touch on lecture notes of Wednesday and then start chapter 7 (methods). We'll finish chapter 7 quickly, although there will be one important new thing we will touch on.

Recursion. The saying goes that "to understand recursion you first need to understand recursion".

That's just a humorous saying. Recursion, in fact, is easy, and thoroughly useful.

Once you have a fixed point. (But we'll get to that shortly.)

Not to mention that we have already seen it. Yes, it was that method in lab five.

That method, what's its name? Well, what was its name, but we changed it.

That we did. Now let's start chapter 7.

Methods. What about them?

All about them. You have already implemented several simple methods and are familiar with the basic concepts.

Let's go over parameters, return values, and variable scope in a more systematic fashion. We will also review some of the more technical issues, such as static methods and variables.

When we implement a method we define the parameters of the method.
Here's an example:
public class BankAccount {
  ...
  public void deposit(double amount) {
    ...
  }
  .. 
}

The deposit method has two parameters: one is explicit, called amount, and has type double. We expect to receive a value in it to be deposited in the account.

The other one is implicit, and can be referred to by the the keyword this. What we mean by that is that inside an instance method (like deposit) the keyword this will always refer to the host object.

So if it's always available and always in the same way, the this reference is not even mentioned in the list of parameters. But from any instance method we can use the keyword this to refer to the object that contains the method, always.

Therefore amount is called a formal parameter for the method deposit. When we want to deposit some money we need to know two things:

a) the account's name, and b) the amount of money

... and we need to actually invoke the method, ... in order to deposit the money.
// somewhere in main (or another method) 

myChecking.deposit(allowance - 200); 

In this example myChecking is an object of type BankAccount... ... and allowance is probably a double.

Both (are names, and the names) should be declared and initialized before we use them. When deposit starts its work, the value of the
allowance - 200
expression becomes the
  • actual parameter or
  • argument
to the method...

... and will be known by the name of amount ... while deposit is running (working on it).

When the method returns (or ends) the formal parameter variables are abandoned (they're disposable) and their values are lost. The entire process is like a phone call: you call myChecking's deposit method and you give it some input: the amount that you want to deposit.

Once it has that it immediately starts working for you and you stay on the line, waiting for it to tell you: I am done, and your transaction is completed. You can't write your checks before that.

When it's done it says so, before you hang up. Sometimes a method also returns a value before the end of conversation, but not deposit.

deposit only says when it's done, without returning anything. It is declared as void. void is its return type.

Yes: it does not return anything to its caller. Hence: void.

It only does what it is supposed to do, and then it says: "I'm done." And balance has changed. One could also call this returning except it's not as in "returning a value", but rather...

... more like in "returning from a trip". A trip to the bank.

Explicit parameter variables (the formals) are no different from other variables. You can modify them during the execution of a method: but unless you have a good reason for that, that is considered bad style.

Let's now consider a more complicated example:
public class BankAccount {
  ...
  public void transfer(BankAccount other, double amount) { 
    withdraw(amount); 
    other.deposit(amount); 
  } 
  ... 
} 
This method can be used to transfer money from one account to another.

Here's how we can use it:
momsSavings.transfer(myChecking, allowance);
I have one question before we go further though...

Yes, what is it? Weren't we supposed to write
this.withdraw(amount)
in the definition of the transfer method?

Yes, since we decided to always put an object reference in front of any instance variable name, so please make the correction in your notes. OK, I've updated mine.

But why does it work though? Because it defaults to it, anyway.

But I think that using this makes the code more uniform and explicit. And I think so too.

How many formal parameters does this new function (method) have? Two of them are explicit: other and amount.

The first one is a BankAccount. The second one is a double.

And in addition to that the method will be able to access the object to which it belongs using this. Yes, this is always available in an instance method and means: "the object that contains this method", or "this method's host".

What happens when the method is invoked? When the method is invoked the reference to myChecking is copied into the method's other formal parameter...

... and allowance will be copied into amount. Note that both the object references and the numbers are copied into the method.

After the method exits, the two bank account balances have changed. The method was able to change the accounts because it received copies of the object references.

Of course, the contents of the allowance variable was not changed. In Java no method can modify the contents of a number variable that is passed as a parameter.

Or the contents of any other variable of primitive type, for that matter. Yes: parameters are always passed by value.

What names should we give to the parameters? You can give them any names you want.

As a rule, choose explicit names for parameters that have specific roles. Choose simple names for those that are completely generic. Your goal is to make the reader understand the purpose of the parameter without having to read the method's description.

The compiler takes the types of the method parameters and return values very seriously. Very very very seriously.

It is an error to call a method by passing it a value of incompatible type, ... or to use the method in a context that is not compatible with its return type (if any).

Java is a strongly typed language. That, it is.

The compiler automatically converts from int to double and from ordinary classes to superclasses (as we will see chapter 9). But it does not convert when there is a possibility of information loss, as we have seen when we discussed casting,

... and does not convert between numbers and strings and objects. This is a useful feature, because it lets the compiler find programming errors before they create havoc when the program runs.

A method that accesses an object and returns some information about it, without changing the object, is called an accessor method. Such as getBalance.

In contrast, a method that modifies the state of an object is called a mutator method. deposit and withdraw are mutator methods.

You can call an accessor method ... as many times as you like.

If that's all you do, you will always get the same answer, and it does not change the state of the object. Some classes have been designed such that objects of that kind have only accessor methods and no mutators at all.

Such classes are called immutable. An example is the String class.

Once a String has been constructed, its contents never change. For example, the substring method does not remove characters from the original string.

Instead it constructs a new string that contains the substring characters. Here's another example of an accessor method that simultaneously looks at two objects:
public class BankAccount {
  public boolean equals(BankAccount other) {
    return (this.getBalance() == other.getBalance()); 
  }
} 

It makes use of two other accessors (or, rather, the same accessor invoked on two different objects) and compares the values that they return, to come up with an answer. And the answer that it returns is the truth value that comes out of (and describes) the comparison.

So we could use it as follows: Very good.
if (account1.equals(account2)) {
  // they have the same balance... 
} else {
  // they do not have the same balance... 
} 

In general the expectation is that accessor methods do not modify any parameters, ... ... and that mutator methods do not modify any parameters beyond this.

This ideal situation is not always the case. Like the transfer method discussed before.

It changed its this, ... ... while also updating the other account.

Such a method is said to have a side effect. A side effect of a method is any kind of observable behaviour outside the object.

In an ideal world, all methods would be accessors. They would simply return an answer without changing any value at all.

In fact, programs that are written in so-called functional programming languages, such as Scheme or ML, come close to this ideal. Scheme is the best! Java is also good.

In an object oriented programming language, we use objects to remember state changes. Therefore, a method that just changes the state of its implicit parameter is certainly acceptable.

A method that does anything else is said to have a side effect. While side-effects cannot be completely eliminated, they can be the cause of surprises and problems and should be minimized.

Sometimes you write methods that don't belong to any particular object. Such a method is called a static (or class) method and needs to be declared as static.

In contrast, methods such as getBalance, withdraw, and deposit in the preceding sections are often called instance methods, ... because they operate on particular instances of an object. There's one getBalance for each BankAccount (object) that gets created.

Have we seen any static methods? Math.sqrt is a static method.

And every application must have a static method where processing begins, called ... main.

Correct. Here's another example, that involves only numbers:
class NumericMethods {
  public static boolean approxEqual (double x, double y) {
    final double EPSILON = 1E-14; 
    double xymax = Math.max(Math.abs(x), Math.abs(y)); 
    return Math.abs(x - y) <= EPSILON * xymax;
  }
  // more numeric methods could come here... 
} 
This method encapsulates computation that involves no objects at all, only numbers (and booleans), hence only primitive types.

To call (or use) a static method you need to supply the name of the class, for example:
double r = Math.sqrt(2); 
if (NumericMethods.approxEqual(r * r, 2)) 
  System.out.println("Math.sqrt(2) is approx. 2"); 
... same as we do with Math.sqrt.

Now it should be clear to you why the main method is a static method. ... when the program starts there may not be any objects at all.

Therefore the first method to be called in a program must be a static method. Good enough.

To summarize our knowledge about static methods we can say that... ... a static method is a method that does not belong to any object, and that has only explicit parameters. (No this!)

Let's look at some examples now. What does the following example illustrate?
public class Example {
  public static void addOneToIt (int number) {
    System.out.println(number); 
    number = number + 1; 
    System.out.println(number); 
  } 
  public static void main(String[] args) {
    int value = 3; 
    System.out.println(value); 
    Example.addOneToIt(value); 
    System.out.println(value); 
  } 
} 
Let's walk through the method call. When the call is made the parameter value is set to the same value as the argument.

The value is copied. Changes to it are not seen outside.

That's all there is to it. Easy.

Is there a moral to it? In Java method parameters are copied into the parameter variables when a method starts.

Computer scientists call this call mechanism "call by value", and we mentioned it in lab 2. As you have just seen there are some limitations to the "call by value" mechanism.

It is not possible to implement methods that modify the contents of number variables. Other programming languages support an alternate mechanism, called "by reference".

This involves passing only the address to where the number variable is stored. This is what happens when you pass an object as an actual parameter.

Let's see an example. Oh, boy. I like examples best.
class NumberHolder {
  int value = 1; 
} 
class Example {
  public static void main(String[] args) {
    NumberHolder n = new NumberHolder();
    System.out.println(n.value); 
    Example.addOneToIt(n); 
    System.out.println(n.value); 
  } 
  public static void addOneToIt (NumberHolder n) {
    n.value = n.value + 1; 
  } 
} 
But we've seen this before, haven't we?

Yes, when we discussed copying of variables. Primitive types are copied by value, while reference types are copied by reference.

Good enough. References though are still passed by value.

Understood. Can we see an example? Oh boy -- that's what I like best.
class Pair {
  double x; 
  double y; 

  Pair(double x, double y) {
    this.x = x; 
    this.y = y; 
  }   

  void report() { 
    System.out.println("Hello! I'm at: (" + x + ", " + y + ")"); 
  }
} 

class Testing {
  public static void main(String[] args) {
    Pair a = new Pair(100, 0); 
    Pair b = new Pair(0, 100); 
    a.report(); 
    b.report(); 
    Testing.swap(a, b); 
    a.report(); 
    b.report(); 
  } 

  static void swap(Pair a, Pair b) {
    Pair temp = a; 
    a = b;
    b = temp; 
  }
} 

I like it. Easy and understandable. But it still gives you a level of indirection.

Yes. You can, at least in principle, get inside those Pairs. Let's summarize: a Java method can update an object's state using the reference to it, but it cannot change the contents of a reference any more it can change a variable of primitive type.

This shows that object references are passed by value in Java, although we can safely say that ... objects themselves are passed by reference.

Except that the reference itself is copied. Copied, yes -- but pointing to the same thing that the original one was.

Fair enough. The distinction is clear now.

A method that has a return type other than void must return a value, by executing a statement of the form:
return <expression> ;
Been there, done that.

Yes, but let's see if we can come up with something new. Well, for one thing, you can return the value of any expression.

You don't need to store the result in a variable and then return the variable. When a return is processed, the method exits immediately.

This is convenient for handling exceptional cases in the beginning.
Oh, yes, here's an example:
public static int fibo (int n) {
  if (n == 1) 
    return 1; 
  else if (n == 2) 
    return 1; 
  else {
    int fOlder = 1; 
    int fOld = 1; 
    int result = fOld + fOlder; 
    for (int i = 3; i <= n; i++) {
      result = fOld + fOlder; 
      fOlder = fOld; 
      fOld = result; 
    } 
    return result; 
  } 
} 
These are Fibonacci numbers!
(http://www-groups.dcs.st-andrews.ac.uk/~history/Mathematicians/Fibonacci.html)

Or rather, the method that computes them. Picky, picky, picky! What can I say.

Can you give me an example? Sure, how about add, below.
class Fraction {
  int num;
  int den; 

  Fraction(int a, int b) { 
    this.num = a; 
    this.den = b;    
  }

  public String toString() {
    return " (" + num + "/" + den + ") "; 
  }

  Fraction add(Fraction other) {
    return new Fraction(this.num * other.den + this.den * other.num, 
                        this.den * other.den);
  }

  public static void main(String[] args) {

    Fraction a = new Fraction(1, 3);
    Fraction b = new Fraction(2, 3); 

    System.out.println(a.toString()); 

    System.out.println(b); 

    System.out.println(a.add(b));

  }

}
That's a good example, and so is... ... toString. But I like add better.

It is important that every branch of a method return a value, that is, a method cannot end without returning a value (if its return type is other than void). Also, a method whose return type is not void always needs to return a value. Oh, you just said that! Nevermind, although reinforcement is good.

If the method contains several if/else branches make sure that each one of the branches returns an adequate value. At the end of every possible path through a non-void method there should be a return statement, returning the value of an expression of compatible type.

For example is this right? It is not.
public static int fibo (int n) {
  if (n <= 0) 
    System.out.println("Incorrect argument!");
  else if (n == 1) 
    return 1; 
  else if (n == 2) 
    return 1; 
  else {
    int fOlder = 1; 
    int fOld = 1; 
    int result = fOld + fOlder; 
    for (int i = 3; i <= n; i++) {
      result = fOld + fOlder; 
      fOlder = fOld; 
      fOld = result; 
    } 
    return result; 
  } 
} 
It is not, because if the argument is negative we don't return anything. What should we return, then?

I don't know, what do you think of this one?
return Math.round( (Math.pow((1 + Math.sqrt(5))/ 2, n) - 
                    Math.pow((1 - Math.sqrt(5))/ 2, n) ) / Math.sqrt(5) );
Ha, that was a good one!

Or we should throw an Exception. Yes, but about those perhaps some other time...

We have now encountered the four kinds of variables that Java supports.
  1. Instance variables
  2. Static variables
  3. Local variables
  4. Parameter variables

The lifetime of a variable defines when the variable is created and how long it stays around. When an object is constructed, all its instance variable are created.

As long as the object is around its instance variables will also be there, inside the object. A static variable is created when its class is first loaded, and it lives as long as the class.

A local variable is created when the program enters the statement that defines it. It stays alive until the block that encloses the variable definition is exited.

Here's an example:
public void withdraw (double amount) {
  if (amount <= balance) {
    double newBalance = balance - amount; 
    // local variable newBalance created and initialized
    balance = newBalance;
  } // end of lifetime of local variable newBalance 
} 
If you tried to print newBalance right before the end of the method you'd get an error.

Yes, and the reason is: it's known only in the then branch of the if statement. Inside the inner pair of curly braces.

Can you say that again? Inside the inner pair of curly braces, only.

Very good. Good to remember.

Finally, when a method is called, its parameter variables are created. They stay alive until the method returns to the caller. They're disposable. Every time a new set is used. Fresh. New scratch paper, as in what.

Next, let us summarize what we know about the initialization of these four types of variables. Instance variables and static variables are automatically initialized with a default value...

... which is
  • 0 for numbers (and chars),
  • false for boolean and
  • null for objects (ref. types),
Yes. So instance variables and static variables are automatically initialized with a default value unless you specify another initial value.

So constructors are not essential. They're hygienic instead: convenient and clean.

Parameter variables are initialized with copies of the actual parameters. That's when the method gets called.

Local variables are not initialized by default. For local variables you must supply an initial value, and the compiler complains if you try to use a local variable that you never initialized.

The scope of a variable is that part of a program that can access it. The part of the program in which you can access it, the variable, is the scope of the variable, yes.

OK. As you know, instance and static variables are usually declared as private, and you can access them only in the methods of their class. I see... Scope answers the question: can I see it?

The scope of a local variable extends from the point of its definition to the end of the enclosing block. The scope of a parameter variable is the entire body of its method.

Now let's look a bit closer to a few situations. We're going to go through a few examples.

It sometimes happen that the same variable name is used in two methods:
public static double area(Rectangle rect) {
  double r = rect.getWidth() * rect.getHeight();
  return r; 
} 

public static void main(String[] args) {
  Rectangle r = new Rectangle(5, 10, 20, 30); 
  double a = area(r); 
  ... 
} 
These variables (the two r's) are independent of each other.

You can have variables with the same name r in different methods, ... just as you can have different motels with the same name (let's say, "Super 8") in different cities.

In this situation the scopes of the two variables are disjoint. Problems arise, however, if you have two or more variable names with overlapping scope.

Like when you have two Kroger's in the same city. Almost, but not exactly. In Java this situation is called shadowing.

There are rules in the language that tell you which one of the variables you will be referring to if you use the ambiguous name. Can we see some examples?

Certainly.
class Employee {
  String name;
  Employee (String name) {
    this.name = name; 
    // this is mandatory not just good style here!!
  } 
} 
The parameter, which is like a local variable, shadows the instance variable.

The Java language specifies that when there is a conflict between a local variable name and an instance variable name the local variable wins out. This sounds pretty arbitrary but there is actually a good reason.

You can still refer to the instance variable using this Which you should do anyway.

Do you have any questions? No, but I have something close to that.

An example! You bet.

Consider this:
class Puzzle {

  public static void main(String[] args) {
    Puzzle p = new Puzzle(); 
    System.out.println("Final result: " + p.fun(6)); 
  }   
         
  int fun(int n) { 
    int result; 
    if (n == 0) return 0; 
    else {
      // [1] 
      result = n + fun(n - 1); 
      // [2] 
      return result; 
    } 
  } 

} 

Neat. What do we do with it? Well, what's the program computing?

This is our old friend what. Yes, what's its name.

But notice the new name of the method. I know, I know, this is a lot of fun.

Well, isn't it? I could make it real fun, you know.

How. I could show you the real power of recursion.

I'd like to see that. Consider the Tower of Hanoi problem.

That's problem 7.14, page 310. Yes. Let me state it here briefly.
This is a neat little puzzle invented by the French mathematician Edouard Lucas.

(http://www-groups.dcs.st-andrews.ac.uk/~history/Mathematicians/Lucas.html)

We are given a tower of eight disks, initially stacked in decreasing size on one of the three pegs. The objective is to transfer the entire tower to one of the other pegs, moving only one disk at a time and never moving a larger one onto a smaller.


Let's solve the problem in general. Base case first: one disk is easy.

Now for all other cases by induction. Assume we can solve the problem for n-1 disks.

Then the general case becomes easy. Place the top n-1 disks on middle peg first.

Then move the largest disk. Then bring the n-1 disks back on top of it.

Can I see the program? Here it is:
frilled.cs.indiana.edu%cat Hanoi.java
class Hanoi {
    public static void main(String[] args) {
	int size = 4; // number of disks
	move(size, "source", "middle", "target"); 
    }
    static void move(int height, String peg1, // from 
     		                 String peg2, // using 
    		                 String peg3  // to 
                    ) {
	if (height == 1) {
	    System.out.println("Move disk from " + peg1 + " to " + peg3); 
	} else {
	    move(height-1, peg1, peg3, peg2); 
	    System.out.println("Move disk from " + peg1 + " to " + peg3); 
	    move(height-1, peg2, peg1, peg3); 
	}
    }
}
frilled.cs.indiana.edu%javac Hanoi.java
frilled.cs.indiana.edu%java Hanoi
Move disk from source to middle
Move disk from source to target
Move disk from middle to target
Move disk from source to middle
Move disk from target to source
Move disk from target to middle
Move disk from source to middle
Move disk from source to target
Move disk from middle to target
Move disk from middle to source
Move disk from target to source
Move disk from middle to target
Move disk from source to middle
Move disk from source to target
Move disk from middle to target
frilled.cs.indiana.edu%
Now that was a lot of fun! I sure think so.

Last updated: Mar 3, 2003 by Adrian German for A201