by Joe Weber
Classes are the major building block of an object-oriented structure. In fact, classes are what make objects possible, and without objects, object-oriented programming would just be oriented programming which, well...would not make sense. There are several major advantages to using objects. They enable you to encapsulate data, keeping all information and actions about a particular item separate from the rest of your code. They allow you to build class hierarchies, which enables you to build up more and more complex structures from simpler ones. Lastly, through a technique called polymorphism, dissimilar objects that share a common attribute can be utilized by their similarities.
From a common-sense view, classes are a way to assemble a set of data and then determine all of the methods needed to access, use, and change that data.
Fundamentally, every class has two major portions. The first portion is that of state. The state of an object is nothing more than the values of each of its variables. If, for instance, you had a class StopLight with one variable, RedGreenYellow, the state of the StopLight would be determined by the value of RedGreenYellow.
public class StopLight{ int RedGreenBlue; }
The second portion of a class is its tools, or methods. The methods of a class determine the utility the class has. In the case of the StopLight, it is likely that you would have a method called changeLight(), which would cause the light to change from red to green (probably by changing the RedGreenYellow variable).
public class StopLight{ int RedGreenBlue; changeLight(){ RedGreenBlue = ++RedGreenBlue%3; } }
NOTE: To distinguish class variables with variables that are parts of methods, class variables are often referred to as fields, or class scope variables. In the previous example, the RedGreenYellow variable would be a field of the StopLight class.
When dealing with classes, it is important to remember that classes do not enable programmers to do anything more than what they would be able to do without them. While it might be significantly more work, you could write all OOP programs structurally.
So why use classes? The answer to this question is similar to the reason why large companies are divided into departments and sub-departments. By organizing hundreds of people with thousands of tasks, the department architecture provides for a simple distribution of tasks and responsibilities. Furthermore, because the billing department knows how to bill customers, the sales department does not need to worry about those details. By doing this work, the billing department has effectively encapsulated the work of billing within itself.
However, the power of object-oriented programming extends beyond the simple ability to encapsulate functionality in objects. A great deal of the appeal of OOP is its ability to provide inheritance--the ability to create new classes based on old classes. As an example of inheritance, consider a game board. Assume that you wrote a Checkers game a couple of months ago, and would now like to write a chess game. By using traditional programming techniques, you would start from scratch, or maybe cut and paste some of your old code. Using inheritance can eliminate most of this work. Instead, you can build upon the code you wrote for your Checkers game. Override only those methods that behave differently than Checkers, and add only those methods that Checkers simply doesn't need.
NOTE: When new classes inherit the properties of another class, they are referred to as child classes or subclasses. The class from which they are derived is then called a parent or superclass.
Finally, the allure of the OOP approach to creating self-sustaining modules is further enhanced by the fact that children of a given class are still considered to be of the same "type" as the parent. This feature, called polymorphism, enables you to perform the same operation on different types of classes as long as they share a common trait. While the behavior of each class might be different, you know that the class will be able to perform the same operation as its parent because it is of the same family tree. For example, if you were to create a Vehicle class, you may later choose to create Truck and Bike classes, each extending the Vehicle class. Although bikes and trucks are very different, they are both still vehicles! Therefore, everything that you are permitted to do with an instance of the Vehicle class you may also do with an instance of the Truck or Bike classes. A car dealership, then, need not worry if it is selling a Volvo or Saturn. The lot is simply full of vehicles.
What's So New About Object-Oriented Programming?
OOP emphasizes a modular view of programming by forcing you to break down your task into manageable components, each with a specific function. However, unlike procedural functions, which are simply pieced together to form a program, objects are living "creatures" that have the ability to manage themselves, running concurrently with other operations and even existing after the rest of the program has terminated. It is this ability to exist and work with objects as a separate entity that makes OOP a nice match for Java, a network-based language.
CAUTION:
In the previous example, while every bike and truck is also a vehicle, a vehicle is not necessarily a bike or a truck. Thus, while the Bike and Truck classes can be treated just like the Vehicle class in Java, you may not perform an operation reserved for the Bike class on an instance of the Vehicle class.
As stated at the beginning of this chapter, classes are the essential building block in any Java applet or application. Classes are used to create objects. When you create an instance of a class, you create an object. You can include all the code for that object within the class. In accordance with the object-oriented paradigm, you can later choose to build upon that class to build new programs or enhance your current program.
Bigger and Better Java
Java itself is built from classes that are made available to the general public in the JDK. While there are some limitations, a large number of the classes that make up the Java architecture may them- selves be extended. By doing this, you may tailor the classes in the Java API library--especially those in the AWT--to meet your particular needs.
public class GameBoard { /* This is the beginning a simple game board class that provides the basic */ /* structures necessary for a game board. It may easily be */ /* extended to create a richer game board. */ private static final int WIDTH = 10; /* These are constants */ private static final int HEIGHT = 10; /* that you want to */ private static final int EMPTY = 0; /* keep as standards */ private int board[][]; // This array will keep track of the board public String myname; // what game is being played public GameBoard (String gamename) { board = new int[WIDTH][HEIGHT]; myname = new String(gamename); } public final void cleanBoard() { for (int i = 0; i < WIDTH; i++) for (int j = 0; j < HEIGHT; j++) board[i][j] = EMPTY; } public synchronized void setSquare(int x, int y, int value) { board[x][y] = value; } public synchronized boolean isEmpty(int x, int y) { if (board[x][y] == EMPTY) return(true); return(false); } }
Take a quick look through this class. The first part of any class is the class declaration. Most classes you write will look very similar to GameBoard:
public class GameBoard
Declaring a class states several things, but probably the most important one is the name of the class (GameBoard). In the case of any public class, the name of the class must also match up with the name of the file it is in. In other words, this class must appear in the file GameBoard.java.
The next part of the class is the opening brace. You should notice that there is a brace ({) at the beginning of the class, and if you look all the way down at the bottom there is also a closing brace (}). The braces define the area in the file where the class definitions will exist.
A bit farther down you will see several comments. As you learned in "Comments" (Chapter 7), comments can exist anywhere in the file and are ignored by the compiler, but they help you leave messages for yourself or other programmers.
Next you will see several fields declared. Each of these variables is accessible from any of the methods in the class. When you change them in one method, all the other methods will see the new value.
private static final int WIDTH = 10; /* These are constants */ private static final int HEIGHT = 10; /* that you want to */ private static final int EMPTY = 0; /* keep as standards */ private int board[][]; // This array will keep track of the board public String myname; // what game is being played
Finally, you should see four methods.
public GameBoard (String gamename) { board = new int[WIDTH][HEIGHT]; myname = new String(gamename); } public final void cleanBoard() { for (int i = 0; i < WIDTH; i++) for (int j = 0; j < HEIGHT; j++) board[i][j] = EMPTY; } public synchronized void setSquare(int x, int y, int value) { board[x][y] = value; } public synchronized boolean isEmpty(int x, int y) { if (board[x][y] == EMPTY) return(true); return(false); } }
In general, Java class declarations have the form:
modifiers class NewClass extends NameofSuperClass implements NameofInterface
where everything in italics is optional. As you can see, there are four properties of the class that may be defined in the declaration:
The modifiers in a class declaration determine how the class can be handled in later development and are very similar to those four modifiers discussed in Chapter 8, "Methods." While they are usually not extremely important in developing the class itself, they become very important when you decide to create other classes, interfaces, and exceptions that involve that class.
When creating a class, you may choose to accept the default status or you may
employ one of the three modifiers: public, final, or abstract.
Public Classes
public class PictureFrame
Also note that public classes must be defined in a file called ClassName.java
(for example, PictureFrame.java).
"Friendly" Classes
class PictureFrame
Final Classes
It is important to remember that the object-oriented approach effectively enables you to create many versions of a class (by creating children that inherit its properties but nevertheless change it somewhat). Consequently, if you are creating a class to serve as a standard (for example, a class that will handle network communications), you would not want to allow other classes to handle this function in a different manner. Thus, by making the class final, you eliminate this possibility and ensure consistency. Here's an example:
final class PictureFrame
Abstract Classes
abstract class PictureFrame
How can a finished class not be complete? In the case of a grammar-checking class that is to be implemented in many languages, there are several methods that would have to be changed for each language-dependent version class. To create a cleaner program, instead of creating an EnglishChecker, a FrenchChecker, and a SpanishChecker class from scratch, you could simply create a GrammarChecker class in which the language-specific methods are declared as abstract and left empty. When ready, you could then create the language-specific classes that would extend the abstract GrammarChecker class and fill in the blanks by redefining these methods with actual code. While you would still end up with separate classes for each language, the heart of your code would be in the GrammarChecker class, leaving only the language-dependent portions for the specific classes.
NOTE: Because they are not complete, you may not create instances of abstract classes.
NOTE: The class declaration need not be very complex, and most often is very simple. In this example, only one modifier, public, was used; no other classes or interfaces were required:public class GameBoard
Like all other Java identifiers, the only requirements on a class name are that it:
Also, it is general practice to capitalize the first letter in the name of any class.
TIP: Although only required for public classes, it is generally a good practice to name the file in which class NewClass is defined NewClass.java. Doing so helps the compiler find NewClass, even if NewClass has not been compiled yet.
One of the most important aspects of OOP is the ability to use the methods and fields of a class you have already built. By building upon these simpler classes to build bigger ones, you can save yourself a lot of coding. Possibly even more important, you can greatly reduce the work of finding and fixing bugs in your code. In order to build upon a previous class, you must extend the class in the class declaration.
By extending a super class, you are making your class a new copy of that class but are allowing for growth. If you were simply to leave the rest of the class blank (and not do anything different with the modifiers), the new class would behave identically to the original class. Your new class will have all of the fields and methods declared or inherited in the original class.
NOTE: Does this example look familiar?
public class MyClass extends Applet {If you look at the source of any applet, you see that its declaration resembles the example. In fact, you probably have been extending the java.applet.Applet class without even knowing what you were doing.
Remember the methods you have been able to use in your applets, such as showStatus(), init(), and keyDown()? Did they appear out of thin air? No, they are drawn from the java.applet.Applet class or one of the classes that it extends, such as java.awt.Component. By extending the java.applet.Applet class, your applet class is able to access and implement these methods, thereby providing your applet with a great deal of power.
NOTE: Multiple-inheritance does not exist in Java. Thus, unlike C++, Java classes may only extend one class.
Constructors are very special methods with unique properties and a unique purpose. Constructors are used to set certain properties and perform certain tasks when instances of the class are created. For instance, the constructor for the GameBoard class is:
public GameBoard (String gamename) { board = new int[WIDTH][HEIGHT]; myname = new String(gamename); }
Constructors are identified by having the same name as the class itself. Thus, in the GameBoard class, the name of the constructor is GameBoard(). Secondly, constructors do not specify a return argument because they are not actually called as a method. For instance, if you wanted to create an instance of the GameClass, you would have a line that looked like this:
GameClass myGame = new GameClass();
When the new GameClass() is actually instantiated, the constructor method is called.
In general, constructors are used to initialize the class's fields and perform various tasks related to creation, such as connecting to a server or performing some initial calculations.
Also note that overloading the constructor enables you to create an object in several different ways. For example, by creating several constructors, each with a different set of parameters, you enable yourself to create an instance of the GameBoard class by specifying the name of the game, the values of the board, both, or neither. This practice is prevalent in the Java libraries themselves. As a result, you can create most data types (such as java.lang.String and java.net.Socket) while specifying varying degrees and types of information.
TIP: Most programmers choose to make their constructors public. This is because if the level of access for the constructor is less than the level of access for the class itself, another class may be able to declare an instance of your class but will not actually be able to create an instance of that class. However, this loophole may actually be used to your advantage. By making your constructor private, you may enable other classes to use static methods of your class without enabling them to create an instance of it.
It is not legal to create two methods with the same name and parameter list within the same class. After all, doing so would just confuse the whole system (which method would you really want to be calling?). However, one of the purposes of extending a class is to create a new class with added functionality. To allow you to do this, when you inherit another class, you can override any of its methods by defining a method with the same name and parameter list as a method in the superclass. For instance, consider an Elevator class.
class Elevator { ... private boolean running = true; ... public void shutDown() { running = false; } }
Now, at some point you realize that this elevator just isn't very safe, so you decide to create a safer one. You want to extend the old Elevator class, and maintain most of its properties, but change some as well. Specifically you want to check to make sure the elevator car is empty before stopping, so you override the shutDown() method as shown in the following code:
class SaferElevator extends Elevator { ... public void shutDown() { if ( isEmpty() ) running = false; else printErrorMessage(); } }
Note that overriding is accomplished only if the new method has the same name and par-ameter signature as the method in the parent class. If the parameter signature is not the same, the new method will overload the parent method, not override it. For instance, if you had created a class like:
class SaferElevator extends Elevator { ... public void shutDown(int delay) { if ( isEmpty() ) running = false; else printErrorMessage(); } }
the shutDown method from the Elevator class would not have changed. Adding the parameter (int delay) to the method changes what is known as the method signature.
NOTE: When you overload a method, you may not make it more protected than the original method. Because the shutDown method is public in Elevator, you cannot make it private in SaferElevator
In order to use a class you have created, you need to be able to create an instance of that class. An instance is an object of the type of the class. Any class you create can be instantiated, just like any other data type in Java. For example, to create an instance of the GameBoard, you would generally declare a variable of that type. The following code fragment shows a class called Checkers creating an instance of the GameBoard class:
public class Checkers{ GameBoard myBoard = new GameBoard(); .... }
As you may have noticed, the one primary difference between declaring an Object type and a primitive type like int is the use of the new keyword. new performs several key tasks:
- Tells the computer to allocate the space necessary to store a GameBoard in.
- Causes the constructor method of GameBoard to be called.
- Returns a reference to the object (which is then assigned to myBoard).
NOTE: One additional difference in Java between objects and primitive types is how they are referenced. Primitive types are always referred to by their value. Object types are always referred to by their reference. This means that in the following code, x and y are not equal at the end, but in w and z myName is the same:
int x = 5; int y = x; y++; // x = 5, y =6; GameBoard w = new GameBoard(); GameBoard z = w; w.myName = "newString"; //Since z and w point to the same object, they now both have the same myName
Now that you have begun to develop classes, examine how they may be used in other classes. As discussed earlier in the section "Why Use Classes?", Java classes may contain instances of other classes that are treated as variables. However, you may also deal with the fields and methods of these class type reference variables. To do so, Java uses the standard dot notation used in most OOP languages. See the following example:
public class Checkers { private GameBoard board; public Checkers() { board = new game board("Checkers"); board.cleanBoard(); } ... public void movePiece(int player, int direction) { java.awt.Point destination; ... if (board.isEmpty(destination.x, destination.y) ) // code to move piece } private void showBoard(Graphics g) { g.drawString(board.myname,100,100); drawBoard(g); } }
Notice that board is an instance in the GameBoard class, and that the variable myname in the GameBoard class is referenced by board.myname. The general notation is instanceName.methodOrVariableName.
CAUTION
Notice that the variable myname is referred to as board.myname, not as GameBoard.myname. If you try to do so, you get an error resembling:Checkers.java:5: Can't make a static reference to non-static variable myname in class GameBoard.This is because GameBoard is a type of class, while board is an instance of the class. As discussed in the previous section, when you deal with board, you deal with a specific copy of the GameBoard class. Because myname is not a static variable, it is not a property of the GameBoard class, but rather a property of the instances of that class. Therefore, it cannot be changed or referenced by using GameBoard as the variable name.
As seen in the following example, the TextScroll class would then be able to display the information across the bottom of the Presentation class's screen.
public class Presentation extends Applet { TextScroll scroller; public void init() { ... scroller = new TextScroll(this, length_of_text); scroller.start(); } ... } class TextScroll extends Thread { Presentation screen; String newMessage; boolean running; int size; TextScroll(Presentation appl, int size) { screen = appl; } public void run() { while (running) { displayText(); } } void displayText() { // perform some operations to update what should // be displayed (newMessage) screen.showStatus(newMessage); } }
See "What Are Threads?" Chapter 13
While the concepts of threads and their uses are discussed in later chapters,
note the use of the special this variable in the init() method
of the Presentation class as well as the result. This technique is extremely
useful and powerful.
Super Special Variable
class NewGameBoard extends game board { private static int FIXEDWALL = 99; // permanent wall, cannot be moved public static synchronized void setSquare(int x, int y, int value){ if (board[x][y] != FIXEDWALL) { super.setSquare(x,y,val); } }
In the preceding example, you use the super variable to refer to the original version of the setSquare() method, found in the GameBoard class. By doing so, you save yourself the headache of recopying the entire method, while at the same time allowing you to add to the functionality of the setSquare method.
You should also examine how to call the super method if the method you are dealing with is a constructor. It is necessary to call the constructor for a parent class, just as you need to call the constructor for any class. While calling a super constructor is not much different from any other super method, its syntax may seem confusing at first.
public NewGameBoard(String gamename) { // new code would go here super(gamename); }
Note that on a simplistic level, super can be considered equivalent to GameBoard. Consequently, because GameBoard() is the name of the original constructor method, it may be referred to as super().
Obviously, variables are an integral part of programs and, thus, classes as well. In Chapter 7, "Data Types and Other Tokens," you examined the various types of variables, but now you must also consider how they are employed in your programs and the different roles they may assume.
When creating variables, whether they are as simple as integers or as complex as derived classes, you must consider how they will be used, what processes will require access to the variables, and what degree of protection you want to provide to these variables.
The ability to access a given variable is dependent on two things: the access modifiers used when creating the variable and the location of the variable declaration within the class.
See "Literals--Assigning Values," Chapter 7
Class Fields Versus Method Variables
In a class, there are two types of variables: those belonging to the class itself and those belonging to specific methods.
Those variables declared outside of any methods, but within a given class (usually immediately after the class declaration and before any methods), are referred to as fields of the class and are accessible to all methods of it.
In addition, one may declare variables within a method. These variables are local to the method and may only be accessed within that method.
- Because method variables exist only for the lifetime of the method, they cannot be accessed by other classes. Consequently, you cannot apply any access modifiers to method variables.
Furthermore, inasmuch as OOP is heavily dependent on the modification of code that you have written beforehand, access restrictions prevent you from later doing something that you shouldn't. (Keep in mind that preventing access to a field does not prevent the use of it.) For example, if you were creating a Circle class, there would most likely be several fields that would keep track of the properties of the class, such as radius, area, border_color, and so on--many of which may be dependent on each other. Although it may seem logical to make the radius field public (accessible by all other classes), consider what would happen if a few weeks later you decided to write the following:
class Circle { public int radius, area; ... } class GraphicalInterface { Circle ball; ... void animateBall() { for (int update_radius = 0; update_radius <= 10; update_radius++){ ball.radius = update_radius; paintBall(ball.area, ball.border_color); ... } } }
This code would not produce the desired result. Although the
ball.radius = update_radius;
statement would change the radius, it would not affect the area field. As a result, you would be supplying the paintBall() method with incorrect information. Now, instead, if the radius and area variables are protected, and any update to the radius forced the area to be recomputed, the problem would disappear as shown in the next set of code:
class Circle { protected int radius, area; public void newRadius (int rad){ radius = rad; area = rad *2 * Math.PI; } public int radius(){ return radius; } public int area (){ return area; } } class GraphicalInterface { Circle ball; ... void animateBall() { for (int update_radius = 0; update_radius <= 10; update_radius++){ ball.newRadius (update_radius); paintBall(ball.area(), ball.border_color); ... } } }
In the next few sections, you examine the various ways of regulating access and solving this problem.
While it is important to consider the level of access that other objects will have to your fields, it is also important to consider how visible the fields and method variables will be within your class. Where the variable is accessible, a property called its scope, is a very important topic. In general, every variable is accessible only within the block (delimited by the curly braces { and } ) in which it is declared. However, there are some slight exceptions to this rule. Examine the following code:
class CashRegister { public int total; int sales_value[]; Outputlog log; void printReceipt(int total_sale) { Tape.println("Total Sale = $"+ total_sale); Tape.println("Thank you for shopping with us."); } void sellItem(int value) { log.sale(value); total += value; } int totalSales() { int num_of_sales, total = 0; num_of_sales = log.countSales(); for (int i = 1; i <= num_of_sales; i++) total += sales_value[i]; return(total); } }
Now examine some of the variables and their scope:
Variable Name | Declared As | Scope |
total | Field global to CashRegister class | Entire class |
total | Local to totalSales() method | Within totalSales() |
log | Field global to CashRegister class | Entire class |
value | Parameter to sellItem() | Within sellItem() |
i | Local to totalSales() within for loop | Within the for loop |
for (int x = 0; x<10 ;x++){ for (int i =0;i < num_of_sales; i++ ) ... }
Finally, you arrive at the problem of having two total variables with overlapping scope. While the total field is accessible to all methods, a problem seems to arise in the totalSales() method. In such cases, using the multiply-defined identifier refers to the most local definition of the variable. Therefore, while having no impact on the rest of the class, within the totalSales() the identifier total refers to the local variable total, not the global one. This means that after exiting the totalSales() method, the total class variable is unchanged. In such a situation, you can access the variable with class scope by using the this keyword. To set the class variable total to the method variable total, you would type:
this.total = total;
While using an identifier as a field and method variable name does not cause many
problems and is considered an acceptable practice, it is preferable to choose a different
(and more descriptive) identifier, such as total_sales.
NOTE: While you are able to use the same identifier as a field and a variable within a method, this does not apply to all code blocks within your code. For example, declaring num_of_sales as your counter within the for block would produce an error.
TIP: If you do create a method variable with the same name as a field and need to refer to the field rather than the method variable, you may do so with the this variable, as explained earlier in this chapter in the section "This Special Variable."
Like the modifiers for classes and methods, access modifiers determine how accessible certain variables are to other classes. However, it is important to realize that access modifiers apply only to the global fields of the class. It makes little sense to speak of access modifiers for variables within methods because they exist only while the method is executing. Afterwards, they are "collected" to free up memory for other variables.
Why Not Make All Variables Fields?
Because all class variables (fields) are accessible to all methods in a given class, why not make all variables fields global to all methods in the class?
The first reason is that you would be wasting a great deal of memory. While local variables (those variables declared within the methods themselves) exist only while the method is executing, fields must exist for the lifetime of the class. Consequently, instead of allocating memory for dozens of fields, by making many of your variables local, you are able to use the same piece of memory over and over again.
- The second reason is that making all your variables global would create sloppy programs that would be hard to follow. If you are going to be using a counter only in one method, why not declare it in that method? Furthermore, if all of your variables are global, someone reviewing your code (or you, a few weeks later) would have no idea from where the variables were obtaining their values, since there would be no logical path of values being passed from method to method.
int size;
public
public int size;
protected
protected int size;
private
private int size;
private protected
Example: private protected int size;
static
static int size;
See Chapter 8, "Methods."
final
final int SIZE = 5;
If the value cannot change, why not use the value itself within the program? The answer to this question is twofold:
While it may be advantageous to restrict access to certain fields in your class, it is nevertheless often necessary to provide some form of access to those fields. A very intelligent and useful way of doing this is to allow access to restricted fields through less restricted methods, such as in the following example:
class Circle { private int radius, area; private Color border_color; public void setRadius(int update_radius) { radius = update_radius; area = Math.PI * radius * 2; } public Color getColor() { return(border_color); } public int getRadius() { return(radius); } public int getArea() { return(area); } } class GraphicalInterface { Circle ball; ... void animateBall() { for (int update_radius = 0; update_radius <= 10; update_radius++){ ball.setRadius(update_radius); paintBall(ball.getArea(), ball.getColor() ); } ... } }
By limiting access to the radius field to the setRadius() method, you ensure that any change of the radius will be followed by an appropriate change of the area variable. Because you have made the two fields private, you must also provide yourself with the means of accessing them through the various get-type methods. These methods are commonly referred to as accessor methods because they provide access to otherwise inaccessible fields. While at first this may seem a bit cumbersome, its benefits by far outweigh its disadvantages. As a result, it is a very widely used approach that is extremely prevalent in the Java API libraries on which Java is heavily dependent.
Belonging to the java.lang.Object class, and thus present in all classes, is the finalize() method. Empty by default, this method is called by the Java runtime system during the process of garbage collection and, thus, may be used to clean up any ongoing processes before the object is destroyed. For example, in a class that deals with sockets, it is good practice to close all sockets before destroying the object defined by the class. Therefore, you could place the code to close the sockets in the finalize() method. Once the instance of the class is no longer being used in the program and is destroyed, this method would be invoked to close the sockets as required.
The finalize() method is very similar to the ~classname()
method in C++.
For example,
public class NetworkSender { private Socket me; private OutputStream out; public NetworkSender(String host, int port) { try { me = new Socket(host,port); out = me.getOutputStream(); } catch (Exception e) { System.out.println(e.getMessage(); } } public void sendInfo(char signal) { try { out.write(signal); out.flush(); } catch (Exception e) { System.out.println(e.getMessage()); } } public void disconnect() { System.out.println("Disconnecting..."); try { me.close(); } catch (Exception e) System.out.println("Error on Disconnect" + e.getMessage()); System.out.println("done."); } /* In this case finalize() is the identical to disconnect() /* /* and only attempts to ensure closure of the socket in the /* /* case that disconnect() is not called. */ protected void finalize() { System.out.println("Disconnecting..."); try { me.close(); } catch (Exception e) System.out.println("Error on Disconnect" + e.getMessage()); System.out.println("done."); } }
finalize() is declared to be protected in java.lang.Object and thus must remain protected or become less restricted.
CAUTION
While the finalize() method is a legitimate tool, it should not be relied upon too heavily because garbage collection is not a completely predictable process. This is because garbage collection runs in the background as a low-priority thread and is generally performed when you have no memory left. Consequently, it is a good practice to attempt to perform such "clean-up" tasks elsewhere in your code, resorting to finalize() only as a last resort and when failure to execute such statements will not cause significant problems.
With the new Java 1.1 compiler, Sun has added some new features to the language. One of these is called Nested classes. Nested classes can only be compiled using a Java 1.1 compiler, but the code that is generated with them is 100 percent backward-compatible to Java 1.0. This feature was necessary to allow virtual machines that comply with the 1.0 specification to be able to run applications written using the new features.
Nested classes are classes that are actually included within the body of another class. In fact, you can include a class within a method. Nested classes are primarily useful to programmers because they can help you to structure your code in a more organized fashion. In addition, in some cases they can add to the readability of the code.
You may wonder why you would ever want to do this. The reality is that you are never required to develop anything using inner classes. However, inner classes provide you with the ability to organize your code in a more understandable fashion, and occasionally provide the compiler with a means to further optimize the final code. It is also true that you can produce identical results by placing the inner classes in their own scope.
At this point, if you're one of those programmers who rode out the evolution of C++, you might be wondering whether or not inner classes are just one of those concepts that seemed like a good idea to the designers at the time, but that ends up only causing confusion. Wasn't Java supposed to avoid these pitfalls? Wasn't that the rationalization for avoiding operator overloading and other useful but confusing aspects of languages such as C++? Well, the unfortunate answer is maybe. Time will tell as to how well inner classes are accepted by the developer community as a whole. Regardless of what your own view is, it's very important to understand how to utilize inner classes in case you find yourself editing code from individuals who do utilize the power of inner classes. With that spirit, forge ahead and look at how inner classes work.
The major advantage of inner classes is the ability to create what are known as adapter classes. Adapter classes are classes that implement an interface. By isolating individual adapters into nested classes you can, in essence, build a package-like structure right within a single top-level class.
Take a look at an example that uses an adapter class. Listing 11.2 demonstrates how two individual and separate Runnable interfaces can be created in the same class. Both of these interfaces need access to the variable currentCount of the top-level class.
/* * * BigBlue * */ public class BigBlue implements Runnable{ int currentCount; class Apple implements Runnable { public void run(){ while(true){ System.out.println("count="+currentCount); try{ Thread.sleep(100); }catch (Exception e){} } } } public Runnable getApple(){ return new Apple(); } public void run(){ while(true){ currentCount+=5; try{ Thread.sleep(75); }catch (Exception e){} } } public static void main(String argv[]){ BigBlue b = new BigBlue(); Thread appleThread = new Thread (b.get Apple()); appleThread.start(); Thread thisThread = new Thread (b); thisThread.start(); } }
As you look at the example above, notice that the run() method of BigBlue has access directly to the currentCount variable, because currentCount is a field of the BigBlue class. This works just like any other method. Now take a look at the Apple class. This class also has access to the currentCount variable, and it accesses it just like it was its own, only it's not; it's received from the top-level class BigBlue.
In order to compile this program, it's not necessary to compile both Apple and BigBlue, just the BigBlue class:
javac BigBlue.java
To run the program type:
java BigBlue
What you will end up seeing are a sequence of numbers. Notice that since the sleep time in the BigBlue thread is a bit shorter than the Apple one, every once in a while the numbers will increment faster. This was done to demonstrate to you that they were in fact two different threads, running in two completely different loops.
CAUTION:
If, when you compile a class containing an inner class, you get an error similar to:bigBlue.java:30 :no enclosing instance of class bigBlue is in scope; an explicit one must be provided when creating class bigBlue. apple, as in outer. new inner() or outer.super().
Thread appleThread = new Thread (new apple());You may be very confused. To explain this error, look at what the main method would be that might generate this error:
public static void main(String argv[]){ bigBlue b = new bigBlue(); Thread appleThread = new Thread (new apple()); appleThread.start(); Thread thisThread = new Thread (b); thisThread.start(); }What causes this error is an attempt to create a new apple() inside of the static main method. In order to be able to access the apple class, you must do so in a non-static instance of BigBlue.
At this point you're probably wondering how inner classes work. Under Java 1.0, inner classes were not available. So, how did Java designers make the programs that you write using inner classes work with virtual machines that were designed from the 1.0 specification? The answer is that inner classes aren't really new. The solution lies in the fact that when you write a class with an inner class in it, the compiler takes the inner class outside of the main class, and just adjusts the compiled result.
Again, if you're one of those programmers who rode the change in the early days of C++, inner classes will spark a note. The reason is that in the beginning of C++, C++ was really C wrapped in an object-oriented shroud. When you wrote a C++ program, the C++ compiler actually just converted your C++ code into C code, and then a C compiler did the real compilation. Well, with Java 1.1 you don't actually need two compilers, but the end result is very similar.
You might be saying to yourself, "Why should I ever utilize an inner class?" The answer, as indicated at the beginning of this section, is to organize your code in a more suitable fashion. Sun's documentation refers to these inner classes as Adapter classes. To understand why, look at what inner classes are usually used for.
An inner class can extend or implement any interface you would like. So can an ordinary class. The only problem is that when a standard class implements an interface, it's often difficult to locate where the methods associated with the interface are located within the code.
When you start creating a large number of classes for a program, it is helpful to be able to keep them together. A clutter of class files is not unlike how your hard drive would look without subdirectories or folders. Imagine if all the files on your hard drive were placed in a single folder. You would have thousands of files, and you would have to make sure that none of them had the same name.
Class files by themselves must comply with this same arrangement. That's a fairly rigid requirement. To overcome this, Java has a system called packages. You can think of each package as a subdirectory. You have already seen how a number of packages are used in the Java API. java.awt, for instance, is a package, java.lang is another package, and so on.
Packages in Java are groups of Classes. These are similar to libraries in many computer languages. A package of Java classes typically contains related classes. You can imagine a package called Transportation, which would have numerous classes defined in it such as Car, Boat, Airplane, Train, Rocket, AmphibiousCar, SeaPlane, and so on. Applications that deal with items of this sort might benefit from importing the imaginary Transportation package.
To make a class a member of a package, you must declare it using the package statement:
package Transportation;
Some unique requirements go along with the package statement, however:
Legal | Illegal |
package Transportation | import java.applet.Applet; |
import java.awt.Graphics; | import java.awt.Graphics; |
import java.applet.Applet; | package Transportation; |
Once a file has been declared to be part of a package, the actual name for the class is the package name dot (.) and the name of the class. In other words, in our Transportation example, the Car class would be Transportation.Car, where before it would have been simply Car.
This leads to a small problem with an easy solution. If you write a program and then later decide to make all of the classes a member of a package, how does the compiler find the other files? Before, they were called Car and Van. Now, you must import them as Transportation.Car in order to use them. In other words, as shown here, where before you imported Car, you must now import Transportation.Car:
Old | New |
import Car; | import Transportation.Car |
It is also possible to import the entire contents of a package or all of the classes in that package. You have probably already seen this done with some of the JDK classes such as java.awt. To import all the classes, replace the individual class name with the wild card (*):
import java.awt.*;
By importing entire packages, you give yourself access to every class in the package. This can be very convenient, because you don't need to make up a big list like:
import java.awt.Graphics; import java.awt.Image; import java.awt.Button; import java.awt.Canvas; ...
Now, if you're thinking, "That seems simple; why don't I just import the entire package all the time?" The answer lies in the fact that there are a couple of drawbacks to importing the entire package:
You may have not realized this before, but it is not necessary to actually import a class before you use it. Ordinarily, classes in the null package (default) and that reside in the same physical directory can be used without doing anything. For instance, if there are two classes Car and Van in the same directory, you can create an instance of Car in the Van class without actually importing the Car class. Listings 11.3 and 11.4 show two such classes.
//Car is just a generic class with a few variables public class Car { int wheels; int tires; int speed; //simple constructor public Car (int inWheels, int inTires, int inSpeed){ wheels=inWheels; tires = inTires; speed = inSpeed; } }
//The Van class is another simple class, but uses the Car class public class Van { //The Car class is used here without being imported Car theCar; int doors; //simple constructor public Van (Car inCar, int inDoor){ theCar= inCar; doors= inDoor; } }
When you place a class in a package, you can still use the class with out importing it. The only difference is that you must use the full class name when declaring the instance. Listings 11.5 and 11.6 are identical to 11.3 and 11.4 except that Car is a member of the Transportation package.
package Transportation; //Car is just a generic class with a few variables public class Car { int wheels; int tires; int speed; //simple constructor public Car (int inWheels, int inTires, int inSpeed){ wheels=inWheels; tires = inTires; speed = inSpeed; } }
//The Van class is another simple class, but uses the Car class public class Van { //The Car class is used here without being imported Transportation.Car theCar; int doors; //simple constructor public Van (Car inCar, int inDoor){ theCar= inCar; doors= inDoor; } }
NOTE: While you do not need to import a package to use the classes, doing so affords a shorthand way to refer to classes defined in the package. Specifically, in the previous example, if the package was imported:
import Transportation.Car;to create an object of Class Car, you would not need Transportation in front of every Car reference, and the code would look otherwise identical to Listing 11.3.
Packages are more than just a shortcut. They are a way of keeping things organized.
Java itself comes with a built-in set of packages, as shown in Table 11.1.
Package | Description |
java.applet | Contains classes needed to create Java applets that run under Netscape 2.0 (or greater), HotJava, or other Java-compatible browsers. |
java.awt | Contains classes helpful in writing platform-independent graphic user interface (GUI) applications. This comes with several subpackages including java.awt.peer and java.awt.image. |
java.io | Contains classes for doing I/O (input and output). This is where the data stream classes are kept. |
java.lang | Contains the essential Java classes. java.lang is implicitly imported, so you don't need to import its classes. |
java.net | Contains the classes used for making network connections. These are used in tandem with java.io for reading and writing data from the network. |
java.util | Contains other tools and data structures, such as encoding, decoding, vectors, stacks, and more. Additional packages are also available commercially. |
The odds are that your Pac Man game will include a lot of code that is likely to be used by other arcade-style games you have written. For instance, you might create what is known as a game sprite engine. It's probably a more far-sighted approach to place all of the elements for the game-sprite in their own package and then place only those classes that are specific to the Pac Man game in the Pac package. Later you can go back and add to the game-sprite package without disrupting the readability of your Pac Man game.