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. At the heart of standard programming is the idea of a task to be performed, and programming consists of finding a sequence of instructions that will accomplish that task. At the heart of object-oriented programming are 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.

Classes in Java are templates for making objects. Every object belongs to some class. We say that the object is an instance of that class. The class of an object determines what sort of data that object contains and what behaviors it has. The object's data is contained in a set of variables, which are called instance variables. It is important to understand that the class of an object determines what types of variables the object contains; 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. The class could specify that every object of type Student includes an instance variable called name, of type String. There could be any number of objects belonging to the Student class. Each of those objects, because it is a Student, would have a name. The point is that each of the Student objects would have its own name. Similarly, if objects of class Student have instance variables to represent test grades, then each Student object has its own set of grades.

In addition to data, an object also has behaviors. These behaviors are subroutines that belong to the object. I will generally use the term "method" for subroutines that belong to an object and that represent its behaviors. Such methods are called instance methods, because they belong to an instance of a class.

Objects that belong to the same class have the same instance methods; that is, they have the same behaviors. However, you should still think of instance methods as belonging to individual objects, not to classes. There is a subtle difference between the instances of the same method in two different objects of the same class: A method belonging to an object has direct access to that particular object's instance variables. For example, a Student class might specify an instance method for computing the overall grade of a student by averaging that student's test grades. 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.

Classes in Java serve a double purpose. One of these is the one just described: to serve as templates for making objects. The other is the one you have seen in previous chapters: to group together related static variables and static methods. The rule is that static variables and static methods -- that is, those declared with the static modifer -- belong to the class itself, and not to objects created from that class. Non-static variables and methods don't belong to the class at all! Instead, they are there to specify what instance variables and instance methods objects of that class will have. This is confusing, no doubt about it, and it could well be argued that Java's design is defective in this regard; it might have been better to assign the two different functions of classes to two different language features.

Static variables and methods are sometimes called class variables and class methods, since they belong to the class itself, rather than to instances of that class.

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 = 1;
           public static int getUniqueID() {  // return a unique ID
              int thisID = nextUniqueID;
              return thisID;
        }  // end of class Student

This class definition says that an 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. 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 name "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 name "std.getAverage()", indicating that the method belongs to the object std. The instance variables of std would be referred to as, std.ID, std.test1, std.test2, and std.test3.

(By the way, it is possible to think of static methods and variables in a class as being "shared" by all the instances of that class. Based on this reasoning, 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 can only increase the 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. (Instance variables, on the other hand, come into existence and disappear as objects are created and disposed of.)

At the beginning of the program's execution, the initial value, 1, 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. We can see everything that can ever be done with this variable by examining the class Student. 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. It might be nice if other variables, such as the instance variables test1, test2, and test3, had similar protection. I'll return to the question of controlling access to member variables later.

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. If you have a class, such as Student, you can 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 object 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,

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 = new Student();  // Declare a variable, std,
                                    //   and initialize it with a
                                    //   reference to a newly created
                                    //   object of the class Student.
      Student std1 = new Student(); // Declare std1, and initialize
                                    //   it to refer to another
                                    //   new object.
      Student std2 = std1;          // Declare std2, and initialize
                                    //   it to refer to the SAME 
                                    //   object that std1 refers to.
      Student std3;                 // Declare std3, and initialize
                                    //   it to null.  (This is done
                                    //   automatically.) = "John Smith";
      std.ID = Student.getUniqueID(); = "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 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 was assigned the value "Mary Jones", it will also be true that has the value "Mary Jones". In fact, and are just different ways of referring to exactly the same thing.

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. It would be possible to have two objects whose instance variables all have identical values. However, those objects are not considered equal by the == operator because they are stored in distinct memory locations.

There are a few surprises in the above illustration. You'll notice that strings in Java are objects. In particular, it is possible for the value of a String variable to be null. And, in any case, a String variable can only store a reference to a string, not the string itself. This explains why the == operator doesn't work as one would expect for Strings (as discussed in Section 2.8): Two strings are judged equal by == if they are stored in the same place in memory, not merely if they happen to contain the same characters. That is, if s1 and s2 are two strings, it is possible that s1 == s2 is false even if s1 and s2 contain exactly the same characters! This is why you should use the test s1.equals(s2) to see whether s1 and s2 are the same.

You'll also notice that I've showed the class, Student, as an object. In Java, classes are technically considered to be objects and to belong to a special class called, appropriately enough, Class. 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 ]