Previous Page TOC Next Page See Page



slug: Web Programming with Java, 113-0, 04 WPJ04FB.W6W

4


Creating Your Own Objects


by Kathryn Jones

This chapter builds on the fundamentals of object-oriented programming covered in Chapter 1, "An Overview of Java." It explains how to create actual objects, classes, and interfaces. First, you will learn to create objects from the classes in Java's packages, which are covered in Chapter 3, "An Introduction to Java Classes." Second, you will learn to write your own classes and methods; and third, you'll learn how to create and use interfaces. After you master these concepts, you will be ready to move onto all the fun chapters that show you how to create Java applets and applications.

As you know, an object is an instance of a class that has variables that describe it and methods that modify it. The Java applications you will write will continuously create objects from classes. Objects you create interact with other objects created in your application by invoking methods to perform actions on the other objects. So, essentially, all you'll need to know in order to write a Java application is how to create objects from classes and how to create methods to manipulate them. Using methods to combine the functionality of individual objects, you can create applications that can do practically anything. You can animate graphics, create interactive games, or create an on-line order-entry system that records and processes information a user enters into on-line forms. Later in the book, you will learn how to write each of these types of applications from beginning to end. This chapter provides shorter examples to introduce you to creating and using objects, classes, and interfaces.

Creating Objects from Java Classes


The next section teaches you how to create your own class. This section uses the classes from Java's built-in packages to concentrate on creating objects. As you learned in Chapter 3, Java provides several basic libraries of classes that have been tested and are thread-safe. You will want to use many of these utilities provided by Java's packages in your code in order to save time.

The operator you will use in your code to create new objects is called, appropriately, new. When you call the new operator in your code, you follow it by the name of the class from which you want to instantiate the object. Java automatically allocates a portion of memory to store instances of the variables declared in the class. This portion of memory is the object. After allocating an object, you will use methods in the object's class to send messages to it. You also can send messages to the object from methods in other classes.

You can use several pieces of code to create an object in Java in addition to the new operator. When new creates the object from the specified class, it automatically calls a constructor to build it. The constructors you create enable parameters to be passed to the object. You can create your own constructors for objects, but it is not a requirement. Java calls a default constructor if new is called without parameters. You can create one or many constructors for the object, each with different parameters, or you can let Java assign a default constructor. When new is called with parameters, Java selects the constructor you created that has the matching parameters.

When you create an object, you typically declare a reference variable that will hold the object's reference. Java creates a reference automatically whenever new instantiates an object in order to locate the object in memory when necessary. You will need a reference variable that stores the reference in order to refer to the object in your code. The reference variable is a name you assign to the object.

The very rudimentary form of an object creation follows:

classname reference-variable = new classname (parameter list);

Here, classname represents the class you use to create the object. reference-variable is the name you use to refer to the reference Java creates so that Java's runtime system can locate it. The new operator followed by classname actually instantiates the object from the class. The parameter list is the part of the object creation that specifies which of the constructors stored in the class are used to create the object.

The following example creates a new Rectangle object from the Rectangle class in the java.awt package:

Rectangle ThisRect = new Rectangle();

This example of the creation of a new Rectangle object accomplishes four tasks in one line of code: it declares the reference variable ThisRect, creates a new Rectangle object, assigns the Rectangle object to the reference variable ThisRect, and initializes the object.

These tasks can be separated into two lines in the following form:

classname reference-variable;  //variable declaration
reference-variable = new classname (parameter list); //creation, assignment, initialization

In the Rectangle example, you could create the Rectangle object in two lines, as this code shows:

Rectangle ThisRect;
ThisRect = new Rectangle();

Declaring the Reference Variable


Rectangle ThisRect is a simple variable declaration, much like the object-variable declarations covered in Chapter 1. This declaration tells the compiler that the reference variable ThisRect refers to an object for which the class is Rectangle. When you think about it, the class in the declaration of an object-reference variable is very similar to the data type in the declaration of a variable in a class. Recall that the data type explained in Chapter 1 was declared as the following:

data-type variable-name;

The data types used to declare object variables basically are predefined Java classes with states and behaviors just like any other class, except that data types are classes that cannot be subclassed. Therefore, you can think of the declaration of a reference variable to hold an object just as you would the declaration of a variable to hold a value of a data type.

Creating the Object


Declarations do not instantiate objects. Rectangle ThisRect does not create a new Rectangle object; it just creates a variable named ThisRect to hold a Rectangle object. To instantiate the Rectangle object, you assign the reference variable to the object-creation sequence, which consists of the new operator followed by the class name and its constructor parameters.

The new operator returns a reference to the newly created Rectangle object, which is stored in the ThisRect reference variable.

Initializing the Object


Constructors are special methods provided by each Java class to initialize new objects from a class. The new operator creates the object, and the constructor initializes it.

Here's an example of using the new operator with parameters for a constructor to build a Rectangle object with a width of 4 and a height of 2:

new Rectangle(4, 2);

Java.awt.Rectangle provides several constructors. In the example, Rectangle(4, 2) calls the constructor that exists in Java.awt.Rectangle that has arguments that match the number and types of parameters specified in the initialization statement. The 4 and 2 parameters match the number and type of the width and height arguments of the following Java.awt.Rectangle constructor:

public Rectangle(int width, int height);

A class may provide multiple constructors to perform different kinds of initializations on new objects. When looking at the implementation for a class, you can recognize the constructors because they have the same name as the class and have no return type. In a class with multiple constructors, they all have the same name but different arguments. Each constructor initializes the new object in a different way. In addition to the default constructor used to initialize a new Rectangle object and the Rectangle constructor used earlier for ThisRect, Rectangle can use a different constructor, as shown in this code:

Rectangle ThisRect = new Rectangle(3, 3, 4, 2);

This creates a Rectangle object at point 3,3 of width 4 and height 2, using the following constructor from Java.awt.Rectangle:

public Rectangle(int x, int y, int width, int height);

Using the Object


After your object is instantiated, you can change its behavior by using methods to change the values of its variables or by directly assigning new values to the variables. Using methods to change variables is a more consistent way to manipulate objects. This section examines both these procedures.

You can access an object's variables directly from another object by adding a period (.) to the end of the reference-variable name and appending the name of the object variable, as shown in this example:

reference-variable.variable;

To access the width variable in one of the Rectangle objects created in the preceding section, you can use the following reference:

ThisRect.width;

To change the value of the width and height variables in the ThisRect object, you simply set them equal to new values in the following statements:

ThisRect.width = 5;
ThisRect.height = 3;

To get the width variable from the ThisRect object, you can refer to it as the following:

Width = ThisRect.width;

You can call an object's methods by adding a period (.) to the end of the reference-variable name and appending the name of the object method, followed by parameters to the method enclosed in parentheses:

Reference-variable.methodName(parameters);

To invoke the Java.awt.Rectangle.reshape method on the ThisRect object, you use this statement:

ThisRect.reshape(5, 3);

This statement reshapes the object by modifying its height and width variables. It has the same effect as the direct variable assignments used earlier in this section:

ThisRect.width = 5;
ThisRect.height = 3;

The reshape() method in the Java.awt.Rectangle package is declared void, so it doesn't return a value. All methods that are not declared as void evaluate to some value. You can use the value returned by a method in expressions or as variable values.

Creating Your Own Classes


Although you can create functional applications by creating and using objects with Java's built-in classes, you undoubtedly will want to know how to create your own classes, constructors, methods, and variables to add additional functionality to your applications. This section explains how you can accomplish these tasks.

Writing the Class


When you create your own class, you usually will want it to be a subclass of a built-in Java class. Most basic functionality is provided by the classes in Java's packages. By restricting your class creations to subclasses of Java classes, you ensure the portability of your application. You know for sure that every user of your application will have Java's built-in classes available in his runtime system.

Remember that the primary advantage of subclassing is that it enables you to reuse code. You create subclasses as extensions of existing classes to create new objects with properties that are enhancements of existing objects. These subclasses use the existing methods and variables of the superclass and add methods and variables that make each subclass unique.

You learned the basic form of a class structure in Chapter 1. An example of how you can create a subclass called Square from the Rectangle class follows:

public class Square extends Rectangle {
//new variable and method declarations
}

Square is the name of your subclass. The name of your class must be a legal Java identifier and should begin with a capital letter. The extends Rectangle part is where the Rectangle class is identified as the superclass. This allows the Square class to use any variables or methods defined in Rectangle. If a superclass is not specified, Java assumes that the Java.lang.Object class is the superclass. The Square class' unique variables and methods are declared next.

The class Square statement uses the public access modifier to allow all other classes and subclasses to access it. You can precede a class name or method name with the word final if you do not want the compiler to allow it to be subclassed or overridden, or by the word abstract if you want to require that it be subclassed.

Your subclass inherits variables and methods from its superclass that are declared public or protected by the superclass. If variables and methods are declared private, they are not inherited. If no access modifier is specified, only classes within the same package can inherit methods and variables.

Within your subclass, you can hide the superclass' variables by using the superclass' variable names for subclass variables, and you can override methods inherited from the superclass. Although the subclass does not inherit the superclass' hidden variables and may override the superclass' methods, the subclass always can access these variables and methods as they appear in the superclass by using the keyword super, as this example shows:

ThisHeight = super.height;

This statement refers to the value of height as it is stored in the superclass.

After the class declaration, you create all the class' methods and variables, enclosing them in curly braces ({}). The collection of methods and variable declarations within the braces are called the body of the class. The variables typically are declared first.

Declaring the Member Variables


The class member variable declaration is much like the reference variable declaration in an object creation:

type variable-name;

The difference is that member variables exist in the body of the class, but are declared outside of methods, object creations, and constructors. In the following example of the Square class, the area variable is declared:

class Square extends Rectangle {
    int area;
    // methods
}

Typically, a member variable is not capitalized. It must be a legal Java identifier. No two member variables within a class can have the same name.

The member variable declaration offers several optional modifiers, as shown in this code:

 [access-modifier] [static] [final] [transient] [volatile] type variable-name;

The access modifier public, private, or protected restricts access in the same way it does for methods and classes. static defines the variable as a class variable rather than an instance variable. This means that when the variable value is changed, it is changed in all instances. An instance variable is specific to the instance only. final indicates that the variable is a constant. Constant variables typically are written in all uppercase letters. They cannot be changed. transient indicates that the variable is not part of the persistent state of the object and will not be saved when the object is archived. volatile indicates that the variable is modified asynchronously by concurrently running threads.

Creating the Methods


As you learned in Chapter 1, a method returns a value unless it is declared as void. The method name is preceded by a return type to inform Java of how to interpret the value returned. It is followed by an optional list of arguments enclosed in parentheses. Like classes and member variables, the method declaration can be preceded by an access modifier. The following code shows how the method declaration is structured:

 [access-modifier] returnType methodName([arguments]) {
    //statements
}

If you include arguments when you write a method, you can call it with matching parameters. Parameters pass information to the method.

Using the fundamentals you learned in the previous chapters of Part I about statements, expressions, operators, and variables, you can create methods to manipulate your objects.

The following sections explain several types of methods available in Java: Class and Instance methods, constructors, and finalize() methods.

Using Instance Members versus Class Members


Members--a word for the variables and methods in a class--can be specific to the class or to the instance of the class, depending on how you write them and where you place them in your code. This is an important concept to learn, because variable values may differ when you define them as class variables rather than instance variables, and vice versa. Different rules apply to instance and class members as well, so you will be prone to compile errors if you are not mindful of these concepts.

Member variables can be class variables or instance variables. Class variables are declared using the static modifier. When Java's runtime system loads the class, the class variables are allocated in memory only once. When instances of the class are created, the class variables are not copied, but instead are shared by all the instances. No additional memory is allocated for these variables because only one copy exists. Class variables can be accessed from any instance or from the class. Because class variables are shared by instances, the values assigned to them are the same for all instances of the class. When class variables are changed, they are changed universally for all instances that refer to them.

If no modifier is specified, variables are instance variables by default. Unlike class variables, all instance variables are copied and allocated memory by the Java runtime system each time an instance is created. An object's instance variables can be accessed only from an object--not from the class. If you want to access the instance variables of object A from your class, for example, you cannot refer to it directly. You must create a new object B that refers to it. The value of an instance variable is specific to the object. Other objects created from the same class can have different values assigned to their instance variables.

Member methods can be class methods or instance methods. Class methods also are declared using the static modifier. They have no access to the instance variables of the objects. They are invoked on the class and do not require any instance to be created in order to be called.

Instance methods, on the other hand, have access to the instance variables of the object, other objects, and the class variables of their class. They can be run only when an object is created.

You will want to use class variables in your code when you need only one copy of an item that must be accessible by all objects created from the class. Using class variables saves memory. You will want to use class methods for security reasons--to restrict access to the objects' instance variables.

Creating the Constructors and the Finalize() Methods


Constructors and finalize() methods are special methods you can use in your class. As you have learned, constructors are used to build objects when they are instantiated. You can use the finalize() method to destroy objects.

As you know, classes can store multiple constructors (all with the same name) with different arguments that are called when the new operator instantiates an object with parameters. If no parameters are passed, Java assigns a default constructor. The default constructor for the Rectangle class, for example, is Rectangle(). Constructors use the same access modifiers as methods and classes.

If you refer to the Java packages in the appendix, you will see that many of Java's built-in classes provide multiple constructors. The Java.awt.Rectangle class that you have been using in this chapter, for example, provides the following constructors:

public Rectangle();
public Rectangle(int x, int y, int width, int height);
public Rectangle(int width, int height);
public Rectangle(Point p, Dimension d);
public Rectangle(Point p);
public Rectangle(Dimension d);

In your class, you may want to create multiple constructors if you will be passing different parameters when creating new objects. The Java compiler decides which constructor to use based on the number and type of parameters passed. If you create a new rectangle with the following statement, for example, Java selects the second constructor:

new Rectangle(3, 3, 4, 2);

When you write your constructors, keep in mind that each name must be the same as the name of its class, and each constructor must have arguments that are different in number or in type. Unlike regular method declarations, you do not define return types.

Constructors are not limited to single-line declarations. They can declare variables and methods like a regular method does. Although they can look much like regular methods, you will be able to spot constructors when you read through Java code, because they do not specify a return type and have the same name as the class.

If you need to access the constructors in your Square class' superclass, for example, you can do so by using the keyword super before the declaration:

super.Rectangle()

This line invokes a constructor provided by Rectangle, which is the superclass of Square. Typically, the superclass constructor is invoked first in the subclass' constructor.

When objects no longer are needed in an application, they must be cleaned out of memory. Java provides an automatic Garbage Collector to find unused objects and reclaim their memory. The Garbage Collector runs a finalize() method just before clearing an object from memory. You can override Java's finalize() method in your code. This finalize() method, provided by the Java.lang.Object class, releases system resources, such as open files or open sockets, before the object is collected. The last section of this chapter describes in detail how garbage collection works. For now, it is enough to know that the Garbage Collector is responsible for automatically clearing unused objects from memory.

You have the option of using the Object class' finalize() method or overriding it by creating your own finalize() method for your class. The structure of a finalize() method declaration follows:

protected void finalize() throws throwable{
//statements
}

In the body of this special method, you will close files and sockets after determining that they are no longer in use.

To call a finalize() method specified in your superclass, precede the name finalize() with the keyword super and a period (.). It is a good idea to call the superclass' finalize() method after your class' finalize() method, in case the object has obtained resources through methods that it inherited. Such a finalize() method looks like this:

protected void finalize() throws Throwable {
// clean up statements
super.finalize();
}

Creating Interfaces


At this point, you know how to create classes, to define their member variables and methods, and to instantiate objects from them. You will be able to create some small applications with this knowledge. Java's strict class hierarchy rules limit a subclass you create, however, to inherit only from the classes in its hierarchy. To create more complex applications, you undoubtedly will need to inherit from classes outside your class' hierarchy. In Java, you access methods and variables from classes outside your class' hierarchy by implementing multiple interfaces in your class. These interfaces must be defined in the foreign classes that you are accessing. You also can make portions of your class available to other classes that cannot inherit from it by defining interfaces in your class.

Interfaces always are abstract. Their variables can be used only as constants; they always are static and final. Their methods are abstract and public. An interface declares a set of methods and constants without specifying the implementation for any of the methods. When a class implements an interface, it provides implementations for all the methods declared in the interface.

As you learned in Chapter 1, interfaces are implemented by a class in the class declaration:

class classname implements [interface-list] {
}

The form of an interface declaration for a class follows:

[public access-modifier] interface Interface-name extends [interface-list]{
     //methods and constants
}

The public access modifier is the only modifier supported in the latest release of Java for interfaces. It can be used before an interface declaration to allow all other classes and packages to use it. If public is not declared, only the classes within its package can use the interface. The keyword interface is followed by the name of the interface, which typically is capitalized. The extends interface-list part is similar to a class extending a superclass, but it can list multiple interfaces that it extends. This list is comma-delimited.

Like inheritance in a subclass, an interface inherits all constants and methods from the interfaces it extends unless the interface hides an inherited constant by declaring a variable of the same name or overrides a method with a new method declaration.

There is no need to use any of the following modifiers in an interface, because they are invalid:

volatile

Here is an example of an interface declaration:

interface Movable {
     void moveLeft(int x, int y);
     void moveRight(int x, int y);
}

This is an example of the implementation of the Movable() interface in a class:

class Rectangle implements Movable {
     public void moveLeft(3, 1) {
     //code for moving object
     }
}

Using the Garbage Collector


Now that you have learned to subclass objects, you might wonder how the memory they are allocated is managed. If you're a C++ developer, you might think that I missed some memory-management steps in my subclassing explanation. In Java, memory management is performed automatically with a utility called a Garbage Collector. When an object that has been instantiated by your application finishes performing its task, it is destroyed and Java's automatic Garbage Collector reclaims its memory.

The Java team made its most important improvement over C++ in automatic memory management and thread controls. Through the use of an automatic, threaded garbage-collection utility, Java removes the burden of memory management from the shoulders of the programmer yet retains high performance standards. Additionally, it eliminates the many bugs commonly caused by the use of pointers in C++ applications without sacrificing performance. This section describes what garbage collection is and how it uses multithreading to maintain the performance of your application.

Understanding Garbage Collection


Garbage collection is Java's answer to automatic memory management. This discussion on garbage collection begins by explaining why the management of memory in a multithreaded application should be automated, how Java automates explicit memory management tasks of C-type languages, and how garbage collection works.

In C and C++, creating multithreaded applications is possible through explicit memory management. Programmers manage memory by using memory-management libraries to allocate memory, free memory, and keep track of which memory is available to be freed and when. Explicit memory management has proved to be a common source of bugs, crashes, memory leaks, and performance degradation in C++ applications. The need to free programmers from the encumbrance of memory management is evidenced by the fact that most bugs in C++-type code are caused by misuse of pointers and freeing of objects that are allocated in memory. If memory management is automated, programmers can spend most of their time worrying about the functionality of their applications instead of wasting it by debugging memory problems.

Pointers, pointer arithmetic, malloc, and free, which are used for memory management in C++, automatically are incorporated into Java's environment. Pointers are replaced by references. As you discovered earlier in this chapter, Java has a new operator, which is used to allocate memory for objects. There is no free function, however, that a programmer can invoke in code to clean up the memory space when the object no longer is needed. There is, in fact, no need to deallocate or free memory explicitly in Java.

Java generally automates memory management by tracking the use of objects that are created as your application runs. The interpreter automatically marks objects to be freed from memory when they no longer are in use. Such automatic memory management is performed by Java's Garbage Collector.

The purpose of Java's Garbage Collector is to ensure that memory is available when it is needed. When the Garbage Collector executes, it searches, discovers, marks, clears, and compacts unused memory, increasing the likelihood that adequate memory resources are available when required by the user.

More specifically, when an object is instantiated from a class, it is given a unique reference, which is used by the Garbage Collector. The Garbage Collector sets the reference counter to 1 when the object is allocated. It keeps track of all the references to objects instantiated in an application by incrementing the counter each time an object is referenced and decrementing it when the reference is gone. The Garbage Collector searches for reference counters that are equal to 0, meaning that there are no more references to the object. After discovering that a counter is set to 0, the Garbage Collector marks the unused object for removal, making it a candidate for garbage collection.

Java ensures that certain objects integral to the system will never be freed, such as the Object class.

After an object is cleared from memory, it leaves a hole the size of the object that can be reused. The Java Garbage Collector searches memory for fragments and reorganizes it by compacting it. When the Garbage Collector compacts memory, it consolidates the objects that have references into a contiguous group, making one large area of unallocated memory available for use by new objects as necessary.

Java provides for situations in which a long chain of object references comes full circle, back to the originating reference, leaving the counts for unused objects at 1 and the memory uncleared. Java avoids this problem by marking root objects, searching all references to objects, marking them, searching those objects' references, and so on until no other references exist. The Garbage Collector then removes all unmarked objects and compacts memory.

The effect of compacting objects is that they are moved to different areas of memory. In Java, references to objects that have moved are not lost, because these references are not pointers to specific areas of memory but instead are handles that are maintained in an object index, which maps them to actual objects. When an object is moved, only its references in the object index must be repaired.

You might wonder how such activities possibly can run throughout the execution of an application without degrading performance. The following section explores that question.

Looking At the Garbage Collector's Effect on Performance


The Garbage Collector's processes, described in the last section, would normally be very CPU-intensive--incrementing and decrementing counters for vast quantities of references to objects, searching them for 0 values, compacting memory, and remapping references in the object index. Java's Garbage Collector is not CPU-intensive, partly because it provides some additional tricks, such as designating objects that do not need to be referenced and saving work for the Garbage Collector, but mostly due to the fact that it runs as a separate, low-priority thread.

The Java Garbage Collector takes advantage of the user's behavior when interacting with Java applications. When a user pauses while using an application, when the system pauses, or when the system requires the use of memory taken up by defunct classes, the Java runtime system runs the Garbage Collector in a low-priority background thread and frees unused objects from memory. Because the Garbage Collector utility is effective only because it runs in a multithreaded environment, this section briefly explains what multithreading means.

Multithreading is an innovation that makes applications more interactive and faster. Single-threaded applications only have the capability to execute one process at a time. While a process is executing, all other processes are stalled. Because every application is a separate process, you have the option of switching between applications in such an environment, pushing one application's processing to the foreground; this pauses all other applications, however. When a process polls the operating system for events and requests an event, the operating system checks to see whether any other processes are performing any event processing and gives them time to complete. The operating system then passes the event to the process and allocates time for it to execute. With several application processes given time to execute, the user perceives that they are running simultaneously. They are not executing their tasks simultaneously, however. If one of these processes is lengthy, it monopolizes the system's resources and other processes do not run.

Such an application would be fine for small tasks but would not be practical for Internet applications, which may need to run several applets at once or perform tasks while the user is doing something else.

Multithreaded applications have the capability to maintain multiple concurrent paths of execution. While the user is performing some action in an application, other applications can perform other tasks. These paths of execution are called threads.

Java is a multithreaded application. It balances thread synchronization between the class level (performed at runtime) and the language level (performed when code is written). The runtime Java interpreter runs the Garbage Collector as a low-priority background thread while executing an application's code without disturbing the performance of the application. You will see how to create multithreaded applications with the Java language in Chapter 16, "Multithreading with Java".

Java's automatic garbage collection makes programming in Java easier, eliminating many potential bugs that would arise if you managed memory explicitly. It generally provides better performance than you will find in most applications created with explicit memory management.

Summary


In this chapter, you learned to create objects, classes, and interfaces; and to declare their variables and methods. If you understand the concepts outlined in this chapter, you will have no problem moving onto the chapters in the rest of the book. In the rest of the chapters, you will have the opportunity to practice putting these concepts to work while creating actual applications.

Previous Page Page Top TOC Next Page See Page