Lecture 5

In this lecture we will further explore static methods, and begin to look at classes and objects.

Static methods continued

Here is another class defining a static method:

public class Demo {
  public static double computeAreaOfCircle(double radius) {
    double area;
    area = Math.PI * Math.pow(radius, 2.0);
    return area;
  }
}

As you would expect, the method (defined inside a class called Demo) computes the area of the circle, given the circle's radius as a double value.  Try typing in this class in DrJava, compiling it, and then invoking the method in the interactions window:

> Demo.computeAreaOfCircle(10.0)
314.1592653589793

There are a couple new things going on in this method:

  1. We are using something called Math.PI, which looks like a variable (perhaps representing the mathematical constant pi?) in a class called Math.

  2. There is a call to a static method called Math.pow: that must be a method called pow in a class called Math.  Again, where is this class Math defined?  And, what does the pow method do?

The Java API Documentation

The answer to these questions can be found in the Java API Documentation.  This website describes every class and method provided as a standard part of Java.  Java comes with a lot of pre-built classes and methods that you can use in your programs: in the most recent version of Java (1.5), there are almost 3,000 classes and 16,000 methods available?  The API documentation helps you find out about these classes.

Here is a screenshot of the API Documentation website (reduced size, click to see the full image):

The site consists of three frames.  The frame in the upper left of the window lists the packages available: more about those in a moment.  The lower left frame lists the classes available.  The main frame (on the right) is where the actual documentation is displayed.  In particular, it is where the documentation for a particular class is displayed, once you select a class.

So, let's find the Math class.  A good place to start looking is in the java.lang package.  Find java.lang in the package frame (upper left).  The class frame (lower right) will now narrow its contents to show only classes that are defined in java.lang.  Scroll through the classes: you should see a class called Math.  Click on the class, and the main frame will display the documentation for the java.lang.Math class, which is the class we made use of in the computeAreaOfCircle method.

Take a look at the documentation for the java.lang.Math class.  Find the definitions of PI and powPI is a named constant defining a close approximation of the mathematical constant pipow is a static method that takes a double value and a double exponent, and raises the value to the exponent.  Java does not have an exponentiation operator, so the pow method is used instead.

Java Packages and Import Statements

Java class names can have a package component.  For example, as we saw above, the java.lang.Math class defines a number of mathematical constants and functions.  In this case, the package is java.lang, and Math is the name of the class.

Normally, if we want to use a class, we need to add an import statement naming the class we want to use.  For example, we might be writing a GUI program that needs to use the java.awt.Graphics class.  We would add the following import statement to the beginning of the source file:

import java.awt.Graphics;

The import statement lets the Java compiler know that when we refer to the class Graphics, we are referring to java.awt.Graphics.  You will generally need to add an import statement for each outside class that you will be using.

An exception to this rule is that for classes in the java.lang package, you do not need use an import statement.  The java.lang package contains the most important and commonly-used classes, such as the String class (whose full name is java.lang.String).

Void methods, Output, String Concatenation

A void method is one that does not return a value.  The (static) methods we have considered so far are like mathematical functions: they take one or more input values and return a single output value.  A void method is one that is executed only for its side effect.  A common example of a side-effect is output: for example, values that are passed to the System.out.println method to be printed to the screen.  Once printed, these values disappear from the program.

Here is an exmaple:

public class Demo {
  public static void greet(String name) {
    System.out.println("Hello, " + name);
  }
}

Let's break down exactly what this method means.

The void return type on the method means that the method does not return a value.

The call to the System.out.println method sends an output value to the screen (where it will be visible to the user running the program).

The expression "Hello, " + name is a string concatenation expression.  Normally, the + operator is used with numeric operands, and means addition.  When one or both of the operands to the + operator are String objects, the meaning is string concatenation.  First, if either operand is not a String, it is converted to a String.  For example, in the expression

"The answer to life, the universe, and everything is " + 42

the int value 42 is first converted to the String value "42" (i.e., the String consisting of the characters '4' and '2').  Once both operands in a string concatenation expression have been converted to Strings, the two Strings are pasted together.  In the case of the greet method above, the Strings "Hello, " and name are pasted together.  Let's try experimenting with the greet method in the interactions window:

DrJava colors values written to System.out green.  We can see that each call prints a String formed by combining the value of the String constant "Hello, " with the value of the String passed as a parameter to the method.

Static Variables

A static variable is a variable that belongs to the class as a whole, and is not associated with a particular object.  Let's consider a modified version of the Demo.greet method.

public class Demo {
  private static int greetingCount = 0;

  public static void greet(String name) {
    System.out.println("Hello, " + name);
    greetingCount = greetingCount + 1;
    System.out.println("Number of greetings so far: " + greetingCount);
  }
}

The variable greetingCount is a static variable.  Unlike variables that are defined inside a method, which disappear when the method returns, a static variable exists for the entire duration of the program.  Whatever value is stored in a static variable persists until it is overwritten with another value: whenever we inspect the value we see the most recent value written there.  We can see this phenomenon when we try calling this method in the interactions window:

Constants

Often, it is useful to define a static variable whose value never changes: a constant.  For example, we can change the Demo class so that the greeting is defined by a constant:

public class Demo {
  private static final String GREETING = "Hello, ";

  public static void greet(String name) {
    System.out.println(GREETING + name);
  }

  public static void complement(String name) {
    System.out.println(GREETING + name + ", that's a nice hat you're wearing");
  }
}

Note that we now have two static methods defined: greet and complement.  Each method uses the value of the constant GREETING.  Note that the constant is defined as final: this means that the value of the constant will never change, and it is illegal to assign a new value to this variable.  Let's try calling these methods in the interactions window:

The advantage of defining constants is that we can change the value of the constant in one place, and that value will be reflected everywhere in the program where the constant is used.  For example we can change the constant to give this class a more international flavor:

public class Demo {
  private static final String GREETING = "Bonjour, ";

  public static void greet(String name) {
    System.out.println(GREETING + name);
  }

  public static void complement(String name) {
    System.out.println(GREETING + name + ", that's a nice hat you're wearing");
  }
}

We can see this change reflected when we try calling the methods in the interactions window:

Classes and Objects

Thus far we have focused on static methods and variables.  Java is an object-oriented language, and to use it effectively you will need to know about how to define, create, and use objects.

Here is an example class we will use to create some objects.  (Recall that a class is a "template" for objects.)

public class Person {
  private String name;
  private int age;
  private String hobby;

  public Person(String name, int age, String hobby) {
    this.name = name;
    this.age = age;
    this.hobby = hobby;
  }
}

As you might imagine, the Person class represents a person and several characteristics pertaining to a particular person: name, age, and hobby.  Let's investigate what each part of the class definition means.

The instance variables are variables that will be part of each object created as an instance of the Person class:

  private String name;
  private int age;
  private String hobby;

Note that these are like the static variables we saw earlier, except by omitting the static keyword, they instead define instance variables that belong to object instances.

One way you can think of an object as as a box, where each instance variable is a smaller box within the object:

The constructor is a special method whose purpose is to initialize newly-created objects:

  public Person(String name, int age, String hobby) {
    this.name = name;
    this.age = age;
    this.hobby = hobby;
  }

There are some important differences between this constructor and the static methods we've looked at previously.

First, a constructor does not have a return type.  Constructors do not return a value.

The name of a constructor is always the same as the name of the class: in this case, "Person".

Constructors are not static.  That means that they are always invoked on a particular instance (object) of the class the constructor belongs to (in this case, Person).  The instance that the constructor is invoked on is accessed through a special hidden parameter called this.

The main purpose of a constructor is to initialize the new object's instance variables.  Often, the values are simply copied from the constructor's parameters, which often have the same name as corresponding instance variables.  That is the case in the constructor above: each instance variable is initialized using the value of an identically-named parameter.  Note that the within the constructors the instance variables are referred to as this.name, this.age, and this.hobby.  Recall that the dot operator (".") is the choice operator: it selects a variable or method.  For example, in the expression

this.name

we are choosing the instance variable called name in the class of which the object referred to by this is a member.  Because we are in a constructor for the Person class, this refers to a Person object, and name is one of the instance variables in the Person class.

Creating Objects

Now that we have a class with instance variables and a constructor, we're ready to create some objects!  In Java, objects are created using the new operator.  Let's try creating an object in the DrJava interactions window.

Let's deconstruct the meaning of this statement:

Person alice = new Person("Alice", 27, "ultimate");

Person alice is a variable declaration: we're declaring a variable called alice that refers to an object that is an instance of the Person class.

The new operator creates a new object.  Immediately following the new operator is an invocation of a constructor: in this case, it is the constructor for our Person class.  The arguments "Alice", 27, "ultimate" are values that will be used to initialize the constructor parameters when the method is called.

Just like any other method call, a call to a constructor creates a private environment in which the statements in the constructor will be executed.  The environment stores the types, names, and values of all parameters and local variables used in the method.  Here is the environment that will be created for this constructor invocation:

The environment defines the values of the parameters this, name, age, and hobby.

After the environment is created for the constructor invocation, the statements inside the constructor are executed within this environment:

    this.name = name;
    this.age = age;
    this.hobby = hobby;

These statements copy the values of the parameters to the corresponding instance variables of the object referred to by this.  After these statements execute, the environment will look like this:

When the constructor returns, the new operator returns a reference to the new, initialized object.  The reference to the object is then stored in the variable alice.

Accessor Methods

Our Person class isn't very useful yet, because once a Person object is constructed, it can't do anything useful.  Recall that the private keyword means "can only be accessed by methods within the class".  That means no code outside the Person class can access the values of its instance variables, because they are declated as private.

An accessor method accesses the value of one of an object's instance variables.  Let's define some accessor methods that can be used on instances of the Person class.  Here is the updated class definition:

public class Person {
  private String name;
  private int age;
  private String hobby;

  public Person(String name, int age, String hobby) {
    this.name = name;
    this.age = age;
    this.hobby = hobby;
  }

  public String getName() { return this.name; }
  public int getAge() { return this.age; }
  public String getHobby() { return this.hobby; }
}

Using the accessor methods, we can now inspect the contents of Person objects:

Note that we have created two Person objects, and using the accessor methods, we can see that their instance variables have different values, illustrating that each object has its own (separate) instance variables.