Introducing classes:
The class is at the core of Java. It is the logical construct upon which the entire Java language is
built because it defines the shape and nature of an object. As such, the class forms the basis for
object-oriented programming in Java. Any concept we wish to implement in a Java program must
be encapsulated within a class.
Class fundamentals:
a class is a template for an object, and an object is an instance of a class. Because an object is an
instance of a class, we will often see the two words object and instance used interchangeably.
The General Form of a Class
When we define a class, we declare its exact form and nature. We do this by specifying the data
that it contains and the code that operates on that data. While very simple classes may contain only
code or only data, most real-world classes contain both. As we will see, a class’ code defines the
interface to its data.
A class is declared by use of the class keyword. The classes that have been used up to this point
are actually very limited examples of its complete form. Classes can (and usually do) get much
more complex. The general form of a class definition is shown here:
class classname {
type instance-variable1;
type instance-variable2;
// ...
type instance-variableN;
type methodname1(parameter-list) {
// body of method
}
type methodname2(parameter-list) {
// body of method
}
// ...
type methodnameN(parameter-list) {
// body of method
}
}
The data, or variables, defined within a class are called instance variables. The code is contained
within methods. Collectively, the methods and variables defined within a class are called members
of the class. In most classes, the instance variables are acted upon and accessed by the methods
defined for that class. Thus, it is the methods that determine how a class’ data can be used.
Variables defined within a class are called instance variables because each instance of the class
(that is, each object of the class) contains its own copy of these variables. Thus, the data for one
object is separate and unique from the data for another. We will come back to this point shortly,
but it is an important concept to learn early.
All methods have the same general form as main( ), which we have been using thus far. However,
most methods will not be specified as static or public. Notice that the general form of a class does
not specify a main( ) method. Java classes do not need to have a main( ) method. We only specify
one if that class is the starting point for your program. Further, applets don’t require a main( )
method at all.
A Simple Class
Here is a class called Box that defines three instance variables: width, height, and depth. Currently,
Box does not contain any methods (but some will be added later).
class Box {
double width;
double height;
double depth;
}
As stated, a class defines a new type of data. In this case, the new data type is called Box. We will
use this name to declare objects of type Box. It is important to remember that a class declaration
only creates a template; it does not create an actual object. Thus, the preceding code does not cause
any objects of type Box to come into existence. To actually create a Box object, we will use a
statement like the following:
Box mybox = new Box(); // create a Box object called mybox
After this statement executes, mybox will be an instance of Box. Thus, it will have “physical”
reality. For the moment, don’t worry about the details of this statement. Again, each time we create
an instance of a class, we are creating an object that contains its own copy of each instance variable
defined by the class. Thus, every Box object will contain its own copies of the instance variables
width, height, and depth. To access these variables, we will use the dot (.) operator. The dot
operator links the name of the object with the name of an instance variable. For example, to assign
the width variable of mybox the value 100, we would use the following statement:
mybox.width = 100;
This statement tells the compiler to assign the copy of width that is contained within the mybox
object the value of 100. In general, we use the dot operator to access both the instance variables
and the methods within an object. Here is a complete program that uses the Box class:
/* A program that uses the Box class. Call this file BoxDemo.java */
class Box {
double width;
double height;
double depth;
}
// This class declares an object of type Box.
class BoxDemo {
public static void main(String args[]) {
Box mybox = new Box();
double vol;
// assign values to mybox's instance variables
mybox.width = 10;
mybox.height = 20;
mybox.depth = 15;
// compute volume of box
vol = mybox.width * mybox.height * mybox.depth;
System.out.println("Volume is " + vol);
}
}
We should call the file that contains this program BoxDemo.java, because the main( ) method is
in the class called BoxDemo, not the class called Box. When we compile this program, we will
find that two .class files have been created, one for Box and one for BoxDemo. The Java compiler
automatically puts each class into its own .class file. It is not necessary for both the Box and the
BoxDemo class to actually be in the same source file. We could put each class in its own file,
called Box.java and BoxDemo.java, respectively. To run this program, we must execute
BoxDemo.class. When we do, we will see the following output:
Volume is 3000.0
As stated earlier, each object has its own copies of the instance variables. This means that if we
have two Box objects, each has its own copy of depth, width, and height. It is important to
understand that changes to the instance variables of one object have no effect on the instance
variables of another. For example, the following program declares two Box objects:
// This program declares two Box objects.
class Box {
double width;
double height;
double depth;
}
class BoxDemo2 {
public static void main(String args[]) {
Box mybox1 = new Box();
Box mybox2 = new Box();
THE JAVA LANGUAGE
double vol;
// assign values to mybox1's instance variables
mybox1.width = 10;
mybox1.height = 20;
mybox1.depth = 15;
/* assign different values to mybox2's
instance variables */
mybox2.width = 3;
mybox2.height = 6;
mybox2.depth = 9;
// compute volume of first box
vol = mybox1.width * mybox1.height * mybox1.depth;
System.out.println("Volume is " + vol);
// compute volume of second box
vol = mybox2.width * mybox2.height * mybox2.depth;
System.out.println("Volume is " + vol);
}
}
The output produced by this program is shown here:
Volume is 3000.0
Volume is 162.0
As we can see, mybox1’s data is completely separate from the data contained in mybox2.
Declaring Objects:
when we create a class, we are creating a new data type. We can use this type to declare objects of
that type. However, obtaining objects of a class is a two-step process. First, we must declare a
variable of the class type. This variable does not define an object. Instead, it is simply a variable
that can refer to an object. Second, we must acquire an actual, physical copy of the object and
assign it to that variable. We can do this using the new operator. The new operator dynamically
allocates (that is, allocates at run time) memory for an object and returns a reference to it. This
reference is, more or less, the address in memory of the object allocated by new. This reference is
then stored in the variable. Thus, in Java, all class objects must be dynamically allocated. Let’s
look at the details of this procedure. In the preceding sample programs, a line similar to the
following is used to declare an object of type Box:
Box mybox = new Box();
This statement combines the two steps just described. It can be rewritten like this to show each
step more clearly:
Box mybox; // declare reference to object
mybox = new Box(); // allocate a Box object
The first line declares mybox as a reference to an object of type Box. After this line executes,
mybox contains the value null, which indicates that it does not yet point to an actual object. Any
attempt to use mybox at this point will result in a compile-time error. The next line allocates an
actual object and assigns a reference to it to mybox. After the second line executes, we can use
mybox as if it were a Box object. But in reality, mybox simply holds the memory address of the
actual Box object. The effect of these two lines of code is depicted in the following Figure
Assigning Object Reference Variables:
Object reference variables act differently than we might expect when an assignment takes place.
For example, what do we think the following fragment does?
Box b1 = new Box();
Box b2 = b1;
We might think that b2 is being assigned a reference to a copy of the object referred to by b1. That
is, we might think that b1 and b2 refer to separate and distinct objects. However, this would be
wrong. Instead, after this fragment executes, b1 and b2 will both refer to the same object. The
assignment of b1 to b2 did not allocate any memory or copy any part of the original object. It
simply makes b2 refer to the same object as does b1. Thus, any changes made to the object through
b2 will affect the object to which b1 is referring, since they are the same object. This situation is
depicted here:
Although b1 and b2 both refer to the same object, they are not linked in any other way. For
example, a subsequent assignment to b1 will simply unhook b1 from the original object without
affecting the object or affecting b2.
For example:
Box b1 = new Box();
Box b2 = b1;
// ...
b1 = null;
Here, b1 has been set to null, but b2 still points to the original object.
Introducing Methods:
This is the general form of a method:
type name(parameter-list) {
// body of method
}
Here, type specifies the type of data returned by the method. This can be any valid type, including
class types that we create. If the method does not return a value, its return type must be void. The
name of the method is specified by name. This can be any legal identifier other than those already
used by other items within the current scope. The parameter-list is a sequence of type and identifier
pairs separated by commas. Parameters are essentially variables that receive the value of the
arguments passed to the method when it is called. If the method has no parameters, then the
parameter list will be empty. Methods that have a return type other than void return a value to the
calling routine using the following form of the return statement:
return value;
Adding a Method to the Box Class
Let’s begin by adding a method to the Box class. It may have occurred to us while looking at the
preceding programs that the computation of a box’s volume was something that was best handled
by the Box class rather than the BoxDemo class. After all, since the volume of a box is dependent
upon the size of the box, it makes sense to have the Box class compute it. To do this, we must add
a method to Box, as shown here:
// This program includes a method inside the box class.
class Box {
double width;
double height;
double depth;
// display volume of a box
void volume() {
System.out.print("Volume is ");
System.out.println(width * height * depth);
}
}
class BoxDemo3 {
public static void main(String args[]) {
Box mybox1 = new Box();
Box mybox2 = new Box();
// assign values to mybox1's instance variables
mybox1.width = 10;
mybox1.height = 20;
mybox1.depth = 15;
/* assign different values to mybox2's
instance variables */
mybox2.width = 3;
mybox2.height = 6;
mybox2.depth = 9;
// display volume of first box
mybox1.volume();
// display volume of second box
mybox2.volume();
}
}
Output:
Volume is 3000.0
Volume is 162.0
Look closely at the following two lines of code:
mybox1.volume();
mybox2.volume();
The first line here invokes the volume( ) method on mybox1. That is, it calls volume( ) relative to
the mybox1 object, using the object’s name followed by the dot operator. Thus, the call to
mybox1.volume( ) displays the volume of the box defined by mybox1, and the call to
mybox2.volume( ) displays the volume of the box defined by mybox2. Each time volume( ) is
invoked, it displays the volume for the specified box. When mybox1.volume( ) is executed, the
Java run-time system transfers control to the code defined inside volume( ). After the statements
inside volume( ) have executed, control is returned to the calling routine, and execution resumes
with the line of code following the call. In the most general sense, a method is Java’s way of
implementing subroutines. There is something very important to notice inside the volume( )
method: the instance variables width, height, and depth are referred to directly, without preceding
them with an object name or the dot operator. When a method uses an instance variable that is
defined by its class, it does so directly, without explicit reference to an object and without use of
the dot operator. This is easy to understand if we think about it. A method is always invoked
relative to some object of its class. Once this invocation has occurred, the object is known. Thus,
within a method, there is no need to specify the object a second time. This means that width, height,
and depth inside volume( ) implicitly refer to the copies of those variables found in the object that
invokes volume( ).
Returning a Value
While the implementation of volume( ) does move the computation of a box’s volume inside the
Box class where it belongs, it is not the best way to do it. For example, what if another part of our
program wanted to know the volume of a box, but not display its value? A better way to implement
volume( ) is to have it compute the volume of the box and return the result to the caller. The
following example, an improved version of the preceding program, does just that:
// Now, volume() returns the volume of a box.
class Box {
double width;
double height;
double depth;
// compute and return volume
double volume() {
return width * height * depth;
}
}
class BoxDemo4 {
public static void main(String args[]) {
Box mybox1 = new Box();
Box mybox2 = new Box();
double vol;
// assign values to mybox1's instance variables
mybox1.width = 10;
mybox1.height = 20;
mybox1.depth = 15;
/* assign different values to mybox2's
instance variables */
mybox2.width = 3;
mybox2.height = 6;
mybox2.depth = 9;
// get volume of first box
vol = mybox1.volume();
System.out.println("Volume is " + vol);
// get volume of second box
vol = mybox2.volume();
System.out.println("Volume is " + vol);
}
}
As we can see, when volume( ) is called, it is put on the right side of an assignment statement. On
the left is a variable, in this case vol, that will receive the value returned by volume( ). Thus, after
vol = mybox1.volume();
executes, the value of mybox1.volume( ) is 3,000 and this value then is stored in vol. There are
two important things to understand about returning values:
■ The type of data returned by a method must be compatible with the return type specified by the
method. For example, if the return type of some method is boolean, we could not return an integer.
■ The variable receiving the value returned by a method (such as vol, in this case) must also be
compatible with the return type specified for the method.
One more point: The preceding program can be written a bit more efficiently because there is
actually no need for the vol variable. The call to volume( ) could have been used in the println( )
statement directly, as shown here:
System.out.println("Volume is " + mybox1.volume());
In this case, when println( ) is executed, mybox1.volume( ) will be called automatically and its
value will be passed to println( ).
Adding a Method That Takes Parameters
While some methods don’t need parameters, most do. Parameters allow a method to be
generalized. That is, a parameterized method can operate on a variety of data and/or be used in a
number of slightly different situations. To illustrate this point, let’s use a very simple example.
Here is a method that returns the square of the number 10:
int square()
{
return 10 * 10;
}
While this method does, indeed, return the value of 10 squared, its use is very limited. However,
if we modify the method so that it takes a parameter, as shown next, then we can make square( )
much more useful.
int square(int i)
{
return i * i;
}
Now, square( ) will return the square of whatever value it is called with. That is, square( ) is now
a general-purpose method that can compute the square of any integer value, rather than just 10.
Here is an example:
int x, y;
x = square(5); // x equals 25
x = square(9); // x equals 81
y = 2;
x = square(y); // x equals 4
In the first call to square( ), the value 5 will be passed into parameter i. In the second call, i will
receive the value 9. The third invocation passes the value of y, which is 2 in this example. As these
examples show, square( ) is able to return the square of whatever data it is passed. It is important
to keep the two terms parameter and argument straight. A parameter is a variable defined by a
method that receives a value when the method is called.
For example, in square( ), i is a parameter. An argument is a value that is passed to a method when
it is invoked. For example, square(100) passes 100 as an argument. Inside square( ), the parameter
i receives that value. We can use a parameterized method to improve the Box class. In the
preceding examples, the dimensions of each box had to be set separately by use of a sequence of
statements, such as:
mybox1.width = 10;
mybox1.height = 20;
mybox1.depth = 15;
While this code works, it is troubling for two reasons. First, it is clumsy and error prone. For
example, it would be easy to forget to set a dimension. Second, in well-designed Java programs,
instance variables should be accessed only through methods defined by their class. In the future,
we can change the behavior of a method, but we can’t change the behavior of an exposed instance
variable. Thus, a better approach to setting the dimensions of a box is to create a method that takes
the dimension of a box in its parameters and sets each instance variable appropriately. This concept
is implemented by the following program:
// This program uses a parameterized method.
class Box {
double width;
double height;
double depth;
// compute and return volume
double volume() {
return width * height * depth;
}
// sets dimensions of box
void setDim(double w, double h, double d) {
width = w;
height = h;
depth = d;
}
}
class BoxDemo5 {
public static void main(String args[]) {
Box mybox1 = new Box();
Box mybox2 = new Box();
double vol;
// initialize each box
mybox1.setDim(10, 20, 15);
mybox2.setDim(3, 6, 9);
// get volume of first box
vol = mybox1.volume();
System.out.println("Volume is " + vol);
// get volume of second box
vol = mybox2.volume();
System.out.println("Volume is " + vol);
}
}
As we can see, the setDim( ) method is used to set the dimensions of each box. For example, when
mybox1.setDim(10, 20, 15);
is executed, 10 is copied into parameter w, 20 is copied into h, and 15 is copied into d. Inside
setDim( ) the values of w, h, and d are then assigned to width, height, and depth, respectively.
What are Constructors? What are the Characteristics of constructors?
It can be tedious to initialize all of the variables in a class each time an instance is created. Even
when we add convenience functions like setDim( ), it would be simpler and more concise to have
all of the setup done at the time the object is first created. Because the requirement for initialization
is so common, Java allows objects to initialize themselves when they are created. This automatic
initialization is performed through the use of a constructor.
A constructor initializes an object immediately upon creation. It has the same name as the class in
which it resides and is syntactically similar to a method. Once defined, the constructor is
automatically called immediately after the object is created, before the new operator completes.
Constructors look a little strange because they have no return type, not even void. This is because
the implicit return type of a class’ constructor is the class type itself. It is the constructor’s job to
initialize the internal state of an object so that the code creating an instance will have a fully
initialized, usable object immediately. We can rework the Box example so that the dimensions of
a box are automatically initialized when an object is constructed. To do so, replace setDim( ) with
a constructor. Let’s begin by defining a simple constructor that simply sets the dimensions of each
box to the same values.
This version is shown here:
/* Here, Box uses a constructor to initialize the dimensions of a box.*/
class Box {
double width;
double height;
double depth;
// This is the constructor for Box.
Box() {
System.out.println("Constructing Box");
width = 10;
height = 10;
depth = 10;
}
// compute and return volume
double volume() {
return width * height * depth;
}
}
class BoxDemo6 {
public static void main(String args[]) {
// declare, allocate, and initialize Box objects
Box mybox1 = new Box();
Box mybox2 = new Box();
double vol;
// get volume of first box
vol = mybox1.volume();
System.out.println("Volume is " + vol);
// get volume of second box
vol = mybox2.volume();
System.out.println("Volume is " + vol);
}
}
When this program is run, it generates the following results:
Constructing Box
Constructing Box
Volume is 1000.0
Volume is 1000.0
As we can see, both mybox1 and mybox2 were initialized by the Box( ) constructor when they
were created. Since the constructor gives all boxes the same dimensions, 10 by 10 by 10, both
mybox1 and mybox2 will have the same volume. The println( ) statement inside Box( ) is for the
sake of illustration only. Most constructors will not display anything. They will simply initialize
an object. Before moving on, let’s reexamine the new operator. As we know, when we allocate an
object, we use the following general form:
class-var = new classname( );
Now we can understand why the parentheses are needed after the class name. What is actually
happening is that the constructor for the class is being called. Thus, in the line
Box mybox1 = new Box();
new Box( ) is calling the Box( ) constructor. When we do not explicitly define a constructor for a
class, then Java creates a default constructor for the class. This is why the preceding line of code
worked in earlier versions of Box that did not define a constructor. The default constructor
automatically initializes all instance variables to zero. The default constructor is often sufficient
for simple classes, but it usually won’t do for more sophisticated ones. Once we define our own
constructor, the default constructor is no longer used.
Parameterized Constructors
While the Box( ) constructor in the preceding example does initialize a Box object, it is not very
useful—all boxes have the same dimensions. What is needed is a way to construct Box objects of
various dimensions. The easy solution is to add parameters to the constructor. As we can probably
guess, this makes them much more useful. For example, the following version of Box defines a
parameterized constructor which sets the dimensions of a box as specified by those parameters.
Pay special attention to how Box objects are created.
/* Here, Box uses a parameterized constructor to initialize the dimensions of a box.*/
class Box {
double width;
double height;
double depth;
// This is the constructor for Box.
Box(double w, double h, double d) {
width = w;
height = h;
depth = d;
}
// compute and return volume
double volume() {
return width * height * depth;
}
}
class BoxDemo7 {
public static void main(String args[]) {
// declare, allocate, and initialize Box objects
Box mybox1 = new Box(10, 20, 15);
Box mybox2 = new Box(3, 6, 9);
double vol;
// get volume of first box
vol = mybox1.volume();
System.out.println("Volume is " + vol);
// get volume of second box
vol = mybox2.volume();
System.out.println("Volume is " + vol);
}
}
The output from this program is shown here:
Volume is 3000.0
Volume is 162.0
As we can see, each object is initialized as specified in the parameters to its constructor. For
example, in the following line,
Box mybox1 = new Box(10, 20, 15);
the values 10, 20, and 15 are passed to the Box( ) constructor when new creates the object. Thus,
mybox1’s copy of width, height, and depth will contain the values 10, 20, and 15, respectively.
The this Keyword
Sometimes a method will need to refer to the object that invoked it. To allow this, Java defines the
this keyword. this can be used inside any method to refer to the current object. That is, this is
always a reference to the object on which the method was invoked. We can use this anywhere a
reference to an object of the current class’ type is permitted. To better understand what this refers
to, consider the following version of Box( ):
// A redundant use of this.
Box(double w, double h, double d) {
this.width = w;
this.height = h;
this.depth = d;
}
This version of Box( ) operates exactly like the earlier version. The use of this is redundant,
but perfectly correct. Inside Box( ), this will always refer to the invoking object.
Lab 4: Classes and Objects
Introduction to Garbage Collection
Since objects are dynamically allocated by using the new operator, we might be wondering how
such objects are destroyed and their memory released for later reallocation. In some languages,
such as C++, dynamically allocated objects must be manually released by use of a delete operator.
Java takes a different approach; it handles deallocation for we automatically. The technique that
accomplishes this is called garbage collection. It works like this: when no references to an object
exist, that object is assumed to be no longer needed, and the memory occupied by the object can
be reclaimed. There is no explicit need to destroy objects as in C++. Garbage collection only occurs
sporadically (if at all) during the execution of our program. It will not occur simply because one
or more objects exist that are no longer used. Furthermore, different Java run-time implementations
will take varying approaches to garbage collection, but for the most part, we should not have to
think about it while writing our programs.
Using Finalize() method
Sometimes an object will need to perform some action when it is destroyed. For example, if an
object is holding some non-Java resource such as a file handle or window character font, then we
might want to make sure these resources are freed before an object is destroyed. To handle such
situations, Java provides a mechanism called finalization. By using finalization, we can define
specific actions that will occur when an object is just about to be reclaimed by the garbage
collector.
To add a finalizer to a class, we simply define the finalize( ) method. The Java run time calls that
method whenever it is about to recycle an object of that class. Inside the finalize( ) method we will
specify those actions that must be performed before an object is destroyed. The garbage collector
runs periodically, checking for objects that are no longer referenced by any running state or
indirectly through other referenced objects. Right before an asset is freed, the Java run time calls
the finalize( ) method on the object.
The finalize( ) method has this general form:
protected void finalize( )
{
// finalization code here
}
Here, the keyword protected is a specifier that prevents access to finalize( ) by code defined outside
its class. It is important to understand that finalize( ) is only called just prior to garbage collection.
It is not called when an object goes out-of-scope, for example. This means that we cannot know
when—or even if—finalize( ) will be executed. Therefore, our program should provide other
means of releasing system resources, etc., used by the object. It must not rely on finalize( ) for
normal program operation.
Overloading methods
In Java it is possible to define two or more methods within the same class that share the same
name, as long as their parameter declarations are different. When this is the case, the methods are
said to be overloaded, and the process is referred to as method overloading. Method overloading
is one of the ways that Java implements polymorphism.
When an overloaded method is invoked, Java uses the type and/or number of arguments as its
guide to determine which version of the overloaded method to actually call. Thus, overloaded
methods must differ in the type and/or number of their parameters. While overloaded methods may
have different return types, the return type alone is insufficient to distinguish two versions of a
method. When Java encounters a call to an overloaded method, it simply executes the version of
the method whose parameters match the arguments used in the call. Here is a simple example that
illustrates method overloading:
// Demonstrate method overloading.
class OverloadDemo {
void test() {
System.out.println("No parameters");
}
// Overload test for one integer parameter.
void test(int a) {
System.out.println("a: " + a);
}
// Overload test for two integer parameters.
void test(int a, int b) {
System.out.println("a and b: " + a + " " + b);
}
// overload test for a double parameter
double test(double a) {
System.out.println("double a: " + a);
return a*a;
}
}
class Overload {
public static void main(String args[]) {
OverloadDemo ob = new OverloadDemo();
double result;
// call all versions of test()
ob.test();
ob.test(10);
ob.test(10, 20);
result = ob.test(123.25);
System.out.println("Result of ob.test(123.25): " + result);
}
}
This program generates the following output:
No parameters
a: 10
a and b: 10 20
double a: 123.25
Result of ob.test(123.25): 15190.5625
As we can see, test( ) is overloaded four times. The first version takes no parameters, the second
takes one integer parameter, the third takes two integer parameters, and the fourth takes one double
parameter. The fact that the fourth version of test( ) also returns a value is of no consequence
relative to overloading, since return types do not play a role in overload resolution.
When an overloaded method is called, Java looks for a match between the arguments used to call
the method and the method’s parameters. However, this match need not always be exact. In some
cases Java’s automatic type conversions can play a role in overload resolution. For example,
consider the following program:
// Automatic type conversions apply to overloading.
class OverloadDemo {
void test() {
System.out.println("No parameters");
}
// Overload test for two integer parameters.
void test(int a, int b) {
System.out.println("a and b: " + a + " " + b);
}
// overload test for a double parameter
void test(double a) {
System.out.println("Inside test(double) a: " + a);
}
}
class Overload {
public static void main(String args[]) {
OverloadDemo ob = new OverloadDemo();
int i = 88;
ob.test();
ob.test(10, 20);
ob.test(i); // this will invoke test(double)
ob.test(123.2); // this will invoke test(double)
}
}
This program generates the following output:
No parameters
a and b: 10 20
Inside test(double) a: 88
Inside test(double) a: 123.2
As we can see, this version of OverloadDemo does not define test(int). Therefore, when test( ) is
called with an integer argument inside Overload, no matching method is found. However, Java can
automatically convert an integer into a double, and this conversion can be used to resolve the call.
Therefore, after test(int) is not found, Java elevates i to double and then calls test(double). Of
course, if test(int) had been defined, it would have been called instead. Java will employ its
automatic type conversions only if no exact match is found.
Method overloading supports polymorphism because it is one way that Java implements the “one
interface, multiple methods” paradigm. To understand how, consider the following. In languages
that do not support method overloading, each method must be given a unique name. However,
frequently we will want to implement essentially the same method for different types of data.
Consider the absolute value function. In languages that do not support overloading, there are
usually three or more versions of this function, each with a slightly different name.
For instance, in C, the function abs( ) returns the absolute value of an integer, labs( ) returns the
absolute value of a long integer, and fabs( ) returns the absolute value of a floating-point value.
Since C does not support overloading, each function has to have its own name, even though all
three functions do essentially the same thing. This makes the situation more complex,
conceptually, than it actually is. Although the underlying concept of each function is the same, we
still have three names to remember. This situation does not occur in Java, because each absolute
value method can use the same name. Indeed, Java’s standard class library includes an absolute
value method, called abs( ). This method is overloaded by Java’s Math class to handle all numeric
types. Java determines which version of abs( ) to call based upon the type of argument.
The value of overloading is that it allows related methods to be accessed by use of a common
name. Thus, the name abs represents the general action which is being performed. It is left to the
compiler to choose the right specific version for a particular circumstance. We, the programmer,
need only remember the general operation being performed. Through the application of
polymorphism, several names have been reduced to one. Although this example is fairly simple,
if we expand the concept, we can see how overloading can help we manage greater complexity.
When we overload a method, each version of that method can perform any activity we desire.
There is no rule stating that overloaded methods must relate to one another. However, from a
stylistic point of view, method overloading implies a relationship. Thus, while we can use the same
name to overload unrelated methods, we should not. For example, we could use the name sqr to
create methods that return the square of an integer and the square root of a floating-point value.
But these two operations are fundamentally different. Applying method overloading in this manner
defeats its original purpose. In practice, we should only overload closely related operations.
Overloading constructors
In addition to overloading normal methods, we can also overload constructor methods.
class Box {
double width;
double height;
double depth;
// This is the constructor for Box.
Box(double w, double h, double d) {
width = w;
height = h;
depth = d;
}
// compute and return volume
double volume() {
return width * height * depth;
}
}
As we can see, the Box( ) constructor requires three parameters. This means that all declarations
of Box objects must pass three arguments to the Box( ) constructor. For example, the following
statement is currently invalid:
Box ob = new Box();
Since Box( ) requires three arguments, it’s an error to call it without them. This raises some
important questions. What if we simply wanted a box and did not care (or know) what its initial
dimensions were? Or, what if we want to be able to initialize a cube by specifying only one value
that would be used for all three dimensions? As the Box class is currently written, these other
options are not available to us. Fortunately, the solution to these problems is quite easy: simply
overload the Box constructor so that it handles the situations just described. Here is a program that
contains an improved version of Box that does just that:
/* Here, Box defines three constructors to initialize the dimensions of a box various ways.*/
class Box {
double width;
double height;
double depth;
// constructor used when all dimensions specified
Box(double w, double h, double d) {
width = w;
height = h;
depth = d;
}
// constructor used when no dimensions specified
Box() {
width = -1; // use -1 to indicate
height = -1; // an uninitialized
depth = -1; // box
}
// constructor used when cube is created
Box(double len) {
width = height = depth = len;
}
// compute and return volume
double volume() {
return width * height * depth;
}
}
class OverloadCons {
public static void main(String args[]) {
// create boxes using the various constructors
Box mybox1 = new Box(10, 20, 15);
Box mybox2 = new Box();
Box mycube = new Box(7);
double vol;
// get volume of first box
vol = mybox1.volume();
System.out.println("Volume of mybox1 is " + vol);
// get volume of second box
vol = mybox2.volume();
System.out.println("Volume of mybox2 is " + vol);
// get volume of cube
vol = mycube.volume();
System.out.println("Volume of mycube is " + vol);
}
}
The output produced by this program is shown here:
Volume of mybox1 is 3000.0
Volume of mybox2 is -1.0
Volume of mycube is 343.0

More Related Content

PDF
Class notes(week 6) on inheritance and multiple inheritance
PPSX
Oop features java presentationshow
PPS
Introduction to class in java
PPT
JAVA CONCEPTS
DOCX
Advanced data structures using c++ 3
PPT
Chapter 4 - Defining Your Own Classes - Part I
PPT
Chapter 7 - Defining Your Own Classes - Part II
PDF
Lect 1-java object-classes
Class notes(week 6) on inheritance and multiple inheritance
Oop features java presentationshow
Introduction to class in java
JAVA CONCEPTS
Advanced data structures using c++ 3
Chapter 4 - Defining Your Own Classes - Part I
Chapter 7 - Defining Your Own Classes - Part II
Lect 1-java object-classes

What's hot (20)

PDF
Java unit2
PDF
Programming in Java: Combining Objects
PPT
Java: Objects and Object References
PPTX
Unit ii
PPTX
Python advance
PPTX
Class introduction in java
PPT
Classes & objects new
PDF
CLASSES, STRUCTURE,UNION in C++
PPT
Chapter 2 - Getting Started with Java
PPT
Object and Classes in Java
PPTX
Only oop
PPTX
Jscript part2
PDF
ITFT-Classes and object in java
PPTX
Ios development
PDF
Euclideus_Language
PDF
Built in classes in java
PPT
Chapter 8 - Exceptions and Assertions Edit summary
Java unit2
Programming in Java: Combining Objects
Java: Objects and Object References
Unit ii
Python advance
Class introduction in java
Classes & objects new
CLASSES, STRUCTURE,UNION in C++
Chapter 2 - Getting Started with Java
Object and Classes in Java
Only oop
Jscript part2
ITFT-Classes and object in java
Ios development
Euclideus_Language
Built in classes in java
Chapter 8 - Exceptions and Assertions Edit summary
Ad

Similar to java classes (20)

PPTX
OOP with Java - Introducing Classes - Module-2.pptx
PPTX
JAVA Module 2jdhgsjdsjdjsddsjjssdjdsjhsdjh.pptx
PPTX
java module 2java module 2java module 2java module 2
PPTX
JAVA Module 2____________________--.pptx
PPT
5.CLASSES.ppt(MB)2022.ppt .
PDF
principles of proramming language in cppg
PPTX
Chapter4.pptxdgdhgfshsfhtgjsjryjusryjryjursyj
PPTX
Class object method constructors in java
PPTX
Chapter 7 java
PPTX
JAVA Module 2 ppt on classes and objects and along with examples
PPTX
unit 2 java.pptx
PDF
Java-Module 3-PPT-TPS.pdf.Java-Module 3-PPT-TPS.pdf
PPTX
Mpl 9 oop
PPTX
Yes it's work soooon the have complete pptx in C#
PPT
class01.ppt | class01.ppt|class01.ppt | class01.ppt
PPT
packages and interfaces
PPTX
PPTX
Ch-2ppt.pptx
PPT
Lecture 2 classes i
PPT
Unidad o informatica en ingles
OOP with Java - Introducing Classes - Module-2.pptx
JAVA Module 2jdhgsjdsjdjsddsjjssdjdsjhsdjh.pptx
java module 2java module 2java module 2java module 2
JAVA Module 2____________________--.pptx
5.CLASSES.ppt(MB)2022.ppt .
principles of proramming language in cppg
Chapter4.pptxdgdhgfshsfhtgjsjryjusryjryjursyj
Class object method constructors in java
Chapter 7 java
JAVA Module 2 ppt on classes and objects and along with examples
unit 2 java.pptx
Java-Module 3-PPT-TPS.pdf.Java-Module 3-PPT-TPS.pdf
Mpl 9 oop
Yes it's work soooon the have complete pptx in C#
class01.ppt | class01.ppt|class01.ppt | class01.ppt
packages and interfaces
Ch-2ppt.pptx
Lecture 2 classes i
Unidad o informatica en ingles
Ad

Recently uploaded (20)

PPTX
ENGlishGrade8_Quarter2_WEEK1_LESSON1.pptx
PPTX
Diploma pharmaceutics notes..helps diploma students
PDF
BSc-Zoology-02Sem-DrVijay-Comparative anatomy of vertebrates.pdf
PPTX
principlesofmanagementsem1slides-131211060335-phpapp01 (1).ppt
PPT
hsl powerpoint resource goyloveh feb 07.ppt
PPTX
ACFE CERTIFICATION TRAINING ON LAW.pptx
PPTX
CHROMIUM & Glucose Tolerance Factor.pptx
PDF
FYJC - Chemistry textbook - standard 11.
PDF
WHAT NURSES SAY_ COMMUNICATION BEHAVIORS ASSOCIATED WITH THE COMP.pdf
PPTX
Neurology of Systemic disease all systems
PPTX
Theoretical for class.pptxgshdhddhdhdhgd
PPTX
Key-Features-of-the-SHS-Program-v4-Slides (3) PPT2.pptx
PPTX
Neurological complocations of systemic disease
PPSX
namma_kalvi_12th_botany_chapter_9_ppt.ppsx
PDF
Kalaari-SaaS-Founder-Playbook-2024-Edition-.pdf
PDF
Laparoscopic Imaging Systems at World Laparoscopy Hospital
PDF
GIÁO ÁN TIẾNG ANH 7 GLOBAL SUCCESS (CẢ NĂM) THEO CÔNG VĂN 5512 (2 CỘT) NĂM HỌ...
PDF
Health aspects of bilberry: A review on its general benefits
PPTX
PAIN PATHWAY & MANAGEMENT OF ACUTE AND CHRONIC PAIN SPEAKER: Dr. Rajasekhar ...
PDF
anganwadi services for the b.sc nursing and GNM
ENGlishGrade8_Quarter2_WEEK1_LESSON1.pptx
Diploma pharmaceutics notes..helps diploma students
BSc-Zoology-02Sem-DrVijay-Comparative anatomy of vertebrates.pdf
principlesofmanagementsem1slides-131211060335-phpapp01 (1).ppt
hsl powerpoint resource goyloveh feb 07.ppt
ACFE CERTIFICATION TRAINING ON LAW.pptx
CHROMIUM & Glucose Tolerance Factor.pptx
FYJC - Chemistry textbook - standard 11.
WHAT NURSES SAY_ COMMUNICATION BEHAVIORS ASSOCIATED WITH THE COMP.pdf
Neurology of Systemic disease all systems
Theoretical for class.pptxgshdhddhdhdhgd
Key-Features-of-the-SHS-Program-v4-Slides (3) PPT2.pptx
Neurological complocations of systemic disease
namma_kalvi_12th_botany_chapter_9_ppt.ppsx
Kalaari-SaaS-Founder-Playbook-2024-Edition-.pdf
Laparoscopic Imaging Systems at World Laparoscopy Hospital
GIÁO ÁN TIẾNG ANH 7 GLOBAL SUCCESS (CẢ NĂM) THEO CÔNG VĂN 5512 (2 CỘT) NĂM HỌ...
Health aspects of bilberry: A review on its general benefits
PAIN PATHWAY & MANAGEMENT OF ACUTE AND CHRONIC PAIN SPEAKER: Dr. Rajasekhar ...
anganwadi services for the b.sc nursing and GNM

java classes

  • 1. Introducing classes: The class is at the core of Java. It is the logical construct upon which the entire Java language is built because it defines the shape and nature of an object. As such, the class forms the basis for object-oriented programming in Java. Any concept we wish to implement in a Java program must be encapsulated within a class. Class fundamentals: a class is a template for an object, and an object is an instance of a class. Because an object is an instance of a class, we will often see the two words object and instance used interchangeably. The General Form of a Class When we define a class, we declare its exact form and nature. We do this by specifying the data that it contains and the code that operates on that data. While very simple classes may contain only code or only data, most real-world classes contain both. As we will see, a class’ code defines the interface to its data. A class is declared by use of the class keyword. The classes that have been used up to this point are actually very limited examples of its complete form. Classes can (and usually do) get much more complex. The general form of a class definition is shown here: class classname { type instance-variable1; type instance-variable2; // ... type instance-variableN; type methodname1(parameter-list) { // body of method } type methodname2(parameter-list) { // body of method } // ... type methodnameN(parameter-list) { // body of method } } The data, or variables, defined within a class are called instance variables. The code is contained within methods. Collectively, the methods and variables defined within a class are called members of the class. In most classes, the instance variables are acted upon and accessed by the methods defined for that class. Thus, it is the methods that determine how a class’ data can be used. Variables defined within a class are called instance variables because each instance of the class (that is, each object of the class) contains its own copy of these variables. Thus, the data for one object is separate and unique from the data for another. We will come back to this point shortly, but it is an important concept to learn early. All methods have the same general form as main( ), which we have been using thus far. However, most methods will not be specified as static or public. Notice that the general form of a class does not specify a main( ) method. Java classes do not need to have a main( ) method. We only specify
  • 2. one if that class is the starting point for your program. Further, applets don’t require a main( ) method at all. A Simple Class Here is a class called Box that defines three instance variables: width, height, and depth. Currently, Box does not contain any methods (but some will be added later). class Box { double width; double height; double depth; } As stated, a class defines a new type of data. In this case, the new data type is called Box. We will use this name to declare objects of type Box. It is important to remember that a class declaration only creates a template; it does not create an actual object. Thus, the preceding code does not cause any objects of type Box to come into existence. To actually create a Box object, we will use a statement like the following: Box mybox = new Box(); // create a Box object called mybox After this statement executes, mybox will be an instance of Box. Thus, it will have “physical” reality. For the moment, don’t worry about the details of this statement. Again, each time we create an instance of a class, we are creating an object that contains its own copy of each instance variable defined by the class. Thus, every Box object will contain its own copies of the instance variables width, height, and depth. To access these variables, we will use the dot (.) operator. The dot operator links the name of the object with the name of an instance variable. For example, to assign the width variable of mybox the value 100, we would use the following statement: mybox.width = 100; This statement tells the compiler to assign the copy of width that is contained within the mybox object the value of 100. In general, we use the dot operator to access both the instance variables and the methods within an object. Here is a complete program that uses the Box class: /* A program that uses the Box class. Call this file BoxDemo.java */ class Box { double width; double height; double depth; } // This class declares an object of type Box. class BoxDemo { public static void main(String args[]) { Box mybox = new Box(); double vol; // assign values to mybox's instance variables mybox.width = 10;
  • 3. mybox.height = 20; mybox.depth = 15; // compute volume of box vol = mybox.width * mybox.height * mybox.depth; System.out.println("Volume is " + vol); } } We should call the file that contains this program BoxDemo.java, because the main( ) method is in the class called BoxDemo, not the class called Box. When we compile this program, we will find that two .class files have been created, one for Box and one for BoxDemo. The Java compiler automatically puts each class into its own .class file. It is not necessary for both the Box and the BoxDemo class to actually be in the same source file. We could put each class in its own file, called Box.java and BoxDemo.java, respectively. To run this program, we must execute BoxDemo.class. When we do, we will see the following output: Volume is 3000.0 As stated earlier, each object has its own copies of the instance variables. This means that if we have two Box objects, each has its own copy of depth, width, and height. It is important to understand that changes to the instance variables of one object have no effect on the instance variables of another. For example, the following program declares two Box objects: // This program declares two Box objects. class Box { double width; double height; double depth; } class BoxDemo2 { public static void main(String args[]) { Box mybox1 = new Box(); Box mybox2 = new Box(); THE JAVA LANGUAGE double vol; // assign values to mybox1's instance variables mybox1.width = 10; mybox1.height = 20; mybox1.depth = 15; /* assign different values to mybox2's instance variables */ mybox2.width = 3; mybox2.height = 6; mybox2.depth = 9; // compute volume of first box vol = mybox1.width * mybox1.height * mybox1.depth;
  • 4. System.out.println("Volume is " + vol); // compute volume of second box vol = mybox2.width * mybox2.height * mybox2.depth; System.out.println("Volume is " + vol); } } The output produced by this program is shown here: Volume is 3000.0 Volume is 162.0 As we can see, mybox1’s data is completely separate from the data contained in mybox2. Declaring Objects: when we create a class, we are creating a new data type. We can use this type to declare objects of that type. However, obtaining objects of a class is a two-step process. First, we must declare a variable of the class type. This variable does not define an object. Instead, it is simply a variable that can refer to an object. Second, we must acquire an actual, physical copy of the object and assign it to that variable. We can do this using the new operator. The new operator dynamically allocates (that is, allocates at run time) memory for an object and returns a reference to it. This reference is, more or less, the address in memory of the object allocated by new. This reference is then stored in the variable. Thus, in Java, all class objects must be dynamically allocated. Let’s look at the details of this procedure. In the preceding sample programs, a line similar to the following is used to declare an object of type Box: Box mybox = new Box(); This statement combines the two steps just described. It can be rewritten like this to show each step more clearly: Box mybox; // declare reference to object mybox = new Box(); // allocate a Box object The first line declares mybox as a reference to an object of type Box. After this line executes, mybox contains the value null, which indicates that it does not yet point to an actual object. Any attempt to use mybox at this point will result in a compile-time error. The next line allocates an actual object and assigns a reference to it to mybox. After the second line executes, we can use mybox as if it were a Box object. But in reality, mybox simply holds the memory address of the actual Box object. The effect of these two lines of code is depicted in the following Figure
  • 5. Assigning Object Reference Variables: Object reference variables act differently than we might expect when an assignment takes place. For example, what do we think the following fragment does? Box b1 = new Box(); Box b2 = b1; We might think that b2 is being assigned a reference to a copy of the object referred to by b1. That is, we might think that b1 and b2 refer to separate and distinct objects. However, this would be wrong. Instead, after this fragment executes, b1 and b2 will both refer to the same object. The assignment of b1 to b2 did not allocate any memory or copy any part of the original object. It simply makes b2 refer to the same object as does b1. Thus, any changes made to the object through b2 will affect the object to which b1 is referring, since they are the same object. This situation is depicted here: Although b1 and b2 both refer to the same object, they are not linked in any other way. For example, a subsequent assignment to b1 will simply unhook b1 from the original object without affecting the object or affecting b2. For example: Box b1 = new Box(); Box b2 = b1; // ... b1 = null; Here, b1 has been set to null, but b2 still points to the original object.
  • 6. Introducing Methods: This is the general form of a method: type name(parameter-list) { // body of method } Here, type specifies the type of data returned by the method. This can be any valid type, including class types that we create. If the method does not return a value, its return type must be void. The name of the method is specified by name. This can be any legal identifier other than those already used by other items within the current scope. The parameter-list is a sequence of type and identifier pairs separated by commas. Parameters are essentially variables that receive the value of the arguments passed to the method when it is called. If the method has no parameters, then the parameter list will be empty. Methods that have a return type other than void return a value to the calling routine using the following form of the return statement: return value; Adding a Method to the Box Class Let’s begin by adding a method to the Box class. It may have occurred to us while looking at the preceding programs that the computation of a box’s volume was something that was best handled by the Box class rather than the BoxDemo class. After all, since the volume of a box is dependent upon the size of the box, it makes sense to have the Box class compute it. To do this, we must add a method to Box, as shown here: // This program includes a method inside the box class. class Box { double width; double height; double depth; // display volume of a box void volume() { System.out.print("Volume is "); System.out.println(width * height * depth); } } class BoxDemo3 { public static void main(String args[]) { Box mybox1 = new Box(); Box mybox2 = new Box(); // assign values to mybox1's instance variables mybox1.width = 10; mybox1.height = 20; mybox1.depth = 15; /* assign different values to mybox2's
  • 7. instance variables */ mybox2.width = 3; mybox2.height = 6; mybox2.depth = 9; // display volume of first box mybox1.volume(); // display volume of second box mybox2.volume(); } } Output: Volume is 3000.0 Volume is 162.0 Look closely at the following two lines of code: mybox1.volume(); mybox2.volume(); The first line here invokes the volume( ) method on mybox1. That is, it calls volume( ) relative to the mybox1 object, using the object’s name followed by the dot operator. Thus, the call to mybox1.volume( ) displays the volume of the box defined by mybox1, and the call to mybox2.volume( ) displays the volume of the box defined by mybox2. Each time volume( ) is invoked, it displays the volume for the specified box. When mybox1.volume( ) is executed, the Java run-time system transfers control to the code defined inside volume( ). After the statements inside volume( ) have executed, control is returned to the calling routine, and execution resumes with the line of code following the call. In the most general sense, a method is Java’s way of implementing subroutines. There is something very important to notice inside the volume( ) method: the instance variables width, height, and depth are referred to directly, without preceding them with an object name or the dot operator. When a method uses an instance variable that is defined by its class, it does so directly, without explicit reference to an object and without use of the dot operator. This is easy to understand if we think about it. A method is always invoked relative to some object of its class. Once this invocation has occurred, the object is known. Thus, within a method, there is no need to specify the object a second time. This means that width, height, and depth inside volume( ) implicitly refer to the copies of those variables found in the object that invokes volume( ). Returning a Value While the implementation of volume( ) does move the computation of a box’s volume inside the Box class where it belongs, it is not the best way to do it. For example, what if another part of our program wanted to know the volume of a box, but not display its value? A better way to implement volume( ) is to have it compute the volume of the box and return the result to the caller. The following example, an improved version of the preceding program, does just that: // Now, volume() returns the volume of a box.
  • 8. class Box { double width; double height; double depth; // compute and return volume double volume() { return width * height * depth; } } class BoxDemo4 { public static void main(String args[]) { Box mybox1 = new Box(); Box mybox2 = new Box(); double vol; // assign values to mybox1's instance variables mybox1.width = 10; mybox1.height = 20; mybox1.depth = 15; /* assign different values to mybox2's instance variables */ mybox2.width = 3; mybox2.height = 6; mybox2.depth = 9; // get volume of first box vol = mybox1.volume(); System.out.println("Volume is " + vol); // get volume of second box vol = mybox2.volume(); System.out.println("Volume is " + vol); } } As we can see, when volume( ) is called, it is put on the right side of an assignment statement. On the left is a variable, in this case vol, that will receive the value returned by volume( ). Thus, after vol = mybox1.volume(); executes, the value of mybox1.volume( ) is 3,000 and this value then is stored in vol. There are two important things to understand about returning values: ■ The type of data returned by a method must be compatible with the return type specified by the method. For example, if the return type of some method is boolean, we could not return an integer. ■ The variable receiving the value returned by a method (such as vol, in this case) must also be compatible with the return type specified for the method.
  • 9. One more point: The preceding program can be written a bit more efficiently because there is actually no need for the vol variable. The call to volume( ) could have been used in the println( ) statement directly, as shown here: System.out.println("Volume is " + mybox1.volume()); In this case, when println( ) is executed, mybox1.volume( ) will be called automatically and its value will be passed to println( ). Adding a Method That Takes Parameters While some methods don’t need parameters, most do. Parameters allow a method to be generalized. That is, a parameterized method can operate on a variety of data and/or be used in a number of slightly different situations. To illustrate this point, let’s use a very simple example. Here is a method that returns the square of the number 10: int square() { return 10 * 10; } While this method does, indeed, return the value of 10 squared, its use is very limited. However, if we modify the method so that it takes a parameter, as shown next, then we can make square( ) much more useful. int square(int i) { return i * i; } Now, square( ) will return the square of whatever value it is called with. That is, square( ) is now a general-purpose method that can compute the square of any integer value, rather than just 10. Here is an example: int x, y; x = square(5); // x equals 25 x = square(9); // x equals 81 y = 2; x = square(y); // x equals 4 In the first call to square( ), the value 5 will be passed into parameter i. In the second call, i will receive the value 9. The third invocation passes the value of y, which is 2 in this example. As these examples show, square( ) is able to return the square of whatever data it is passed. It is important to keep the two terms parameter and argument straight. A parameter is a variable defined by a method that receives a value when the method is called. For example, in square( ), i is a parameter. An argument is a value that is passed to a method when it is invoked. For example, square(100) passes 100 as an argument. Inside square( ), the parameter
  • 10. i receives that value. We can use a parameterized method to improve the Box class. In the preceding examples, the dimensions of each box had to be set separately by use of a sequence of statements, such as: mybox1.width = 10; mybox1.height = 20; mybox1.depth = 15; While this code works, it is troubling for two reasons. First, it is clumsy and error prone. For example, it would be easy to forget to set a dimension. Second, in well-designed Java programs, instance variables should be accessed only through methods defined by their class. In the future, we can change the behavior of a method, but we can’t change the behavior of an exposed instance variable. Thus, a better approach to setting the dimensions of a box is to create a method that takes the dimension of a box in its parameters and sets each instance variable appropriately. This concept is implemented by the following program: // This program uses a parameterized method. class Box { double width; double height; double depth; // compute and return volume double volume() { return width * height * depth; } // sets dimensions of box void setDim(double w, double h, double d) { width = w; height = h; depth = d; } } class BoxDemo5 { public static void main(String args[]) { Box mybox1 = new Box(); Box mybox2 = new Box(); double vol; // initialize each box mybox1.setDim(10, 20, 15); mybox2.setDim(3, 6, 9); // get volume of first box vol = mybox1.volume(); System.out.println("Volume is " + vol); // get volume of second box vol = mybox2.volume(); System.out.println("Volume is " + vol);
  • 11. } } As we can see, the setDim( ) method is used to set the dimensions of each box. For example, when mybox1.setDim(10, 20, 15); is executed, 10 is copied into parameter w, 20 is copied into h, and 15 is copied into d. Inside setDim( ) the values of w, h, and d are then assigned to width, height, and depth, respectively. What are Constructors? What are the Characteristics of constructors? It can be tedious to initialize all of the variables in a class each time an instance is created. Even when we add convenience functions like setDim( ), it would be simpler and more concise to have all of the setup done at the time the object is first created. Because the requirement for initialization is so common, Java allows objects to initialize themselves when they are created. This automatic initialization is performed through the use of a constructor. A constructor initializes an object immediately upon creation. It has the same name as the class in which it resides and is syntactically similar to a method. Once defined, the constructor is automatically called immediately after the object is created, before the new operator completes. Constructors look a little strange because they have no return type, not even void. This is because the implicit return type of a class’ constructor is the class type itself. It is the constructor’s job to initialize the internal state of an object so that the code creating an instance will have a fully initialized, usable object immediately. We can rework the Box example so that the dimensions of a box are automatically initialized when an object is constructed. To do so, replace setDim( ) with a constructor. Let’s begin by defining a simple constructor that simply sets the dimensions of each box to the same values. This version is shown here: /* Here, Box uses a constructor to initialize the dimensions of a box.*/ class Box { double width; double height; double depth; // This is the constructor for Box. Box() { System.out.println("Constructing Box"); width = 10; height = 10; depth = 10; } // compute and return volume double volume() { return width * height * depth; } }
  • 12. class BoxDemo6 { public static void main(String args[]) { // declare, allocate, and initialize Box objects Box mybox1 = new Box(); Box mybox2 = new Box(); double vol; // get volume of first box vol = mybox1.volume(); System.out.println("Volume is " + vol); // get volume of second box vol = mybox2.volume(); System.out.println("Volume is " + vol); } } When this program is run, it generates the following results: Constructing Box Constructing Box Volume is 1000.0 Volume is 1000.0 As we can see, both mybox1 and mybox2 were initialized by the Box( ) constructor when they were created. Since the constructor gives all boxes the same dimensions, 10 by 10 by 10, both mybox1 and mybox2 will have the same volume. The println( ) statement inside Box( ) is for the sake of illustration only. Most constructors will not display anything. They will simply initialize an object. Before moving on, let’s reexamine the new operator. As we know, when we allocate an object, we use the following general form: class-var = new classname( ); Now we can understand why the parentheses are needed after the class name. What is actually happening is that the constructor for the class is being called. Thus, in the line Box mybox1 = new Box(); new Box( ) is calling the Box( ) constructor. When we do not explicitly define a constructor for a class, then Java creates a default constructor for the class. This is why the preceding line of code worked in earlier versions of Box that did not define a constructor. The default constructor automatically initializes all instance variables to zero. The default constructor is often sufficient for simple classes, but it usually won’t do for more sophisticated ones. Once we define our own constructor, the default constructor is no longer used. Parameterized Constructors While the Box( ) constructor in the preceding example does initialize a Box object, it is not very useful—all boxes have the same dimensions. What is needed is a way to construct Box objects of
  • 13. various dimensions. The easy solution is to add parameters to the constructor. As we can probably guess, this makes them much more useful. For example, the following version of Box defines a parameterized constructor which sets the dimensions of a box as specified by those parameters. Pay special attention to how Box objects are created. /* Here, Box uses a parameterized constructor to initialize the dimensions of a box.*/ class Box { double width; double height; double depth; // This is the constructor for Box. Box(double w, double h, double d) { width = w; height = h; depth = d; } // compute and return volume double volume() { return width * height * depth; } } class BoxDemo7 { public static void main(String args[]) { // declare, allocate, and initialize Box objects Box mybox1 = new Box(10, 20, 15); Box mybox2 = new Box(3, 6, 9); double vol; // get volume of first box vol = mybox1.volume(); System.out.println("Volume is " + vol); // get volume of second box vol = mybox2.volume(); System.out.println("Volume is " + vol); } } The output from this program is shown here: Volume is 3000.0 Volume is 162.0 As we can see, each object is initialized as specified in the parameters to its constructor. For example, in the following line, Box mybox1 = new Box(10, 20, 15);
  • 14. the values 10, 20, and 15 are passed to the Box( ) constructor when new creates the object. Thus, mybox1’s copy of width, height, and depth will contain the values 10, 20, and 15, respectively. The this Keyword Sometimes a method will need to refer to the object that invoked it. To allow this, Java defines the this keyword. this can be used inside any method to refer to the current object. That is, this is always a reference to the object on which the method was invoked. We can use this anywhere a reference to an object of the current class’ type is permitted. To better understand what this refers to, consider the following version of Box( ): // A redundant use of this. Box(double w, double h, double d) { this.width = w; this.height = h; this.depth = d; } This version of Box( ) operates exactly like the earlier version. The use of this is redundant, but perfectly correct. Inside Box( ), this will always refer to the invoking object. Lab 4: Classes and Objects Introduction to Garbage Collection Since objects are dynamically allocated by using the new operator, we might be wondering how such objects are destroyed and their memory released for later reallocation. In some languages, such as C++, dynamically allocated objects must be manually released by use of a delete operator. Java takes a different approach; it handles deallocation for we automatically. The technique that accomplishes this is called garbage collection. It works like this: when no references to an object exist, that object is assumed to be no longer needed, and the memory occupied by the object can be reclaimed. There is no explicit need to destroy objects as in C++. Garbage collection only occurs sporadically (if at all) during the execution of our program. It will not occur simply because one or more objects exist that are no longer used. Furthermore, different Java run-time implementations will take varying approaches to garbage collection, but for the most part, we should not have to think about it while writing our programs. Using Finalize() method Sometimes an object will need to perform some action when it is destroyed. For example, if an object is holding some non-Java resource such as a file handle or window character font, then we might want to make sure these resources are freed before an object is destroyed. To handle such situations, Java provides a mechanism called finalization. By using finalization, we can define specific actions that will occur when an object is just about to be reclaimed by the garbage collector. To add a finalizer to a class, we simply define the finalize( ) method. The Java run time calls that method whenever it is about to recycle an object of that class. Inside the finalize( ) method we will specify those actions that must be performed before an object is destroyed. The garbage collector runs periodically, checking for objects that are no longer referenced by any running state or
  • 15. indirectly through other referenced objects. Right before an asset is freed, the Java run time calls the finalize( ) method on the object. The finalize( ) method has this general form: protected void finalize( ) { // finalization code here } Here, the keyword protected is a specifier that prevents access to finalize( ) by code defined outside its class. It is important to understand that finalize( ) is only called just prior to garbage collection. It is not called when an object goes out-of-scope, for example. This means that we cannot know when—or even if—finalize( ) will be executed. Therefore, our program should provide other means of releasing system resources, etc., used by the object. It must not rely on finalize( ) for normal program operation. Overloading methods In Java it is possible to define two or more methods within the same class that share the same name, as long as their parameter declarations are different. When this is the case, the methods are said to be overloaded, and the process is referred to as method overloading. Method overloading is one of the ways that Java implements polymorphism. When an overloaded method is invoked, Java uses the type and/or number of arguments as its guide to determine which version of the overloaded method to actually call. Thus, overloaded methods must differ in the type and/or number of their parameters. While overloaded methods may have different return types, the return type alone is insufficient to distinguish two versions of a method. When Java encounters a call to an overloaded method, it simply executes the version of the method whose parameters match the arguments used in the call. Here is a simple example that illustrates method overloading: // Demonstrate method overloading. class OverloadDemo { void test() { System.out.println("No parameters"); } // Overload test for one integer parameter. void test(int a) { System.out.println("a: " + a); } // Overload test for two integer parameters. void test(int a, int b) { System.out.println("a and b: " + a + " " + b); } // overload test for a double parameter double test(double a) { System.out.println("double a: " + a); return a*a;
  • 16. } } class Overload { public static void main(String args[]) { OverloadDemo ob = new OverloadDemo(); double result; // call all versions of test() ob.test(); ob.test(10); ob.test(10, 20); result = ob.test(123.25); System.out.println("Result of ob.test(123.25): " + result); } } This program generates the following output: No parameters a: 10 a and b: 10 20 double a: 123.25 Result of ob.test(123.25): 15190.5625 As we can see, test( ) is overloaded four times. The first version takes no parameters, the second takes one integer parameter, the third takes two integer parameters, and the fourth takes one double parameter. The fact that the fourth version of test( ) also returns a value is of no consequence relative to overloading, since return types do not play a role in overload resolution. When an overloaded method is called, Java looks for a match between the arguments used to call the method and the method’s parameters. However, this match need not always be exact. In some cases Java’s automatic type conversions can play a role in overload resolution. For example, consider the following program: // Automatic type conversions apply to overloading. class OverloadDemo { void test() { System.out.println("No parameters"); } // Overload test for two integer parameters. void test(int a, int b) { System.out.println("a and b: " + a + " " + b); } // overload test for a double parameter void test(double a) { System.out.println("Inside test(double) a: " + a); } } class Overload { public static void main(String args[]) {
  • 17. OverloadDemo ob = new OverloadDemo(); int i = 88; ob.test(); ob.test(10, 20); ob.test(i); // this will invoke test(double) ob.test(123.2); // this will invoke test(double) } } This program generates the following output: No parameters a and b: 10 20 Inside test(double) a: 88 Inside test(double) a: 123.2 As we can see, this version of OverloadDemo does not define test(int). Therefore, when test( ) is called with an integer argument inside Overload, no matching method is found. However, Java can automatically convert an integer into a double, and this conversion can be used to resolve the call. Therefore, after test(int) is not found, Java elevates i to double and then calls test(double). Of course, if test(int) had been defined, it would have been called instead. Java will employ its automatic type conversions only if no exact match is found. Method overloading supports polymorphism because it is one way that Java implements the “one interface, multiple methods” paradigm. To understand how, consider the following. In languages that do not support method overloading, each method must be given a unique name. However, frequently we will want to implement essentially the same method for different types of data. Consider the absolute value function. In languages that do not support overloading, there are usually three or more versions of this function, each with a slightly different name. For instance, in C, the function abs( ) returns the absolute value of an integer, labs( ) returns the absolute value of a long integer, and fabs( ) returns the absolute value of a floating-point value. Since C does not support overloading, each function has to have its own name, even though all three functions do essentially the same thing. This makes the situation more complex, conceptually, than it actually is. Although the underlying concept of each function is the same, we still have three names to remember. This situation does not occur in Java, because each absolute value method can use the same name. Indeed, Java’s standard class library includes an absolute value method, called abs( ). This method is overloaded by Java’s Math class to handle all numeric types. Java determines which version of abs( ) to call based upon the type of argument. The value of overloading is that it allows related methods to be accessed by use of a common name. Thus, the name abs represents the general action which is being performed. It is left to the compiler to choose the right specific version for a particular circumstance. We, the programmer, need only remember the general operation being performed. Through the application of polymorphism, several names have been reduced to one. Although this example is fairly simple, if we expand the concept, we can see how overloading can help we manage greater complexity. When we overload a method, each version of that method can perform any activity we desire. There is no rule stating that overloaded methods must relate to one another. However, from a stylistic point of view, method overloading implies a relationship. Thus, while we can use the same name to overload unrelated methods, we should not. For example, we could use the name sqr to
  • 18. create methods that return the square of an integer and the square root of a floating-point value. But these two operations are fundamentally different. Applying method overloading in this manner defeats its original purpose. In practice, we should only overload closely related operations. Overloading constructors In addition to overloading normal methods, we can also overload constructor methods. class Box { double width; double height; double depth; // This is the constructor for Box. Box(double w, double h, double d) { width = w; height = h; depth = d; } // compute and return volume double volume() { return width * height * depth; } } As we can see, the Box( ) constructor requires three parameters. This means that all declarations of Box objects must pass three arguments to the Box( ) constructor. For example, the following statement is currently invalid: Box ob = new Box(); Since Box( ) requires three arguments, it’s an error to call it without them. This raises some important questions. What if we simply wanted a box and did not care (or know) what its initial dimensions were? Or, what if we want to be able to initialize a cube by specifying only one value that would be used for all three dimensions? As the Box class is currently written, these other options are not available to us. Fortunately, the solution to these problems is quite easy: simply overload the Box constructor so that it handles the situations just described. Here is a program that contains an improved version of Box that does just that: /* Here, Box defines three constructors to initialize the dimensions of a box various ways.*/ class Box { double width; double height; double depth; // constructor used when all dimensions specified Box(double w, double h, double d) { width = w; height = h;
  • 19. depth = d; } // constructor used when no dimensions specified Box() { width = -1; // use -1 to indicate height = -1; // an uninitialized depth = -1; // box } // constructor used when cube is created Box(double len) { width = height = depth = len; } // compute and return volume double volume() { return width * height * depth; } } class OverloadCons { public static void main(String args[]) { // create boxes using the various constructors Box mybox1 = new Box(10, 20, 15); Box mybox2 = new Box(); Box mycube = new Box(7); double vol; // get volume of first box vol = mybox1.volume(); System.out.println("Volume of mybox1 is " + vol); // get volume of second box vol = mybox2.volume(); System.out.println("Volume of mybox2 is " + vol); // get volume of cube vol = mycube.volume(); System.out.println("Volume of mycube is " + vol); } } The output produced by this program is shown here: Volume of mybox1 is 3000.0 Volume of mybox2 is -1.0 Volume of mycube is 343.0