- Using Visual J++ -

Chapter 12

Working with Classes


In this chapter, you will learn all about Java classes. Classes form OOP's foundation. in this chapter, you'll gain an insight into object-oriented programming with Java. If you are a C++ programmer, you'll feel at home here except Java does provide some special OOP features (and limitations) you'll not find in C++.

Most of your Java programming revolves around a class's data and methods. Java contains all kinds of rules and conventions for specifying class data and methods. In this chapter, you'll finally understand how methods perform their work and you'll see how to set up data that respond to those methods.

Overview of Classes

A class is often synonymous with a Visual J++ applet. A Java program usually consists of a single class definition. Classes exist for objects that you define. Although I've thrown around the term object throughout this book, I've never really defined the term. An object as like a variable except that an object is active whereas a variable is passive. A variable has a data type and holds a value. An object not only can hold data, but the object can hold data of several different types of data. Often, an object's data values are called data members. In addition to data members (sometimes called fields), objects also contain methods.

Think about that last sentence. A method, as you already know, is set of code lines similar to a procedure. Methods belong to classes. When you write a class, you not only describe the data within the class but you also describe the class abilities by including methods in the class.

Let's return once again to chapter 9's sample program. Listing 12.1 contains the code that you studied in chapter 9. Whereas chapter 9 approached the code form a micro-explanatory viewpoint (line-by-line), we'll now look at the listing using a class perspective.


Listing 12.1 Analyzing a Program's Class

//*******************************************************************
// Simple.java: Applet
//*******************************************************************
import java.applet.*; // Requires support files
import java.awt.*;
//===================================================================
// Main Class for Simple applet
//===================================================================
public class Simple extends Applet
{
public void init()
{
resize(320, 240); // applet's window size
}
//--------------------------------------------------------------
public void paint(Graphics g)
{
// Change subsequent text color to red
g.setColor(Color.red);
// Write a simple message in the window
g.drawString("Very simple!", 75, 100);
}
}

Although the Simple applet contains several statements, all of those statements reside in a class named Simple. The Simple class happens not to contain data; as you can see, no variables are defined because all of the data consists of literal data values. Nevertheless, you could define integers, characters, and variables of all the other data types within the Simple class. If you did define variables, the variables would be part of the Simple class. When the Web page that contains this applet executes the compiled bytecode, one object, the Simple object, is created. In OOP terminology, when you or your computer creates an object, the object is instantiated (Never use a small word when a huge one will confuse lots of people!).

The Simple object can do two things that a variable could never do: initialize itself and paint itself on the screen. The Simple object that Java instantiates can perform these two actions. In addition, once you define a class such as Simple, you can create additional classes that inherit all of Simple's definition and actions and you can also extend the capabilities of that inherited object.

An object is actually a data item but an object is a full-packaged data item with active methods and usually data fields. Generally, in an OOP language such as Java, anything you can do with variables, you can do with objects. Therefore, you can define objects (through class definitions), create (instantiate) objects, assign data to objects, and even perform calculations with objects. Unlike the built-in data types such as int and double, if you perform a math operation on one of your objects, you'll have to teach Java how to do the math by writing a method that performs the work. Once you supply the method's code once, however, calculating with the objects is basically as easy to do as calculating with the built-in types.

You define variables but you instantiate objects. Listing 12.2 helps clarify the difference.


Listing 12.2 Instantiating Objects

//*******************************************************************
// The Box class
//===================================================================
class Box // No inheritance
{
float area; // A class data member
int colorCode; // A class data member
void calcArea (float width, float length)
{
area = width * length;
}
void setColor (int colorValue)
{
colorCode = colorValue;
}
}

Box is a class with four members: Two of Box's members are data members and two are methods. Box is not an object! You can, however, instantiate objects from the Box class.

 
Box is not complete. You would need to add additional members to use Box effectively as you'll learn throughout this chapter.

In a way, a class works almost like a new data type that you add to the Java language. Java does not need to be told what an int is before you define an int variable like this:

int value; // This statement defines a new int variable named value

Java, however, does not know what Box is. Only until you define the Box class, as in listing 12.2, can you then define, or more accurately instantiate, a Box object:

Box hatHolder; // This statement defines a new Box object named hatHolder

Can you see that, loosely speaking, an object is a lot like a variable? You first have to tell Java about the object's type by defining the object's class, and you then have to define one or more objects just as you can define one or more variables. Unlike a variable, however, the hatHolder object contains more than a single data type; hatHolder is a combination int and float value, along with two methods that manipulate the data values.

After instantiating an object, execute that object's methods with the dot operator. The following statement executes hatHolder's calcArea() method:

hatHolder.calcArea(4.3, 10.244); // Executes the calcArea() method

In chapter 10, "Java Language Fundamentals," you learned how to use the new keyword to define an array. new also defines objects. Due to special object-constructing considerations that you'll learn about later in this chapter (see "Special Constructor Methods"), get into the habit now of defining objects using new instead of defining them with the simpler className objectName; format. Here is how you would define, or instantiate, a Box object using new:

Box hatHolder = new Box(); // This statement instantiates a new Box object

 
You can create objects based on the internal data types. The following statement defines a character object:
 
initial = new Char('X'); // Defines and initializes a new character object
 
All of the built-in data types, such as int and double can produce objects. Be warned, however, that if you create and define objects from the built-in data types, you cannot perform normal operations on those objects! In other words, you cannot add two double data type objects with the plus operator, +. You must write methods to manipulate all objects, even those you define from the built-in data types.

Always use the parentheses when you use new with object instantiation. You can instantiate two Box objects like this:


Box slot1 = new Box();
Box slot2 = new Box();

You now can calculate the area for the second Box object like this:

slot2.calcArea(1.0, 5.0); // Sets the second object's area

You can specify the color for the first box like this:

slot1.setColor( 4 ); // Sets the first object's color

The data members are not always available to the outside world. One of OOP's strengths is data hiding, or limiting the access of data members to classes and methods outside the current class. Although you can provide a way for data members to be fully available to outside code (as you'll see later in the section entitled, "Data Member Modifiers,") this chapter will assume that all data members are hidden from access by any code outside the current class.

Therefore, if an outside class or a derived subclass is to instantiate a Box object, that class cannot generally access the data members colorCode and area directly; the methods, however, do allow code to access and even initialize the data members, but only through the method's code. Suppose you don't want someone changing the color of a box unless they know a password. You can add the password-checking code at the beginning of the setColor() method. Only code that sends the correct password can then change the color because the color data member is completely unavailable to code outside the Box class's method area.

If you need to define an array of objects, you can do so like this:

Box hatHolders[] = new Box[50];

The array named hatHolders contains the elements hatHolders[0] to hatHolders[49].

To execute one of the array's methods, specify the array element and then the method:

hatHolder[2].setColor(12); // Sets the color for the 3rd Box object to 12

If you inherit a class from Box, all objects of the inherited class will also contain the original Box class's two data and two method members. You can add additional data and method members to the subclasses if you want inherited objects to gain more functionality than the Box parent class provides alone.

In addition to inheritance, you can use a class or a package of classes from other classes. You saw how to simulate inheritance in part II," Taking Visual J++ to Task," by specifying new parameter values, although you did not yet have enough background necessary to understand full inheritance concepts at the time.

 
Why inherit? To eliminate redundant coding. When you inherit a class, your subclass (sometimes called the child class) inherits all methods and data fields from the parent class. Therefore, with one simple extends keyword, your new class that you inherit from a parent class gains all of the parent's power, behaviors, and data. When you define methods and data fields in the inherited class, that class becomes stronger than its parent class because the inherited class now has extended abilities. To clarify, when you inherit from the Applet class, as in listing 12.1, you gain all the functionality of Applet and, as listing 12.1 shows, you can add to that functionality by specifying init(), paint(), and much more.

The Simple class's init() and paint() methods are not new methods! You are extending methods already given to you. At this point, you cannot tell if the methods came from Applet's methods or one of the imported classes.

Often, a class's data members and method members are called instance variables and instance methods because the data and methods really don't exist (they are only described) until you instantiate an object.

Where Are We Going with All This?

The nature of objects and OOP sneaks up on you. I'll throw some at you here, and some later. Relax if you don't feel as if you've mastered the concept of objects after completing this chapter. As a matter of fact, complete books much thicker than this one are devoted to the explanation of OOP concepts and many of them fail miserably despite their length.
 
OOP requires a special kind of thinking that does not always come quickly. Throughout this book, we'll work on your mastering Visual J++ and the Java language as well as your OOP skills. We'll return to discussion on OOP theory when needed.
 
Actually, all code that you see in this book is object-oriented because Java is such a pure object-oriented language. We'll now continue using Java inside the Visual J++ environment but we'll return to OOP-based theory when an extra explanation is needed.

Types of Classes

A class falls into one of the following types:

You're responsible for defining and naming your own classes. A class name follows the identifier naming rules, so when naming a class follow the same guidelines as you use when naming variables.

 
General Java programming principles suggest, but do not require, that you begin all class names with an uppercase letter to help distinguish class names from variable and methods.

Public Classes

When you define a public class, you make that class available to any and all classes that might want to inherit from the class. In part II, you extended many public classes and you were able to change the way a program worked simply by specifying parameter values. You can also extend a public class and add to the behavior of that class.

The public keyword defines a class to be public. Therefore, listing 12.1's Simple class (the child class of Applet that's supplied with Visual J++) is public due to the class definition line shown here:

public class Simple extends Applet

You don't have to inherit a class to create a class. The following statement defines a class named MyClass that is not a subclass of any other class because the definition does not include the extends keyword:

public class MyClass // The class body would follow

Final Classes

Instead of public, if you define a class to be final with the final keyword, no code can inherit the class. Security concerns generally require that some classes, such as password classes, network-based classes, and file-encryption classes, remain protected from any later inheritance.

The following statement defines a final class named Secure:

final class Secure

 
Although you cannot inherit from a final class, you can create a final class by inheriting the class from a non-final class.

Abstract Classes

An abstract class is an incomplete class. The class contains at least one method but the method contains no code. Although you cannot instantiate objects from abstract classes, you can inherit from an abstract class and fill in the missing pieces in the inherited class.

For example, you could create an abstract printing routine that will eventually, in inherited classes, print output on different kinds of printers. You could include an unfinished colorPrint() method. If you then inherit the class for a specific color printer, the inherited class could fill in the colorPrint() method with colorized output. If you inherited the class for a black-and-white printer, you could fill in the colorPrint() method that produces only black-and-white colors. You'll still retain all the functionality of the parent class, however, as long as you fill in the missing pieces and extend whatever data fields and methods needed by inherited objects.

The following statement defines an abstract class:

abstract class printArt

 
An abstract class must contain at least one abstract method.

Friendly Classes

A friendly class contains no public, final, or abstract keyword. The following statement defines a friendly class:

class Casper // Defines a friendly class

The friendly class makes its data fields and methods available to all other classes that want to inherit as long as those classes reside in the same package as the friendly class. In other words, the friendly class is lenient about giving its inheritance away but only to other members of the same class package.

 
Often, a friendly class is useful for writing support utility classes used throughout a class package, such as a special calculating class that other classes within a particular class might need to inherit from.

Methods do the work of classes. Therefore, you should take a few moments to learn about methods before continuing your class tutorial.

Introduction to Methods

Although you've seen methods in action, and although you know a little about what methods are all about (detours from code you call or class procedures that you write that activate the class objects), this section explains methods in a little more detail and fills in a few more pieces of the method puzzle.

 
As with classes, a method is an identifier. When you name a method, follow the same identifier naming rules as you use with variable and class names.

A Method's Execution

Methods perform work. Methods operate on class data by using the controlling statements you learned about in the previous chapter and also by utilizing variables that you declare along the way. In the previous chapter, you learned that a method's parameter list may contain values or the list may be empty. If parameters exist, the code that calls the method must initialize those values and send the expected parameter number and data types.

 
Think of a parameter as a variable that is passed from one statement to a method. Parameters look and act like variables.

Figure 12.1 shows how the code in one method calls another method and sends that called method two parameters. Java sends a copy of the parameter values but does not really send the parameter variables themselves. Therefore, if the called method changes a parameter in any way, the change is not noticed when the calling code regains control.

 
Java is known as a call-by-value language because the called method can never change the calling method's parameter data. The called method can use and modify the parameters within the called method's lines but when control returns to the calling code, the variables sent as parameters to the methods are unmodified.


FIG. 12.1

The calling code sends two expected parameters to the called code.

Notice that Java passes the values and not the variables to the receiving method. The method takes the two values, computes with them, prints their newly-computed values, and then returns control to the calling class.

Here is the output from figure 12.1's code:


In method...
a=456.000
b=4608.294930876
In calling class...
a=10.0
b=20.0

As you can see, the called method receives two values and changes those values but the changes last only inside the method. The calling class code's variables are not, and cannot, be changed by the method.

The return statement does not have to appear at the end of this called method because the method does not return any values to the calling class program. Without the return, program control would still return to the calling class once the method finishes.

The method's parameters are named x and y even though the calling program names those variables a and b. The received names might or might not match those in the calling code because only values are passed not variables. The biggest concern you must make when passing data to methods is to get the number of parameters and data types correct. The method is expecting a float followed by a double as you can see from the method's definition line shown below:

void myMethod(float x, double y)

The calling code must provide two variables that match this pattern. The first definition line of a called method must always define the parameters and their data types.

The passing of data between code and methods is not strictly one-way. A method can return a value to the calling code. Although you can send zero, one, or multiple parameters to a method, you can return only a single value. Figure 12.2 illustrates the return nature of methods.


FIG. 12.2

A called method can return only a single value.

Here is the output from figure 12.2's program:


In calling class...
a=10.0
doub=20.0

The doubleIt() method is a two-way method in that the calling code passes a parameter and the method also returns a value back to the calling code. The called method does not change any value in the calling code; the called method does, however, use the passed value, doubles the value, and returns the doubled value back to the calling code. The calling code captures the return value in the variable named doub.

The float in front of the doubleIt() method's definition line describes the method's return value. Although you can pass zero, one, or more parameters to a method, you can return only one. Therefore, a method always has at most one data type listed to the left of the method name. When you write your own methods that your class code will call, you'll want to include the method's returndata type in front of the method name as well.

Look back at figure 12.1's myMethod() definition line. You'll see the void keyword. void is a method modifier that indicates no return value will be coming from the method to back to the calling code. Therefore, the first word before a method's name, in the method's definition line, describes either the returned value's data type or void if no return value exists.

If a method does not require parameters, leave the parameter list empty. Unlike C and C++, you don't include the void keyword in a method's parameter list when no parameters are expected. A method definition's empty parameter list indicates that you are to pass no data to that method.

 
Methods do not have data types. Only their return value and optional parameters have data types.
 

Receiving the Returned Value

When calling a method that returns a value, the calling code must be able to capture the returned value. In figure 12.1's listing, the called method does not return a value so the calling code simply lists the method name and includes the passed parameters to execute the method. When the method returns control to the calling program, the statement following the method executes.
 
When calling code executes a method that will return a value, however, the calling code must make allowances for that return value. As you can see from figure 12.2, the calling code stores the return value in a variable named doub with this assignment:
 
doub = doubleIt(a);
 
The calling code must do something with the returned value. Here, the code assigns the return value to doub. The calling code could, for instance, print the returned value like this:
 
System.out.println("The return value is " + doubleIt(a) );
 
The called method becomes its data type. That is, the doubleIt(a) value turns into the actual return value when the method ends. Due to the return value, you should not call methods that return values by placing those called methods on lines by themselves like this:
 
doubleIt(a); // Wrong!
 
Since doubeIt(a) becomes its return value, doubleIt() becomes the returned value of 20.0. Therefore, this incorrect method call does nothing with the returned value ends up acting as if you'd typed this on the line that performs the call:
 
20.0;
 
As you can see, there would be problems if you have a returned value with and did nothing with it.
 
 
Notice the general-purpose nature of doubleIt(). Although the method is simple, it doubles whatever float value you pass to it. doubleIt() returns that doubled value to the calling code. Methods are great for general purpose routines that you perform often. As you write such methods, you can include them in classes that you create, then include those classes in your own class package, and then import your collection of routines into whatever code you write subsequently.
 
 
doubleIt() is extremely simple; so much so that an error could occur. If doubleIt() receives a value that is almost as large as the largest floating-point value, then doubleIt() doubles that value, the doubled value will not fit in the returning float data type. Such overflow, especially with float values, is rare, but keep your eye out for such hazards. You know your program's data requirements better than anyone else and as you write Java code, you'll know when the possibilities of overflow can take place.

Method Access Modifiers

A method may or may not be available to classes that inherit from the method's original class. Through the following access modifiers (sometimes called specifiers) that define how an inherited class can use a method, you can limit or supply access to subsequent child classes:

A class might contain several methods. Within the class, all methods can access (call) the other methods within the class. Derived classes, however, cannot and should not always be able to access all methods as freely as the class's original brother and sister methods.

To understand why, you should know that a class's data members are not always available to the outside world including classes that derive from that class and to methods that use that class. Suppose you write a security class that holds a network password in a string data member. You don't want other classes to be able to see or change the string except under strict circumstances. You can specify those circumstances by writing a class method that returns the password only when a user has access to see the password. In other words, derived classes cannot access the password data member but derived classes can access a method that returns the password if the user enters the correct permission.

You'll need to specify method access availability whenever a method handles critical data. The following sections briefly introduce you to the access specifiers and explain when and where you can use them.

Public Methods

When you place the public keyword in front of a method's definition line (before the return data type), the method is accessible to any and all derived methods. In addition, the method is also available to any program that uses the method's class.

The following statement defines a public method:

public float freeMethod(int x, float y, char z)

 
Public methods allow for the easiest access and the easiest programming characteristics because you don't have to perform fancy coding footwork to use the method from other classes and methods. Public methods, although simple, do not always lend themselves to good coding practices. Often, it's best to limit the scope of access because code should have access only hen needed to do its job. You should begin to give access on a need-to-know basis; if a class method does not have to be available to all other classes, limit its access using the non-public access modifiers from this chapter.

Protected Methods

A protected method behaves exactly like public methods with one limitation: Objects that reside outside the protected method's package, that is, objects that you instantiate from classes outside the protected method's class package, have no access to the method.

The following statement defines a protected method:

protected float freeMethod(int x, float y, char z)

Private Methods

When you want to restrict a method's access only to the class in which you define the method, use the private access specifier. A method is not always a general-purpose method such as the one described in the section named "A Method's Execution." Sometimes, you'll write a helper method that supports other methods within the class, perhaps to encrypt or decrypt a password, but you don't want any class, derived or otherwise, to use the method. That method is best left private; therefore, only the other class methods have access because they are the only ones with a need to know about the method.

The following statement defines a private method:

private float freeMethod(int x, float y, char z)

Friendly Methods

A friendly method occurs when you forget to put a method's access specifier, or when you choose to omit the access specifier. Friendly methods are available to all other methods and classes within the package in which you define the friendly method. However, derived classes cannot access a friendly method.

There is no friendly keyword. A method automatically friendly if you do not use any other access specifier. The following statement defines a friendly method:

float freeMethod(int x, float y, char z)

Private Protected Methods

A private protected method makes itself available only to derived subclasses. That is, a private protected method is usable from derived classes but no other class in the package or in an outside package can use the method.

The following statement defines a private protected method:

private protected float freeMethod(int x, float y, char z)

Abstract Methods

An abstract method contains no code. By using the abstract keyword, you designate a method as an abstract method, and therefore, you designate the class as abstract as well. Consider the following method:

abstract float freeMethod(int x, float y, char z);

Look at the last character on the line! The semicolon does not appear in the other method definitions you've seen! The semicolon terminates the method right there. The method has no body because it's an abstract method. It is now incumbent upon a derived class to supply an overriding method, with a coded body, to turn this abstract method into a functioning derived method.

 
Abstract methods give you a consistent interface for methods that all derived classes are to override. Derived objects often have many additional data members. Suppose you know in advance that a class will be derived three times by three very different child classes. If all three child classes were to perform a printing task, only they would perform that task very differently depending on the make-up of their data members, you could supply an abstract class named printThis() in the original parent class. The parent class would not be abstract so you could not instantiate objects directly from the parent. Only when you derive classes, add data members, and supply the derived method code bodies, would any printThis() method be operational. The parent class, therefore, ensures that all of its derived child classes code a printThis() class. Java will tell from the object's data type which printThis() to execute when you use an object to execute a method.

Static Methods

To understand a static method, you must understand a static data member. Suppose you defined a data member to be static as done in listing 12.3. Listing 12.3 looks like listing 12.2 except for the static data member and method.


Listing 12.3 Using a Static Member and Method

//*******************************************************************
// The Box class with a static member and method
//===================================================================
class Box // No inheritance
{
float area; // A class data member
static int colorCode; // A class data member
void calcArea (float width, float length)
{
area = width * length;
}
static void setColor (int colorValue)
{
colorCode = colorValue;
}
}

A static data member exists once no matter how many instance variables, or objects, you instantiate. Therefore, if you were to define an array of 100 Box objects, then set the static colorCode variable to 5, all 100 objects would have a colorCode value of 5. A static data member exists once. You create the static data member the first time you create an object. Thereafter, all objects contain that same static member.

With a static data member, one Box object cannot have a different colorCode value from another Box object. They all have the same colorCode value. A static method can work only with static data members. Therefore, if you want to allow your user a chance to set the color for all boxes, no matter how many Box objects have been or will additionally be instantiated, you could write the following code that asks the user for a color code value and sets the color for all instantiated Box objects that exist:


Int userColorCode;
System.out.print("What color code do you want for all the boxes?");
System.in.read(userColorCode); // The user types the code
hatHolder.setColor(userColorCode); // Set the user's color to all boxes

As long as you use one instantiated object to set the static data member, all the other objects will get the member. Remember that code cannot usually change an object's data member values directly, but methods can. The static method changes every static data member instantiated. If you then instantiate additional Box objects later in the program, those objects will also contain the same colorCode set for the current objects.

 
By limiting the access of your class data members, you can protect their values. For example, if outside code could directly change colorCode, a user could set the color to a meaningless value such as -32894 (color codes are always positive values). The method can use data-validation to check for appropriate colors before changing the protected data members like this:

static void setColor (int colorValue)
{
if (colorCode > 0)
{ colorCode = colorValue; } // The colorCode value is now set
else
{ System.out.println("You cannot use a negative color code.");
// The colorCode value is unchanged
}
}

Final Methods

By defining a method with the final keyword, you disallow a subclass from overriding the method with a new derived method. In other words, the parent class's method version will be sure to exist in all child classes, even if the child classes contain the same method name. If a child object triggers the method, the parent's method will execute and not the method defined (incorrectly) inside the child's class.

The following statement defines a final method:

final float freeMethod(int x, float y, char z)

Native Methods

Native methods are methods that you write in C++, not Java! By defining a method as native, as shown here:

native float freeMethod(int x, float y, char z);

you tell Visual J++ that the method exists in C++ and you'll supply that method later.

The semicolon at the end of the native method definition tells Visual J++ that this method does not contain any Java code so Visual J++ will not look for Java code within the native method's body.

Synchronized Methods

When you program in Java's threaded mode (where two or more threads, or program logic flows execute in parallel with each other), you should write synchronized methods for those methods that might attempt to access the same data at the same time.

For example, suppose that one method sets a boolean data member to true and another method prints an hourly report only if the first boolean data member is set to true. If you were to set up these methods without synchronizing them, the hourly check could execute right as the other method sets the boolean value to true. One of two results could occur:

The hourly report printing could be too critical to leave to chance. If you add the synchronized keyword before both methods, the methods will never execute at the same time and would always produce consistent results.

The following statement defines a synchronized method:

synchronized float freeMethod(int x, float y, char z)

Overloading Methods

Two or more methods can have the same name and yet, Visual J++ has no trouble compiling the code and knowing which method you want to execute. The trick is to change the parameter list in the methods. The overloaded methods must either have a different number of parameters or the parameter data types must differ.

Visual J++ considers these four method definitions to be different because the four methods are properly overloaded:


int overloaded(float x, char y)
int overloaded(float x)
int overloaded(int x, char y)
int overloaded()

Suppose you want to write a method that computes and returns the square root of its parameter. If you want the square root method to be general-purpose, the method should be able to handle whatever data type you pass to it. Without overloading, however, you would have to tediously write a separate method for each data type. Worse, however, is that you would have to call the correct method depending on the data type of the value you want the square of. Therefore, you might be faced with a situation of calling one of the following methods depending on the data type of the argument:


byte squareByte(byte p)
int squareInt(int p)
short squareShort(short p)
long squareLong(long p)
float squareFloat(float p)
double squareDouble(double p)

Surely you get the idea. Such classes would consume a terrible amount of your time and bugs could easily enter your program because you may not call the appropriate method for the data type you're passing. With overloading, you'll still need to write separate methods for each data type but you can give each function the same name as done in the following method definitions:


byte square(byte p)
int square(int p)
short square(short p)
long square(long p)
float square(float p)
double square(double p)

You now never have to worry about calling the correct method! If you have a byte variable, you'll find the byte variable's square root like this:

ans = square(myByteVar); // Passes a byte variable to the correct method

If you need to get the square of a variable defined with the double data type, you would find the double variable's square root like this:

ans = square(myDoubVar); // Passes a double variable to the correct method

 
Java cannot distinguish between overloaded methods based solely on the return data types. You must change the parameter count or parameter data types before Java can distinguish between two overloaded methods. Therefore, the following methods are not overloaded and cannot appear in the same class:

int notOverloaded(float x, char y) // More than return data type musts differ!
double notOverloaded(float x, char y)

Special Constructor Methods

Constructors are special methods that initialize an object's data members. If you've supplied a constructor for a class, the Visual J++ compiler ensures that the constructor method executes every time you create a new instance of the object. That is, every time you instantiate a new object, whose class contains a constructor, the constructor automatically executes.

You don't call a constructor method directly. The constructor executes on its own. A class method is a constructor as long as you define the method with these two rules:

  1. A constructor has the same name as its class.
  2. A constructor has no return data type; the return data type is always void.

If you define a class named Box, the following method would define Box's constructor method:

void Box()

Due to overloading, you can define multiple constructors. All of the following methods could be Box constructor methods within the same class:


void Box() // Default constructor
{} // No initialization takes place
void Box(float a)
{
area = a; // Initializes the area data member but not the color
}
void Box(int c)
{
colorCode = c; // Initializes the color but not the area
}
void Box(float a, int c)
{
area = a; // Initializes both data members
colorCode = c;
}

Such overloaded constructors might appear in your applet to offer several initializations for Box objects. As you'll recall from earlier in this chapter, an object's data members generally are not available to code outside the class. Therefore, if your applet defines an object from an imported class, your applet probably cannot initialize that object's data members directly.

The constructors give you several options you can use to define the data members of the Box objects you define. Suppose you want to instantiate a Box object but you don't yet know the values for the object's data members. Your applet could define the object like this:

Box aBox = new Box(); // No data members are initialized

To initialize the data members (assuming the members are unavailable as they usually are), you would have to call additional methods that initialize the data members once you learn the values. The constructor that takes no parameters, by the way, is called the default constructor.

Later in the same applet, you want to initialize a Box object and you know the color code you want that object to have at the time you define the object. If you called the second constructor like this:

Box blueBox = new Box(28.75); // A new object with its area definedc1

Java creates an object named blueBox and the area data member contains the value 28.75. As you can see, the parameter (or parameters) that you supply determines which constructor executes and also determines which data members get initialized. The constructor you decide to call in your program depends on how much initialization information you know when you instantiate your objects.

Default Constructors Call Parent Constructors

The default constructor is the constructor with no parameters. If you don't supply a default constructor, Java supplies a default constructor for you. The default constructor, either supplied by you or by the Visual J++ compiler, necessitates the empty parentheses after you instantiate an object with new as in this object instantiation:
 
Box aBox = new Box(); // Instatiates and uses the default constructor
 
The default constructor that you or that the Visual J++ supplies, looks as though no code executes but when you trigger a default constructor, the parent class's constructor executes!
Here's the execution chain: If you supply a constructor with parameters, your constructor will execute when you instantiate an object using the same parameter set. If you supply a default constructor and instantiate an object with no parameters, your default constructor triggers the object parent class constructor. If you do not supply a default constructor, the object class's parent constructor will still execute.
 
This obvious question might arise: What if I construct an object from a class that I've not derived from a parent class? It would seem in this case that no parent class constructor exists. The Java language provides a trick to fix this potential problem. All classes have a parent class! If you create a new class, Java secretly derives your class from an internal parent class named java.lang.Object . This default constructor does not do anything other than construct the object and does not initialize any data members.

Data Access Rights

As with classes and methods, you can modify the access rights to data members as well. You can modify the access to data using the following data access modifiers:

Public Data

When you define a class's data member as public, any code that uses the class can directly access and modify the data member. You could define the Box class's area data member like this:

public float area; // Available to all!

The public area data member is visible and available to any class, subclass, or method that uses Box objects.

Protected Data

When you define a class's data member as protected, only derived subclasses and other classes within the current class package can access the data member. No other code outside the class or outside a derived class can directly access or modify the protected data member. You could define the Box class's area data member like this:

protected float area; // Available only to this package's class and subclasses

Protected data members are safer than public members because only classes defined to access the data members can access the data. All other code must use the Box class's (or Box's derived class's) methods to view or change area.

Private Data

When you define a class's data member as private, only the current class can access or change the data member. Not even derived classes can access the private member.

You could define the Box class's private area data member like this:

private float area; // Available only to THIS class

Friendly Data

You define a class's data member as friendly when you don't use any other access modifier. The following statement defines the area data member as friendly because no other modifier appears:

float area; // A friendly modifier exists here

Only classes within this class's package can access or modify the friendly area. Derived subclasses do not have access so someone cannot derive a new class and expect to have access to the data.

Private Protected Data

When you define a data member as a private protected data member, that data is accessible only to the class and to all derived subclasses. You could define the Box class's private area data member like this:

private protected float area; // Available only to this class and subclasses

Static Data

As you learned earlier in this chapter, a static data member exists only once. No matter how many data objects you instantiate, a static data member exists only once and is the same for every object.

Final Data

When you define a data member as a final data member, that data is no longer a variable data member! The data member instantly becomes a named constant. A named constant is a named variable that's fixed to a specific value for the life of the final variable.

You must always specify the final value when you define a final data member. The following statement defines a final data member:

final float PI = 3.14159; // Unchanging!

Any code that uses this final variable's class can refer to PI but cannot change PI. PI is a fixed variable and its value can never change. Use named constants for mathematical constants (such as PI and e) as well as other literals that you'll use throughout a class. The named constant eases subsequent program maintenance because you don't have to remember what you're using a numeric literal for; the name can offer a clue as to the literal's use.

 
Final variables work like #define named constants in C and C++.

Scoping Considerations

A variable's scope refers to that variable's life or availability within code. When you define a variable, that variable exists only for the block in which it's defined. Therefore, x is not available to the second System.out.println method here:


{ // Starts a new block
int x = 14; // "Local" to this block
System.out.println("x is equal to " + x);
} // x goes completely away
{ // Starts a new block
System.out.println("x is equal to " + x); // Error!
}

Not only will the second System.out.println() method not be able to print x, but Visual J++ will not even compile this program! Visual J++ knows that x is outside the scope of the second block so the second block cannot be expected to print the value of a variable that does not exist.

An applet can even contain two variables with the same name as long as you define both variables in different blocks. Although two variables with exact names often makes for confusing debugging sessions, such variables are great for loop counters as in the following:


{ int loopCtr;
System.out.println("Even numbers below 20:");
for (loopCtr = 2; loopCtr <= 20; loopCtr+=2) // Adds 2 each iteration
{ System.out.println(loopCtr); }
}
// Later in the applet, perhaps in a different method
{ int loopCtr; // Same name, no conflict
System.out.println("Odd numbers below 20:");
for (loopCtr = 1; loopCtr <= 20; loopCtr+=2) // Adds 2 each iteration
{ System.out.println(loopCtr); }
}

 
You can localize for loop control variables even further by defining them inside the for like this:
 
for (int loopCtr = 1; loopCtr <= 20; loopCtr+=2)
 
The loopCtr variable will exist only for the for loop and its body.

When you define a data member (an instance variable), that data member exists for every method throughout the entire class, not just in a single block.

Two Special Data Variables: this and super

The this variable lets a class refer to itself. Suppose you create a class with a data member and also include a method that has the same data member name. Although such name duplications are not recommended, some applets make sense to have them. When the method refers to its own data member, you can refer just to the method's data member name. However, when the method needs to refer to the class's data member, use this to scope the data member to the class's data member. In this situation, value would be the method's data member and this.value would be the class's.

 
You'll also see this used when a class passes itself as a parameter to a method.

The super variable refers to the most recent parent class. Therefore, if you want to refer to a parent class's data member or execute a parent class's method, because the subclass contains more functionality than you want, precede the data or method with super. Therefore, super.value refers to the current class's parent version of value.

These two special variables will make more sense as you continue to grow your OOP skills and come upon programs that require this and super.

Summary

The goal of this chapter was to teach you the basics of object-oriented programming. Now that you've completed the chapter, you should have a much better idea what classes, methods, and data are all about. The new operator creates the class objects that your program can use. An object is much more than just a variable that holds data; objects contains methods so objects are active.

The next chapter starts a new section of this book. You will learn all about the built-in methods that Visual J++ provides. You will also see how to implement much of the OOP-related features that you learned here.

Here are the points this chapter covered:


Previous Page TOC Next Page

| Previous Chapter | Next Chapter |

| Table of Contents | Book Home Page |

| Que Home Page | Digital Bookshelf | Disclaimer |


To order books from QUE, call us at 800-716-0044 or 317-361-5400.

For comments or technical support for our books and software, select Talk to Us.

© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.