CSCI A201/A597

Lecture Notes 29

Spring 2000


Methods, objects, classes. An introduction to A202.

If we have a mathematical function f from the set of positive integers N to the set of positive integers N and with the following definition:

f(x) = 3x + 1 
then we can write it in Java as follows:
int f(int n) {
  return 3x + 1; 
} 
The domain and codomain of the function as well as the number of arguments are specified in this definition. A return statement is used to pass back the result.

Functions are encapsulating computations. In Java functions are called methods. Let's suppose we now want to implement a (still simple) but more complicated function, that computes the sum of the first n positive numbers.

We can do it in several ways:

a) iterative approach

int sum (int n) {
  int sum; 
  for (int i = 0; i <= n; i++) {
    sum += 1; 
  } 
  return sum; 
} 
b) closed form formula approach

int sum(int n) {
  return n * (n + 1) / 2; 
} 
c) recursive approach
int sum(int n) {
  if (n == 1) return 1; 
  else return n + sum(n - 1); 
} 
The iterative approach is very constructive. It uses a local variable, sum.

The closed form is OK as long as we can prove that the formula we use does indeed compute the sum of the first n positive integers. To prove that the formula is correct we can use mathematical induction. To come up with a formula it's a different story however.

Exercise: write a function that computes the sum of the squares of the first n positive integers. Write another that computes the sum of the cubes of the first n integer numbers.

The recursive approach is an elegant approach.

Functions can also be mutually recursive, for example consider this definition of when a given positive integer number n is odd or even:

boolean odd(int n) {
  if (n == 0) return false; 
  else return even(n - 1); 
} 

boolean even(int n) {
  if (n == 0) return true; 
  else return odd(n - 1); 
}
We notice that the codomain of the function is boolean.

In Java there are eight primitive types:

  1. boolean

  2. byte
  3. short
  4. int
  5. long
  6. char

  7. float
  8. double
Java has primitive types and reference types. We will talk about reference types in a minute.

Now we know how to write functions, but where do we put them. Functions can't exist on their own in Java. They must be located either inside objects or classes. We'll first put them in an object. But for this we need to create an object, so we need to talk about objects and classes.

We create a type of object Abacus and put the methods in it.

class Abacus {
  int sum(int n) {
    if (n == 1) return 1; 
    else return n + sum(n - 1); 
  } 
  boolean odd(int n) { 
    ... 
  }
  boolean (even int n) {
    ...
  } 
}
Classes and objects: Example:
class Point {
  double x; 
  double y; 
} 
We can draw a picture of a point of type Point.

Objects are being created with new.

An invocation of new activates a constructor.

Every class gets a default no-args constructor by default if no constructor is specified. If at least one constructor is specified then there is no default constructor whatsoever.

So to create a Point object with the current definition:

new Point(); 
should appear somewhere in the body of a method.

Reference types

To store a reference to a newly created Point one needs to use a variable of type Point.

Point p = new Point(); 
We could draw a picture of that.

The instance variables x, and y are initialized to 0.0 by default. That's different from the local variables used by methods.

If we add a new constructor to class Point like that

class Point { 
  double x, y;
  Point (double initialX, double initialY) {
    x = initialX;
    y = initialY; 
  } 
  // no default no-args constructor now! 
} 
we would not be able to create a point like we did before
Point p = new Point(); // this doesn't compile now. 
but we could create one in which we specify the initial values of the coordinates:
Point p = new Point(-1.0, -3.5); 
To have the no-arg constructor we need to specify it explicitly, when other constructors have been defined:
class Point { 
  double x, y;
  Point (double initialX, double initialY) {
    x = initialX;
    y = initialY; 
  } 
  // explicit no-args constructor 
  Point() {
    x = 0.0; 
    y = 0.0; 
  } 
} 
Access of members

Data members (fields) are accessed like this:

objectReference.fieldName
while methods are accessed as follows:
objectReference.methodName()
We're are getting closer to explaining the components that will help us write a full-blown program and run it, to test all these things.

Java programs are organized as sets of packages. To start with we will be working with the default package, essentially "the current directory". The default visibility for instance variables is public within the current package.

We look at the following examples:

Point p = new Point(); 
p.x = 3.0; 
p.y = 5.0; 
and draw a diagram for it, and then modify the diagram to include
p = new Point(); 
p.x = -1.0;
p.y = -2.0; 
So what we have been written so far are snippets of code that should appear somewhere in a method.

Instance members are of two kinds: (data) fields, and methods (that encapsulate behaviour).

Let's give an example of an instance method for objects of type Point():

double squareOfDistanceToOrigin() {
  return x * x + y * y; 
} 
Let's define a more general one:
double squareOfDistanceToPoint(Point p) {
  return (x - p.x) * (x - p.x) + (y - p.y) * (y - p.y); 
} 
Note: we could have written the above as
double squareOfDistanceToPoint(Point p) {
  return this.x * p.x + this.y * p.y; 
} 
this is a reference to the object to which the method belongs to. It's a reference like p. We'll come back to this later.

So we have discussed about instance variables (fields) and about instance methods. We now need the bigger picture so we have the following classification:

Members are:

Let's add a static variable and a static method to class Point:
class Point {
  ...
  static int howMany; 
  static void count() {
    howMany += 1; 
  } 
  ...
}
Just like for instance members we would access the static method as follows
Point.count(); 
and the howMany variable
Point howMany(); 
We mention the convention that classes names are capitalized.

We could use count() in the constructors and keep track of how many Points have been generated in our program. The main point though is to remember that access of class members has a similar syntax to those of instance members.

To write a full-blown Java program you need at least a class. To run it you need at least one of your classes to implement a method called main with the following look:

class One { 
  public static void main(String[] args) {
    ...
  } 
} 
Note: the name of a function together with the number and types of the arguments are called the signature of that function. Let's now make a few more observations on static members before we move ahead to writing our first full-blown Java program. (Once that is done we could start writing programs to test all the concepts and examples discussed in this lecture).

In class Point the following definition

class Point {
  ...
  static Point origin = new Point(); 
  ...
}
defines a class member, an object of type Point.

We could define some more:

class Point {
  ...
  static Point bearsPlace = new Point(-2.0, -1.5); 
  static Point lindleyHall = new Point(-1.0, 10.5); 
  ...
}
Exercises

1. Assume the following lines of code:

Point p = new Point(3.2, 5.1); 
double val = Point.origin.squareDistanceToPoint(p); 
What's the contents of val at the end of the code.

2. Same question for

Point q = new Point(3.2, 5.1); 
double val = Point.bearsPlace.squareDistanceToPoint(lindleyHall); 
Now let's look closer at this line:
Point.origin.squareDistanceToPoint(p)
and compare it with this line
System.out.println("Hello, world!")
The lines are almost identical, in a sense, except that you may have seen the second one much more often than the first. The structure of the lines is as follows:
  1. first a class name is mentioned (System, Point)
  2. then a static member of that class is accessed (out, origin)
  3. an instance method of the resulting object is invoked
    (println(...), squareDistanceToPoint(...))
Check the docs for java.lang.System for your reference.

(Note: the java.lang is imported by default).

So now we can write a full-blown Java program, compile it, and run it.

(We can also use Math.sqrt() in the instance methods of class Point that compute distances).

To illustrate a full-blown program let's compile and run this program:

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; 
    } 
  } 
} 
What's the program computing?

Exercises

  1. Add a line
    System.out.println(n); 
    
    in place of the first comment. Explain the change in output.

  2. Move the line
    System.out.println(n); 
    
    in place of the second comment, thus leaving the first comment unchanged.
    Explain the change in output.
We end with another exercise.

Draw a diagram for the objects of type Pair defined below.

 
class Pair {
  int value; 
  Pair nextPair; 

  Pair(int initialValue, Pair initialNext) {
    value = initialValue; 
    nextPair = initialNext; 
  } 
}
Define a class of list processor objects that can compute the length of a list implemented with Pair objects. Place the main method in the list processor (LisP) class and have LisP objects generate a random list at creation time, that they would keep as an instance variable.

class LisP {
  Pair myList; 
  // define your constructors

  public static void main(String[] args) {
    // define your main function here 
  }

  // define your method length here 
}

Last modified: Apr 25, 2000 for A201 by Adrian German