Section 4.1
Objects, Instance Variables, and Instance Methods


OBJECT-ORIENTED PROGRAMMING (OOP) represents an attempt to make programs more closely model the way people think about and deal with the world. In the older styles of programming, a programmer who is faced with some problem must identify a computing task that needs to be performed in order to solve the problem. Programming then consists of finding a sequence of instructions that will accomplish that task. But at the heart of object-oriented programming, instead of tasks we find objects -- entities that have behaviors, that hold information, and that can interact with one another. Programming consists of designing a set of objects that somehow model the problem at hand. Software objects in the program can represent real or abstract entities in the problem domain. This is supposed to make the design of the program more natural and hence easier to get right and easier to understand.

To some extent, OOP is just a change in point of view. We can think of an object in standard programming terms as nothing more than a set of variables together with some subroutines for manipulating those variables. In fact, it is possible to use object-oriented techniques in any programming language. However, there is a big difference between a language that makes OOP possible and one that actively supports it. An object-oriented programming language such as Java includes a number of features that make it very different from a standard language. In order to make effective use of those features, you have to "orient" your thinking correctly.

Every object belongs to some class. We say that the object is an instance of that class. In object-oriented programming, classes are templates for making objects. That is, the class of an object specifies what sort of data that object contains and what behaviors it has. The data of an object is contained in a set of variables called the instance variables, and the behaviors of the object exist as a set of subroutines called the instance methods of the object.

In Java in particular, it is the non-static variables and methods in the class that determine the contents of an object, while the class's static variables and methods are members of the class itself rather than of the objects that belong to that class. See Section 2.6. Each non-static variable in a class becomes an instance variable in every object that belongs to that class. Each non-static subroutine in the class becomes an instance method in the object. It is important to understand that the class of an object determines the types of the instance variables; however, the actual data is contained inside the individual object, not the class. Thus, each object has its own set of data.

For example, there might be a class named Student. Suppose that this class has a non-static variable called name, of type String. When this class is first loaded by the system, there are no actual names -- just a kind of idea that students have names. Every time a Student object is created using the Student class as a template, the object will include an instance variable called name of type String. Since each Student includes its own name variable, each student can have a different name. Similarly, if objects of class Student have instance variables to represent test grades, then each Student object has its own set of grades.

Objects that belong to the same class have the same behaviors. That is, they have the same instance methods. In some sense, all these objects share the same instance methods, in the sense that the methods contain exactly the same program code. However, it is useful to think of each object as having its own copy of the instance methods. Subroutines manipulate data; the instance methods of an object manipulate the instance variables of that object. An instance method belonging to an object has direct access to that particular object's instance variables. For example, suppose that Student objects include an instance method called getAverage() that computes and returns a student's average test grade. When this method is called for a particular object of class Student, it will use that student's test grades, taken from the object's instance variables. The same method called for a different student will use the other student's grades instead.

Static variables and methods in a class are sometimes called class variables and class methods, since they belong to the class itself, rather than to instances of that class. Objects belonging to the class do have access to the static variables and methods of the class, even those that are declared to be private. However, there is only one copy of a static variable, which is shared by all the objects and which exists during the whole time a program is running. Instance variables are created and destroyed during the run of a program as the objects to which they belong are created and destroyed. Instance variables are said to be dynamically created and destroyed. (You can think of "static" as the opposite of "dynamic.")


Let's look at a specific example to see how all this works. Consider this rather simplified version of a Student class:

        public class Student {
        
           public String name;  // Student's name
           public int ID;       // unique ID number for this student
           public double test1, test2, test3;   // grades on three tests
           
           public double getAverage() {  // compute average test grade
              return (test1 + test2 + test3) / 3;
           }
           
           private static int nextUniqueID = 0;
           
           public static int getUniqueID() {  // return a unique ID
              nextUniqueID++;
              return nextUniqueID;
           }
           
        }  // end of class Student

This class definition says that each object of class Student will include instance variables name, ID, test1, test2, and test3, and it will include an instance method named getAverage(). The names, IDs, and tests in different objects will generally have different values. When called for a particular student, the method getAverage() will compute that student's average, using that student's test grades.

On the other hand, nextUniqueID and getUniqueID() are static members of class Student. There is only one copy of the variable nextUniqueID, and it belongs to the class itself. Similarly, getUniqueID() is associated with the class, not with any particular instance. The definition of getUniqueID() refers to the static variable nextUniqueID. It would make no sense -- and would be a syntax error -- for getUniqueID() to refer to one of the instance variables, such as name. A static method is not part of an object, and it does not have direct access to the instance variables inside any object.

The method getUniqueID() can be called from outside the class using the compound identifier "Student.getUniqueID()", indicating its membership in the class Student. This can be done, of course, whether or not any instances of the class even exist. The instance method getAverage(), on the other hand, can only be called through an object of class Student. If std is such an object, then the method can be called using the identifier "std.getAverage()", indicating that the method belongs to the object std. The instance variables of std would be referred to as std.name, std.ID, std.test1, std.test2, and std.test3.

(By the way, since it is possible to think of static methods and variables in a class as being shared by all the instances of that class, Java will let you refer to static members through objects, as well as through the class name. Thus, if std is an instance of class Student, it is legal to refer to std.getUniqueID() instead of Student.getUniqueID(). However, I feel that this syntax is likely to lead to confusion, and I urge you to avoid it.)

It's worth taking a somewhat closer look at the static member variable nextUniqueID. Since it is a static variable, there is just one version of this variable, and it exists for the whole time that the program is running. At the beginning of the program's execution, the initial value, 0, is stored in the variable Student.nextUniqueID. Every time the static method Student.getUniqueID() is called, the value of nextUniqueID is incremented. Now, since nextUniqueID is declared to be private, it is completely inaccessible from outside the class Student. By examining the text of the class Student, we can see everything that can ever be done with this variable. This means that it is absolutely guaranteed that the only way that the value of nextUniqueID can change is when the method getUniqueID() is called. In particular, getUniqueID() is absolutely guaranteed to return a different value each time it is called.

Guarantees of this type are important if you want to prove that a program behaves the way it is supposed to. It might be nice if instance variables such as test1, test2, and test3, had similar protection. It is possible to declare instance variables to be private. If you did that in the Student example, you would also want to add instance methods to manipulate the instance variables, just as getUniqueID() has been provided to manipulate the private variable nextUniqueID. (There is, by the way, another access modifier, in addition to private and public. A variable or method that is declared to be protected can be used in the class where it is declared and in any subclass of that class. Subclasses are covered in the next section.)


So far, I've told you that objects can be created using classes as templates, but I haven't told you how to create objects in a program. A class name is considered to be a type, similar to built-in types such as int and boolean. So, if you have a class, such as Student, you can use the class name to declare variables of that class:

          Student std;  // declare variable std of type Student

However, declaring a variable does not create an object! This is an important point, which is related to this Very Important Fact:

In Java, no variable can ever hold an object.
A variable can only hold a reference to an object.

You should think of objects as floating around independently in the computer's memory. In fact, there is a portion of memory called the heap where objects live. Instead of holding an object itself, a variable holds the information necessary to find the object in memory. This information is called a reference or pointer to the object. In effect, a reference to an object is the address of the memory location where the object is stored. When you use a variable of class type, the computer uses the reference in the variable to find the actual object.

Objects are actually created by an operator called new, which creates an object and returns a reference to that object. For example, assuming that std is a variable of type Student, the assignment statement

std = new Student();

would create a new object of type Student and store a reference to that object in the variable std. The instance variables and methods of the object could then be accessed through std, as in "std.test1".

It is possible for a variable like std, whose type is given by a class, to refer to no object at all. We say in this case that std holds a null reference. The null reference can be written in Java as "null". You could assign a null reference to the variable std by saying

std = null;

and you could test whether the value of std is null by testing

if (std == null) . . .

If the value of a variable is null, then it is, of course, illegal to refer to instance variables or instance methods through that variable -- since there is no object, and hence no instance variables to refer to. For example, if the value of the variable std is null, then it would be illegal to refer to std.test1. If your program attempts to use a null reference illegally like this, the result is an error called a null pointer exception.

Let's look at a sequence of statements that work with objects:

      Student std, std1,       // Declare four variables of
                std2, std3;    //   type Student.
      std = new Student();     // Create a new object belonging
                               //   to the class Student, and
                               //   store a reference to that
                               //   object in the variable std.
      std1 = new Student();    // Create a second Student object
                               //   and store a reference to
                               //   it in the variable std1.
      std2 = std1;             // Copy the reference value in std1
                               //   into the variable std2.
      std3 = null;             // Store a null reference in the
                               //   variable std3.
                               
      std.name = "John Smith";             // Set values of some instance
      std.ID = Student.getUniqueID();      //       variables.
      std1.name = "Mary Jones";
      std1.ID = Student.getUniqueID();
           // (Other instance variables have default
           //    initial values of zero.)

After the computer executes these statements, the situation looks something like this (assuming that the two calls to getUniqueID() were the very first two times in the program that this method was called):

(Objects and variables created by above code)

This picture shows variables as little boxes, labeled with the names of the variables. Objects are shown as boxes with round corners. When a variable contains a reference to an object, the value of that variable is shown as an arrow pointing to the object. The variable std3, with a value of null doesn't point anywhere. The arrows from std1 and std2 both point to the same object. This illustrates a Very Important Point:

When one object variable is assigned
to another, only a reference is copied.
The object referred to is not copied.

This is very different from the usual semantics associated with an assignment statement. (When the values involved belong to Java's primitive types, then assignments obey the expected semantics. That is, primitive type values are copied when they are assigned.) Note that in this example, since std1.name was assigned the value "Mary Jones", it will also be true that std2.name has the value "Mary Jones". In fact, std1.name and std2.name are just different ways of referring to exactly the same memory location.

You can test objects for equality and inequality using the operators == and !=, but here again, the semantics are unusual. When you make a test "if (std1 == std2)", you are testing whether the object references in std1 and std2 point to exactly the same location in memory; you are not testing whether the values stored in the objects are equal. For example, two distinct Student objects with the same name, ID, and test grades would not be considered equal by the == operator because distinct objects are stored in distinct memory locations.

The semantics of the == operator for Strings came up previously, in Section 2.9. As mentioned in that section, and as shown in the above illustration, Strings are objects. This means that a variable of type String holds a reference to a String object, which is stored elsewhere in memory. It also means, by the way, that the value of a String variable can be null, so you have to be careful to avoid null pointer exceptions when you work with strings.

You'll also notice that I've shown the class, Student, as an object in the illustration. As mentioned in Section 2.6, classes are technically considered to be objects. The "instance variables" and "instance methods" of a class, considered as an object, are just the static members of the class. Perhaps thinking of things this way will help you to understand static and non-static members.


[ Next Section | Previous Chapter | Chapter Index | Main Index ]