Introduction to Computer Graphics and Java Programming for Artists


School of the Museum of Fine Arts ::: Continuing Education

George Aroush, instructor


Lecture Twelve -- More on Java's classes; Inheratation


 

Readings

  1. The handouts named:
  2. Sample Programs in Batch Twelve:

 

Exercises

    Do before our next lecture

  1. None

Back to the top of this page. Back to main Index

 

More on Java's Classes

The Need for Initialization & Cleanup

Many C (and other traditional programming languages) bugs occur when the programmer forgets to initialize a variable. This is especially true with libraries, when users don't know how to initialize a library component, or even that they must. Cleanup is a special problem because it's easy to forget about an element when you're done with it, since it no longer concerns you. Thus, the resources used by that element are still retained, and you can easily end up running out of resources (most notably memory).

C++ introduced the concept of a constructor, a special method automatically called when an object is created. Java also adopted the constructor, and in addition has a garbage collector that automatically releases memory resources when they're no longer being used.

Guaranteed Initialization with the Constructor

We can imagine creating a method called initialize() (or init() as we have been doing) for every class we write. The name is a hint that it should be called before using the object. Unfortunately, this means the user must remember to call the method. In Java, the class designer can guarantee initialization of every object by providing a special method called a constructor. If a class has a constructor, Java automatically calls that constructor when an object is created, before users can even get their hands on it. So initialization is guaranteed.

The next challenge is what to name this method. There are two issues. The first is that any name we use could clash with a name we might like to use as a member in the class. The second is that because the compiler is responsible for calling the constructor, it must always know which method to call. The C++ solution seems the easiest and most logical, so it's also used in Java: The name of the constructor is the same as the name of the class. It makes sense that such a method will be called automatically on initialization.

Here's a simple class with a constructor:

class Rock    // our class with a constructor
{
    Rock()    // This is the constructor
    {
        System.out.println("Creating Rock");
    }
}

public class SimpleConstructor
{
    public static void main(String args[])
    {
        Rock r1 = new Rock();
        Rock r2 = new Rock();
        Rock r3 = new Rock();
    }
}

Now, when an object is created using the new operator, storage is allocated and the constructor is called. It is guaranteed that the object will be properly initialized before you can get your hands on it.

Like any method, the constructor can have arguments to allow you to specify how an object is created. The above example can easily be changed so the constructor takes an argument:

class Rock    // our class with a constructor
{
    Rock(int i)    // This is the constructor
    {
        System.out.println("Creating Rock number " + i);
    }
}

public class SimpleConstructor
{
    public static void main(String args[])
    {
        Rock r1 = new Rock(1);
        Rock r2 = new Rock(2);
        Rock r3 = new Rock(3);
    }
}

Constructor arguments provide us with a way to provide parameters for the initialization of an object. For example, if the class Tree has a constructor that takes a single integer argument denoting the height of the tree, we would create a Tree object like this:

Tree t = new Tree(12);    // 12-foot tree

If Tree(int) is our only constructor, then the compiler won't let us create a Tree object any other way. Thus for the latest Rock class example where Rock() takes an int, we can't create a Rock object using:

Rock r3 = new Rock();    // ERROR: must use Rock(int)

Constructors eliminate a large class of problems and make the code easier to read. In the preceding code fragment, for example, we don't see an explicit call to some initialize( ) (or init()) method that is conceptually separate from definition. In Java, definition and initialization are unified concepts - you can't have one without the other.

The constructor is a very unusual type of method: it has no return value. This is distinctly different from a void return value, where the method returns nothing but we still have the option to make it return something else. Constructors return nothing and we don't have an option. If there were a return value, and if we could select our own, the compiler would somehow have to know what to do with that return value.

Default Constructor

As mentioned previously, a default constructor is one without arguments, used to create a "vanilla object." If we create a class that has no constructors, the compiler will automatically create a default constructor for us. For example:

class Bird
{
    int     i;
}

public class DefaultConstructor
{
    public static void main(String args[])
    {
        Bird nc = new Bird(); // default!
    }
}

The line: new Bird(); creates a new object and calls the default constructor, even though one was not explicitly defined. Without it we would have no method to call to build our object. However, if we define any constructors (with or without arguments), the compiler will not synthesize one for us:

class Bush
{
    Bush(int i)
    {
    }

    Bush(float f)
    {
    }
}

public class DefaultConstructor
{
    public static void main(String args[])
    {
        Bird nc = new Bird(); // default!
    }
}

Now if we say: new Bush(); the compiler will complain that it cannot find a constructor that matches. It's as if when we don't put in any constructors, it says: "you are bound to need some constructor, so let me make one for you." But if you write a constructor, it says "you've written a constructor so you know what you're doing; if you didn't put in a default it's because you meant to leave it out."

Cleanup: Finalization and Garbage Collection

Programmers know about the importance of initialization, but often forget the importance of cleanup. After all, who needs to clean up an int? But with libraries, simply "letting go" of an object once we are done with it is not always safe. Of course, Java has the garbage collector to reclaim the memory of objects that are no longer used. Now lets consider a very special and unusual case. Suppose our object allocates "special" memory without using new. The garbage collector knows only how to release memory allocated with new, so it won't know how to release the object's "special" memory. To handle this case, Java provides a method called finalize() that we can define for our class. Here's how it's supposed to work: when the garbage collector is ready to release the storage used for our object, it will first call finalize(), and only on the next garbage-collection pass will it reclaim the object's memory. So if we choose to use finalize(), it gives us the ability to perform some important cleanup at the time of garbage collection.

For example, suppose in the process of creating our object it draws itself on the screen. If we don't explicitly erase its image from the screen, it might never get cleaned up. If we put some kind of erasing functionality inside finalize(), then if an object is garbage-collected, the image will first be removed from the screen, but if it isn't the image will remain.

But our objects might not get garbage collected. We may find that the storage for an object never gets released because our program never nears the point of running out of storage. If our program completes and the garbage collector never gets around to releasing the storage for any of our objects, that storage will be returned to the operating system en masse as the program exits. This is a good thing, because garbage collection has some overhead, and if we never do it we never incur that expense.


Back to the top of this page. Back to main Index

 

Inheratation: Reusing classes

Its all about Code Reuse

One of the most compelling features about Java is code reuse. But to be revolutionary, we have got to be able to do a lot more than copy code and change it. That's the approach used in procedural languages like C, and it hasn't worked very well. As with everything in Java, the solution revolves around the class. We reuse code by creating new classes, but instead of creating them from scratch, we use existing classes that someone has already built and debugged.

The trick is to use the classes without soiling the existing code. Today we will look at two ways. The first is quite straightforward: we simply create objects of our existing class inside the new class. This is called composition because the new class is composed of objects of existing classes. Here, we are simply reusing the functionality of the code, not its form. The second approach is more subtle. It creates a new class as a type of an existing class. We literally take the form of the existing class and add code to it, without modifying the existing class. This magical act is called inheritance, and the compiler does most of the work. Inheritance is one of the cornerstones of object-oriented programming and has additional implications that will be explored in an upcoming lecture.

Composition syntax

Up till now composition has been used quite frequently. We simply place object references inside new classes. For the non-primitive objects, we just put references inside our new class, and for the primitives we just define them inside our class. This we have done when we created a Color object for example such as:

void paint(Graphics g)
{
    Color rgb = new Color(0, 0, 256);
    
    ....
}

Inheritance syntax

Just like composition we have been using inhabitation but not to it's full extents. The syntax for composition is obvious, but to perform inheritance there's a distinctly different form. When we inherit, we say "This new class is like that old class." We state this in code by giving the name of the class as usual, but before the opening brace of the class body, we put the keyword extends followed by the name of the base class. When we do this, we automatically get all the data members and methods in the base class. Here's an example of how we used it so far:

public class MyApplet extends java.applet.Applet
{
    void paint(Graphics g)
    {
    }
}

As a result of having extends after MyApplet, MyApplet now has the full functionality of Applet  (sort of -- more on this later) as if MyApplet and Applet are one. Another example of inheritance with more details follows:

    // This class allows basic 2D point access
class TwoDPoint
{
    int     mX, mY;

    void    Set(x, y)
    {
        mX = x;
        mY = y;
    }

    int     GetX()
    {
        return (mX);
    }

    int     GetY()
    {
        return (mY);
    }
}

    // This class provides 3D point access
class ThreeDPoint extends TwoDPoint
{
    int     mZ;

    void    Set(x, y, z)
    {
        mX = x;
        mY = y;
        mZ = z;
    }

    void    GetZ()
    {
        return (mZ);
    }
}

    // Test class to try out those two classes
class MyTest
{
    void    TestOne()
    {
        int          x, y, z;
        ThreeDPoint p = new ThreeDPoint();

        p.Set(10, 15, 5);
   
        x = p.GetX();
        y = p.GetY();
        z = p.GetZ();
    }

    void    TestTwo()
    {
        int          x, y, z;
        TwoDPoint p = new TwoDPoint();

        p.Set(10, 15, 5);    // this won't work as Set() takes 2 parameters only
        p.Set(10, 15);
   
        x = p.GetX();
        y = p.GetY();
        z = p.GetZ();      // this won't work as GetZ() is not part of TwoDPoint class
    }
}

In this example, we have created two classes: TwoDPoint and ThreeDPoint. ThreeDPoint inherits the functionality of TwoDPoint in such a way that it utilizes and provides TwoDPoint's functionality through itself.

This basic capability allows a developer to create new classes without having to re-write everything from scratch. And, for the user of ThreeDPoint, in many cases, it's as if ThreeDPoint class is a stand alone object doing everything itself.


Back to the top of this page. Back to main Index

 

Sample Programs -- Batch Twelve


Back to the top of this page. Back to main Index.