Chapter 4 -- Creating Your Own Objects
Chapter 4
Creating Your Own Objects
CONTENTS
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.
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();
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.
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.
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);
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 do evaluate to some value. You can use
the value returned by a method in expressions or as variable values.
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.
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.
You create all the methods and variables of the class, 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.
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.
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.
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.
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.
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();
}
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:
private
protected
synchronized
transient
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
}
}
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.
Garbage collection is Java's answer to automatic memory management.
This section on garbage collection begins by explaining why the
management of memory in a multithreaded application should be
automated, and 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.
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.
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.
Next
Previous
Contents
|