The Way of the Java/Interesting objects

Interesting objects

edit

What is interesting?

edit

Although Strings are objects, they are not very interesting ones, because:

  • They are immutable.
  • They have no instance variables.
  • You do not have to use the new construct to create one.

In this chapter, we are going to use two new object types that are part of the Java language: Point and Rectangle. Please note that these are not graphical objects that appear on the screen, but variables that contain data, just like ints and doubles. Like other variables, they can be used internally to perform computations.

The definitions of the Point and Rectangle classes are in the java.awt package, so we have to import them.

Packages

edit

The built-in Java classes are divided into a number of packages, including java.lang, which contains almost all of the classes we have seen so far, and java.awt, which contains classes that pertain to the Java Abstract Window Toolkit (AWT), which contains classes for windows, buttons, graphics, etc.

In order to use a package, you have to import it, which is why the program in Section graphics starts with import java.awt.*. The * indicates that we want to import all the classes in the AWT package. If you want, you can name the classes you want to import explicitly, but there is no great advantage. The classes in java.lang are imported automatically, which is why most of our programs have not required an import statement.

All import statements appear at the beginning of the program, outside the class definition.

Point objects

edit

At the most basic level, a point is two numbers (coordinates) that we treat collectively as a single object. In mathematical notation, points are often written in parentheses, with a comma separating the coordinates. For example, indicates the origin, and indicates the point units to the right and units up from the origin.

In Java, a point is represented by a Point object. To create a new point, you have to use the new command:

    Point blank;
    blank = new Point (3, 4);

The first line is a conventional variable declaration: blank has type Point. The second line is kind of funny-looking; it invokes the new command, specifies the type of the new object, and provides arguments. It will probably not surprise you that the arguments are the coordinates of the new point.

The result of the new command is a reference to the new point. I'll explain references more later; for now the important thing is that the variable blank contains a the address of the newly-created object.

Taken together, all the variables, values, and objects in a program are called the state. Diagrams like this that show the state of the program are called state diagrams. As the program runs, the state changes, so you should think of a state diagram as a snapshot of a particular point in the execution.

Instance variables

edit

The pieces of data that make up an object are sometimes called components, records, or fields. In Java they are called instance variables because each object, which is an instance of its type, has its own copy of the instance variables.

It is like the glove compartment of a car. Each car is an instance of the type car, and each car has its own glove compartment. If you asked me to get something from the glove compartment of your car, you would have to tell me which car is yours.

Similarly, if you want to read a value from an instance variable, you have to specify the object you want to get it from. In Java this is done using dot notation.

    int x = blank.x;

The expression blank.x means go to the object blank refers to, and get the value of x. In this case we assign that value to a local variable named x. Notice that there is no conflict between the local variable named x and the instance variable named x. The purpose of dot notation is to identify which variable you are referring to unambiguously.

You can use dot notation as part of any Java expression, so the following are legal.

    System.out.println (blank.x + ", " + blank.y);
    int distance = blank.x * blank.x + blank.y * blank.y;

The first line prints 3, 4; the second line calculates the value 25.

Objects as parameters

edit

You can pass objects as parameters in the usual way. For example:

  public static void printPoint (Point p){
    System.out.println ("(" + p.x + ", " + p.y + ")");
  }

is a method that takes a point as an argument and prints it in the standard format. If you invoke printPoint (blank), it will print (3, 4). Actually, Java has a built-in method for printing Points. If you invoke System.out.println (blank), you get: java.awt.Point[x=3,y=4]

This is a standard format Java uses for printing objects. It prints the name of the type, followed by the contents of the object, including the names and values of the instance variables.

As a second example, we can rewrite the distance method from Section distance so that it takes two Points as parameters instead of four doubles.

  public static double distance (Point p1, Point p2) {
    double dx = (double)(p2.x - p1.x);
    double dy = (double)(p2.y - p1.y);
    return Math.sqrt (dx*dx + dy*dy);
  }

The typecasts are not really necessary; I just added them as a reminder that the instance variables in a Point are integers.

Rectangles

edit

Rectangles are similar to points, except that they have four instance variables, named x, y, width and height. Other than that, everything is pretty much the same.

    Rectangle box = new Rectangle (0, 0, 100, 200);

creates a new Rectangle object and makes box refer to it.

If you print box, you get:

   java.awt.Rectangle[x=0,y=0,width=100,height=200]

Again, this is the result of a built-in Java method that knows how to print Rectangle objects.


Objects as return types

edit

You can write methods that return objects. For example, findCenter takes a Rectangle as an argument and returns a Point that contains the coordinates of the center of the Rectangle:

  public static Point findCenter (Rectangle box)
    int x = box.x + box.width/2;
    int y = box.y + box.height/2;
    return new Point (x, y);

Notice that you can use new to create a new object, and then immediately use the result as a return value.

Objects are mutable

edit

You can change the contents of an object by making an assignment to one of its instance variables. For example, to move a rectangle without changing its size, you could modify the x and y values:

    box.x = box.x + 50;
    box.y = box.y + 100;

We could take this code and encapsulate it in a method, and generalize it to move the rectangle by any amount:

  public static void moveRect (Rectangle box, int dx, int dy)
    box.x = box.x + dx;
    box.y = box.y + dy;

The variables dx and dy indicate how far to move the rectangle in each direction. Invoking this method has the effect of modifying the Rectangle that is passed as an argument.

    Rectangle box = new Rectangle (0, 0, 100, 200);
    moveRect (box, 50, 100);
    System.out.println (box);

prints:

   java.awt.Rectangle[x=50,y=100,width=100,height=200].

Modifying objects by passing them as arguments to methods can be useful, but it can also make debugging more difficult because it is not always clear which method invocations do or do not modify their arguments. Later, I will discuss some pros and cons of this programming style.

In the meantime, we can enjoy the luxury of Java's built-in methods, which include translate, which does exactly the same thing as moveRect, although the syntax for invoking it is a little different. Instead of passing the Rectangle as an argument, we invoke translate on the Rectangle and pass only dx and dy as arguments.

    box.translate (50, 100);

The effect is exactly the same.

Aliasing

edit

aliasing

aliasing
reference

Remember that when you make an assignment to an object variable, you are assigning a reference to an object. It is possible to have multiple variables that refer to the same object. For example, this code:

    Rectangle box1 = new Rectangle (0, 0, 100, 200);
    Rectangle box2 = box1;

Both box1 and box2 refer or point to the same object. In other words, this object has two names, box1 and box2. When a person uses two names, it is called aliasing. Same thing with objects.

When two variables are aliased, any changes that affect one variable also affect the other. For example:

    System.out.println (box2.width);
    box1.grow (50, 50);
    System.out.println (box2.width);

The first line prints 100, which is the width of the Rectangle referred to by box2. The second line invokes the grow method on box1, which expands the Rectangle by 50 pixels in every direction (see the documentation for more details).

As should be clear from this figure, whatever changes are made to box1 also apply to box2. Thus, the value printed by the third line is 200, the width of the expanded rectangle. (As an aside, it is perfectly legal for the coordinates of a Rectangle to be negative.)

As you can tell even from this simple example, code that involves aliasing can get confusing fast, and it can be very difficult to debug. In general, aliasing should be avoided or used with care.

null

edit

When you create an object variable, remember that you are creating a reference to an object. Until you make the variable point to an object, the value of the variable is null. null is a special value in Java (and a Java keyword) that is used to mean no object.

The declaration Point blank; is equivalent to this initialization:

    Point blank = null;

If you try to use a null object, either by accessing an instance variable or invoking a method, you will get a NullPointerException. The system will print an error message and terminate the program.

    Point blank = null;
    int x = blank.x;              // NullPointerException
    blank.translate (50, 50);     // NullPointerException

On the other hand, it is legal to pass a null object as an argument or receive one as a return value. In fact, it is common to do so, for example to represent an empty set or indicate an error condition.

Garbage collection

edit

In Section aliasing we talked about what happens when more than one variable refers to the same object. What happens when no variable refers to an object? For example:

    Point blank = new Point (3, 4);
    blank = null;

The first line creates a new Point object and makes blank refer to it. The second line changes blank so that instead of referring to the object, it refers to nothing (the null object).

If no one refers to an object, then no one can read or write any of its values, or invoke a method on it. In effect, it ceases to exist. We could keep the object in memory, but it would only waste space, so periodically as your program runs, the Java system looks for stranded objects and reclaims them, in a process called garbage collection. Later, the memory space occupied by the object will be available to be used as part of a new object.

You do not have to do anything to make garbage collection work, and in general you will not be aware of it.

Objects and primitives

edit

There are two kinds of types in Java, primitive types and object types. Primitives, like int and boolean begin with lower-case letters; object types begin with upper-case letters. This distinction is useful because it reminds us of some of the differences between them:

When you declare a primitive variable, you get storage space for a primitive value. When you declare an object variable, you get a space for a reference to an object. In order to get space for the object itself, you have to use the new command.

If you do not initialize a primitive type, it is given a default value that depends on the type. For example, 0 for ints and true for booleans. The default value for object types is null, which indicates no object.

Primitive variables are well isolated in the sense that there is nothing you can do in one method that will affect a variable in another method. Object variables can be tricky to work with because they are not as well isolated. If you pass a reference to an object as an argument, the method you invoke might modify the object, in which case you will see the effect. The same is true when you invoke a method on an object. Of course, that can be a good thing, but you have to be aware of it.

There is one other difference between primitives and object types. You cannot add new primitives to the Java language (unless you get yourself on the standards committee), but you can create new object types! We'll see how in the next chapter.

Glossary

edit
  • package: A collection of classes. The built-in Java classes are organized in packages.
  • AWT: The Abstract Window Toolkit, one of the biggest and most commonly-used Java packages.
  • instance: An example from a category. My cat is an instance of the category feline things. Every object is an instance of some class.
  • instance variable: One of the named data items that make up an object. Each object (instance) has its own copy of the instance variables for its class.
  • reference: A value that indicates an object. In a state diagram, a reference appears as an arrow.
  • aliasing: The condition when two or more variables refer to the same object.
  • garbage collection: The process of finding objects that have no references and reclaiming their storage space.
  • state: A complete description of all the variables and objects and their values, at a given point during the execution of a program.
  • state diagram: A snapshot of the state of a program, shown graphically.