|
Second Summer 2002
|
Lecture Notes Sixteen: Milestones.
The reading assignment for this is 270-291, and 298-301.
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.
|
|
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 boolean s), 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 Pair s.
|
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> ;
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;
}
}
(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) );
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.
|
- Instance variables
- Static variables
- Local variables
- 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.
|
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 char s),
-
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?
|
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.
|
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: Jun 16, 2002 by Adrian German for A201