Notice: This material is excerpted from Special Edition Using Java, ISBN: 0-7897-0604-0. The electronic version of this material has not been through the final proof reading stage that the book goes through before being published in printed form. Some errors may exist here that are corrected before the book is published. This material is provided "as is" without any warranty of any kind.
by Michael Afergan
To write a Java program, whether an applet or application, it is necessary to understand the underlying structure of all Java programs: classes. Java is based on an object-oriented outlook on code, viewing programs as an aggregation of several objects-constructs specifically designed to accomplish a given task. Nevertheless, it is classes that create and define the properties of these objects. By allowing developers to adopt the object-oriented model and presenting developers with a powerful library of tools developed from classes, Java becomes an interesting and powerful language.
In this chapter, you explore in detail how to create and use classes. More specifically you cover:
From a very simple viewpoint, classes define the state and behavior of an object. An assembly of data and tools to handle the data, classes are clearly defined tools that may be integrated, combined, and extended to create powerful yet lucid programs.
In terms of code, every class has two portions: the properties that define the class and the ability to manage these properties, with the latter giving the class its utility. The properties, called fields, are essentially variables that may be accessed by the entire class and possibly by other classes as well. The tools to handle the fields, called methods, are similar to functions in such languages as C. However, they are slightly different from functions inasmuch as they are provided with complete access to the fields, or state, of the class.
In a practical sense, a class is a group of related information and methods, placed together for utility. For example, if you were creating a game, you might want to have one class to handle the board. This class would contain all the information about the board (player positions, and so on) as well as the appropriate methods required to manage the board, such as moving a piece, and checking the validity of a move. By creating the game board in this manner, we not only encapsulate the necessary game board functionality in the class, and but also enable ourselves to use such a class in an applet or application, making use of its methods and fields.
When dealing with classes, it is important to remember that classes do not enable us as programmers to do anything more than what we would be able to do without them. Then why use them? The answer to this is similar to the reason why large companies are divided in departments and sub-departments. 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 the details of this process. Finally, this process of distributing tasks may be extended to the departments, thereby creating sub-departments - all of which contribute to creating an efficient company.
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 for inheritance-the ability to create new classes based on old classes. Using the example of the game board, in an object-oriented architecture, we are able to create new "board" classes that inherit the properties of the old board, such as the layout and moving mechanisms. The beauty of this is that in this new class, we are now able to build upon these structures. For example, the new game may require that certain locations remain hidden until they are explored. Therefore, the new board class may have the added capability of keeping track of those locations that have been explored by the player.
Object-Oriented Programming Jargon
Because these new classes inherit the properties of another class, they are referred to as child classes or subclasses. Consequently, the class from which they are derived is called a parent or superclass.
An instance of an object is a specific copy of that object. While classes can define objects, it is still necessary to create a copy of the class before using it. In the declaration String name, name is an instance of the String class.
Furthermore, due to the length of its name, object-oriented programming is often abbreviated as OOP (as will be the case in this chapter).
Another benefit of enclosing data and methods in classes is the OOP characteristic of encapsulation, the ability to effectively isolate and insulate information from the rest of your program. By creating isolated modules, once you have developed a complete class to perform a certain task, you may effectively forget the intricacies of that task and simply utilize the methods provided by the class. This means that while you may later have to significantly change the inner workings of a given class, as long the methods used to gain access to the class do not change, you do not need to modify the rest of your program. Furthermore, by placing the data within the class and creating the appropriate methods to manipulate it, you may seal the data off from the rest of the program, thereby preventing accidental corruption of the data.
Furthermore, the allure of the OOP approach of creating self-sustaining modules is 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 diffrent types of classes, as long as they share a common trait. While the behavior of each class might be diffrent, you know that the class will be able to perform the same operation as its parent becuase 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 which extended 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. As an example, if you had a method that accepted an instance of the GameBoard class as one of its parameters, and you derived a class named NewGameBoard that extended the GameBoard class, you would still be able to pass instances of NewGameBoard to the same method.
What's so New About Object-Oriented Programming?
The answer is: plenty and not much. Contrary to the belief of many, OOP is not the antithesis of the top-down approach to programming. Conversely, OOP actually 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.
Note that 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.
Finally, inasmuch as Java is designed to be a web-based programming language, it is a good match for the OOP property of dynamic binding, the ability to link code "on the fly" during execution as needed. Consequently, if you look carefully at the bottom of the Netscape screen when you are loading an applet or the prompt from which the appletviewer was spawned, you will see that the browser does not load "the applet" as an entity, but rather a .class file which is often dependent on several other classes residing on the server. Nevertheless, when writing your code, you do not need to worry about how this binding will occur or where the other classes will be loaded from.
As stated earlier, classes are the essential building block in any Java applet or application. Each class that you create will build upon some of the classes already created by Sun to enable it to function, and will finally be fleshed out and brought to life with the code that you add. In accordance with the object-oriented paradigm, you may later choose to build upon older programs or to enhance your program by creating new classes that will perform certain tasks for you.
Bigger and Better Java
You will note that Java itself is built from classes 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 themselves be extended. Consequently, you may tailor the classes in the Java API library, especially those in the AWT, to meet your particular needs-thereby creating your own "version" of Java!
Before you start creating large programs, you must first learn how to create simple classes. In terms of syntax, there are two parts to a class in Java: the declaration and the body. Listing 10.1 is a simple class (that fulfills some of the requirements of the simple game board discussed earlier). Examine this listing to get an idea of what constitutes a class. You can refer to this listing again later as your understanding of classes grows.
Listing 10.1-file name Title public class GameBoard { /* This is the begining 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 */ /* that you want to */ private static final int HEIGHT = 10; /* keep as standards */ private static final int EMPTY = 0; 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); } }
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:
Declaration in Practice: public class GameBoard
You will note that 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.
The modifiers in a class declaration determine how the class can be handled in later development. 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.
Example: class PictureFrame
If you choose not to place a modifier in front of the class declaration, the class is created with the default properties. Therefore, you should be aware of what these properties are.
By default all classes are assigned the "friendly" level of access. This means that while the class may be extended and employed by other classes, only those objects within the same package may make use of this class.
Example: public class PictureFrame
By placing the modifier public in front of the class declaration, the class is defined to be public. Public classes are, as their name implies, accessible by all objects. This means that they can be used or extended by any object, regardless of its package.
Also note that public classes must be defined in a file called ClassName.java.
Example: final class PictureFrame
Final classes may not have any subclasses, and are created by placing the modifier final in front of the class declaration.
At first the reason for doing may not be not be evident. Why would you want to prevent other classes from extending your class? Isn't that one of the appeals of the object-oriented approach?
While this is true it is important to remember that the object-oriented approach effectively enables you to create many versions of a class (by creating children that inherits 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 for the possibility of classes that would handle this function in a different manner. Thus, by making the class final you prevent this possibility and ensure consistency.
Example: abstract class PictureFrame
An abstract class, denoted by the modifier abstract, is a class in which at least one method is not complete. Thisstate of not being finished is referred to as abstract.
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 GrammarCheker class in which the language-specific methods were declared as abstract and left empty. When ready, you could then create the language-specific classes which would extend the abstract GrammarCheker class and would 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. (You will deal more with abstract methods and the topic of overriding methods later in this chapter in the sections "Abstract" and "Overriding" respectively.)
Because they are not complete, you may not create instances of abstract classes.
There is not much to discuss with regards to the class name inasmuch as the options are virtually endless and the ramifications virtually non-existent. Like all other Java identifiers, the only requirements on a class name are that it begin with a letter, the character ' ' or '$,' contain only Unicode characters above hex 00C0 (basic letters and digits, as well as some other special characters), and not be the same as any Java keyword (such as void or int). Also, it is general practice to capitalize the first letter in the name of any class.
Although only required for public classes, it is generally a good practice to name the file in which class NewClass is defined as NewClass.java. This is because doing so enables you to compile AnyClass that uses NewClass even if NewClass has not been compiled yet.
If you attempt to use a currently uncompiled class in AnyClass, the compiler will first compile these classes before it compiles AnyClass. Thus, if AnyClass is dependent on NewClass, NewClass must be compiled before compilation of AnyClass concludes.
If, however, you had placed the code for NewClass in a file named AnotherName.java, the compiler would be unable to find and thus unable to compile NewClass. Therefore, it would also be unable to compile AnyClass.
Finally, and even more importantly, by placing NewClass in NewClass.java you will be able to find the source code for NewClass more easily.
By specifying the names of other classes after the extends keyword, you are able to build upon other classes in typical OOP fashion as explained earlier in this chapter in "Why Use Classes".
By extending a superclass, 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 the fields and methods declared and/or inherited in the original class.
public class MyClass extends Applet {
Does the above example look familiar? If you look at the source of any applet, you will see that its declaration resembles the above example. In fact, you probably have been extending the java.applet.Applet class without even knowing what you were doing.
Remember the methods that 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/or implement these methods, thereby providing your applet with a great deal of power.
Furthermore, note that every class in Java is considered to be a an object. Therefore, every class is derived from the java.lang.Object class in some form. As a result, if you do not declare your class as extending any other class, it is by default considered to extend the java.lang.Object class.
Multiple-inheritance does not exist in Java. Thus, unlike C++, Java classes may have only one superclass.
See "More About Interfaces," (Ch. 11)
As discussed in chapter 11, interfaces, much like abstract classes, provide for the creation of pseudo-classes of entirely abstract methods. By implementing an interface, any non-abstract class must override all methods declared in the interface.
By implementing an interface, you guarantee that the class will posses certain properties. Consequently, choosing to implement an interface may provide you with a great deal of flexibility in later development.
If you choose to implement an interface, be sure to override every method declared in the interface! If you don't, you must make your method abstract.
Like C functions and C++ methods, Java methods are the essence of the class and are responsible for managing all tasks that will be performed by the class. While the actual implementation of the method is contained within the method's body, like classes, a great deal of important information is defined in the method declaration.
In general, method declarations have the form:
access_specifier modifier return_value nameofmethod (parameters) [ic:ccc] throws ExceptionList
where everything in italics is optional.
Like classes, it is often wise to restrict access to the methods of a given class. However, the considerations involved are even more important for methods. While all methods in a given class are accessible by all other methods in the class, there may be certain necessary tasks that you may not want other objects to be able to perform.
In the example of the game board class, you may later decide to develop a multi-player game. In such a program, moving a piece would not be as simple an operation as changing the value of a given square, but would require other tasks, such as informing other clients of the move. As a result, we would not want to allow other objects to access the setSquare() method, for changing the board without notifying the other players would ruin the game. Therefore, it would be necessary for each move to pass through a publicly accessible wrapper method, such as movePiece(), which would in turn call such private methods as setSquare() and notifyClients(). As a result, the game board would have complete control over the movement of the pieces, and the master class would not need to worry about these processes. Finally, if the networking protocols specified in the notifyClient() method had to change for any reason, there would be no need to change the program itself, only the notifyClients() method, since the master class never knew that notifyClients() even existed.
Finally note that each class may have only one access modifier. It makes little sense for a method to be both public and private!
Example: public void toggleStatus()
public is the most relaxed access status that may be applied to a method. In addition to being accessible to all methods in the class, a public method is accessible to all classes regardless of their lineage or their package. One should make an effort to limit the number of public methods in a class.
Example: protected void toggleStatus()
protected methods are identical to public methods except for the fact that they cannot be accessed by objects outside of the current package.
If you are having a compile-time error caused by an attempt to access a method not visible to the current scope, you may have some trouble diagnosing the source of your problems. This is because the error message will not tell you that you are attempting to access a protected method. Instead it will resemble the following: No method matching paramString() found in class java.awt.TextArea. (Note that java.awt.TextArea.paramString() is a protected method in java.awt.TextArea.) This is because the restricted methods are effectively hidden from the non-privileged classes. Therefore, when compiling a class that does not meet the security restrictions, such methods are hidden from the compiler. Also note that you will encounter a similar error message when trying to access a private or friendly method outside of its range of access as well as when you attempt to access a field from a unprivileged class.
Example: private void toggleStatus()
private is the highest degree of protection that may be applied to a method. A private method is only accessible by those methods in the same class.
If a method deals with the inner workings of a class, and you do not want to make it accessible to subclasses of the current class, you should make it private.
Example: void toggleStatus()
Like classes, if you fail to specify an access modifier, the method will be considered friendly. Friendly methods are accessible both within the class and the package. However, friendly methods are not accessible to subclasses of the current class.
Example: private protected void toggleStatus()
Those methods declared to be private protected are accessible to both the class and any subclasses, but not the rest of the package nor any classes outside of the current package. This means that while subclasses of the given class may invoke private protected methods of a given class, instances of the given class in its subclasses cannot.
For example:
class NetworkSender { private protected void sendInfo(String mes) { out.println(mes); } } class NewNetworkSender extends NetworkSender { void informOthers(String mes) { NetworkSender me; me = new NetworkSender(); super.sendInfo(mes); // this is legal me.sendInfo(mes); // this is not } }
The first statement invokes sendInfo() as a method belonging to the superclass of NewNetworkSender. This is legal because private protected methods are accessible to subclasses. However, the second statement is illegal because it attempts to invoke sendInfo() on a instance of the NetworkSender class. Even though NewNetworkSender is a subclass of NetworkSender, it is referencing sendInfo() not as a method belonging to its superclass, but rather as a method belonging to an instance of NetworkSender.
While the above statement accurately describes how private protected methods should work, as of the 1.0 release they do not behave in this manner.
While the proper access is provided, subclasses were unable to successfully override private protected methods. While the subclass code would compile properly and execute, the original method, not the new method, would be executed even when the newer method was called.
This bug should be fixed as of the 1.01 release.
Like class modifiers, method modifiers allow you to set properties for the method such as where it will be visible and how subclasses of the current class with interact with it.
Example: static void toggleStatus()
Static, or class, variables and methods are closely related. You learn about both-and their relationship to the static keyword-here.
It is important to differentiate between the properties of a specific instance of a class and the class itself. In the following code, you create two instances of the Elevator class and perform some operations with them.
Listing 10.2-Hotel.java Hotel Example with Instance Methods class Elevator { boolean running = true; void shutDown() { running = false; } } class FrontDesk { private final int EVENING = 8; Elevator NorthElevator, SouthElevator; FrontDesk() { // the class constructor NorthElevator = new Elevator(); SouthElevator = new Elevator(); } void maintenance(int time) { if (time == EVENING) NorthElevator.shutDown(); } void displayStatus() { // code is very inefficient, but serves a purpose System.out.print("North Elevator is "); if (!(NorthElevator.running )) System.out.print("not "); System.out.println("running."); System.out.print("South Elevator is "); if (!(SouthElevator.running )) System.out.print(" not "); System.out.println("running."); } } public class Hotel { public static void main(String args[]) { FrontDesk lobby; lobby = new FrontDesk(); System.out.println("It's 7:00. Time to check the elevators."); lobby.maintenance(7); lobby.displayStatus(); System.out.println(); System.out.println("It's 8:00. Time to check the elevators."); lobby.maintenance(8); lobby.displayStatus(); } }
Both NorthElevator and SouthElevator are instances of the Elevator class. This means that each is created with its own running variable, and its own copy of the shutDown() method. While these are initially identical for both elevators, as you can see from the above example, the status of running in NorthElevator and SouthElevator will not remain equal once the maintenance() method is called.
Consequently, if compiled and run, the above code will produce the following output:
C:\dev>\jdk\java\bin\java Hotel It's 7:00. Time to check the elevators. North Elevator is running. South Elevator is running. It's 8:00. Time to check the elevators. North Elevator is not running. South Elevator is running.
In the above example, you will notice a rather funny looking method named FrontDesk(). What is it? As you will see in "Constructors" later in this chapter, this is the constructor method for the FrontDesk class. Called whenever an instance of FrontDesk is created, it provides you with the ability to initialize fields and perform other such preparatory operations.
Variables and methods such as running and shutDown() are called instance variables and instance methods. This is because every time the Elevator class is instantiated, a new copy of each is created. In the example above, while the value of the running variable certainly can change because there are two copies of it, changing one does not change the other. Therefore, we are able to track the status of the NorthElevator and SouthElevator separately.
However, what if we wanted to define and modify a property for all elevators? Examine the following example and note the additions:
Listing 10.3-Hotel2.java-Hotel Example with Static Methods class Elevator { boolean running = true; static boolean powered = true; void shutDown() { running = false; } static void togglePower() { powered = !powered; } } class FrontDesk { private final int EVENING = 8; private final int CLOSING = 10; private final int OPENING = 6; Elevator NorthElevator, SouthElevator; FrontDesk() { NorthElevator = new Elevator(); SouthElevator = new Elevator(); } void maintenance(int time) { if (time == EVENING) NorthElevator.shutDown(); else if ( (time == CLOSING) || (time == OPENING) ) Elevator.togglePower(); } void displayStatus() { // code is very inefficient, but serves a purpose System.out.print("North Elevator is "); if (!(NorthElevator.running )) System.out.print("not "); System.out.println("running."); System.out.print("South Elevator is "); if (!(SouthElevator.running )) System.out.print(" not "); System.out.println("running."); System.out.print("The elevators are "); if (!(Elevator.powered )) System.out.print("not "); System.out.println("powered."); } } public class Hotel2 { public static void main(String args[]) { FrontDesk lobby; lobby = new FrontDesk(); System.out.println("It's 7:00. Time to check the elevators."); lobby.maintenance(7); lobby.displayStatus(); System.out.println(); System.out.println("It's 8:00. Time to check the elevators."); lobby.maintenance(8); lobby.displayStatus(); System.out.println(); System.out.println("It's 10:00. Time to check the elevators."); lobby.maintenance(10); lobby.displayStatus(); } }
In this case, the variable powered is now a static variable and the method togglePower() is a static method. This means that each is now a property of all Elevator classes, not the specific instances. Consequently, invoking either the NorthElevator.togglePower(), SouthElevator.togglePower(), or Elevator.togglePower() method would change the status of the powered variable in both classes.
Consequently, the code would produce the following output:
C:\dev>\jdk\java\bin\java Hotel2 It's 7:00. Time to check the elevators. North Elevator is running. South Elevator is running. The elevators are powered. It's 8:00. Time to check the elevators. North Elevator is not running. South Elevator is running. The elevators are powered. It's 10:00. Time to check the elevators. North Elevator is not running. South Elevator is running. The elevators are not powered.
Placing the static modifier in front of a method declaration makes the method a static method. While non-static methods may also operate with static variables, static methods may only deal with static variables and static methods. Consequently, if you are creating a method that deals only with static variables, it is a good practice to make the method a static method. Also, if you are creating a method that will run in a loop for the lifetime of the class - such as a thread to read from a socket - and you want to share that information with all instances of the class, you could do so by making the method static. As a result, although you may create many instances of the class, you will only create one copy of the method-thereby saving yourself a good deal of memory.
Example: abstract void toggleStatus();
Abstract methods are simply methods that are declared, but are not implemented in the current class. It is therefore the responsibility of the class's subclasses to override and implement these methods.
You declare an abstract method by placing the keyword abstract in front of the method name and replacing the method body with a simple semicolon. Therefore a complete abstract method would have the following form:
abstract boolean checkStatus();
It is the responsibility of subclasses of the abstract class to override all abstract methods an provide their implementation. This is done by creating a new method in the subclass with the same name and parameter list as the abstract method. (See "Overriding" later in this chapter for more information on this process.) If a subclass does not override all abstract methods, it too must be declared to be abstract.
Also note that neither static methods nor class constructors may be declared to be abstract. Furthermore, you should not make abstract methods final, inasmuch as doing so prevents you from overriding the method.
Example: final void toggleStatus()
By placing the keyword final in front of the method declaration, you prevent any subclasses of the current class from overriding the given method. This ability enhances the degree of insulation of your classes, for you ensure that the functionality defined in this method will never be altered in any way.
In the game board example, you will notice that the cleanboard() method is declared as final. Because this method is vital and should not change, it is declared as final to ensure that all subclasses the game board class posses the proper cleanboard() method.
Example: native void toggleStatus();
See "Native Methods" (Ch. 15)
Native methods are methods that you wish to use, but do not want to write in Java. Native methods are most commonly written in C++, and can provide several benefits such as faster execution time. Like abstract methods, they are declared simply by placing the modifier native in front of the method declaration and by substituting a semicolon for the method body.
However, it is also important to remember that the declaration will inform the compiler as to the properties of the method. Therefore it is imperative that you specify the same return type and parameter list as can be found in the native code.
Example: synchronized void toggleStatus()
See "What are Threads?" (Ch. 25)
By placing the keyword synchronized in front of a method declaration, you can prevent data corruption that may result when two methods attempt to access the same piece of data at the same time. While this may not be a concern for simple programs, once you begin to use threads in your programs, this may become a serious problem.
In the original game board example, consider what would happen if you had two threads running in the main program, one which used setSquare() method to enable the player to move some blocks, and another which used the isEmpty() method to continually determine if there was a direct path to the finish. What would happen if the second thread called the isEmpty() method to check a certain space just as the other thread was using the setSquare() method to move that same block? Would it find the square empty, occupied, neither?
To avoid this problem, you make the two methods synchronized. In a given object, no two synchronized methods may run at the same time. Once one is called, it obtains a "lock" on the object. Thus, if another synchronized method is called, the second will wait until the first is completed and has relinquished its lock on the object. In this manner, you prevent such access problems created by having two concurrently running threads each requiring access to the same data. By placing the synchronized modifier in front of both methods, neither may run at the same time as the other.
While returning information is one of the most important things that a method can do, there is little to discuss by way of details about returning information. Java methods may return any data type ranging from simple ones such as integers and characters to more complex objects. (This means that you can return things such as strings as well.)
Keep in mind that unless you use the keyword void as your return type, you must return a variable of the type specified in your declaration.
As an example, the following method is declared to return a variable of type boolean. This is accomplished through by employing the return() (either true or false) statements in the third and fourth lines.
public synchronized boolean isEmpty(int x, int y) { if (board[x][y] == EMPTY) return(true); return(false); }
The rules regarding method names are quite simple and are the same as any other Java identifier: Begin with a Unicode letter (or an underscore or dollar sign) and continue with only Unicode characters.
There are, however, two twists in Java's naming structure that are worth examination. In the next two sections we will describe and discuss both.
Assume that you were expanding your GameBoard class to accept input from a wide variety of sources-the keyboard, the mouse, a text file, and so on-each of which was supplying information to the move() method. Each would present the move() method with a different type of data-a character, integers, or a string, respectively. Therefore, it seems as if you would be forced to create three new methods, parseCharInfo(), parseIntInfo(), and parseStringInfo(), each of which would then call the move() method-a real headache!
However, Java provides you with a rather convenient means of sidestepping this naming problem. While you cannot avoid the fact that you will still need to write appropriate handling methods for each data type, you could instead use the one method name, parseInfo(), which had three definitions:
public class GameBoard { ... private void parseInfo(String move) { // parse string move(...); } private void parseInfo(int key) { // parse keyboard move move(...); } private void parseInfo(int x, int y) { // parse mouse click move(...); } public int updateBoard(Event evt) { // checks 3 sources of data, but is able to use the same // method name for all 3 if ( infoInFile() ) parseInfo( readInfo() ); // readInfo() would return a String else if ( validKeyboardMove(evt) ) parseInfo( evt.key ); else if ( validMouseMove(evt) ) parseIfno(evt.x, ext.y); } }
While the details of updateBoard() and move are not important, the fact that you have three definitions of the parseInfo() method is extremely significant. This process, called overloading, extends the OOP theory of isolation by permitting you to simply call the parseInfo() method without worrying about passing the correct data type-as long as you have created a corresponding parseInfo() method.
Overloading a method, the process of creating several methods with the same name but different parameter signatures, provides you with a great deal of flexibility. At run time the program will determine which version of the parseInfo() method is most appropriate for your use and call it appropriately.
When you later deal with constructors, you will see how this feature enables you to create the same class in several different manners.
Parameter Signature vs. Parameter List
When discussing overloading and overriding it is important to distinguish between a method's parameter signature and its parameter list. A method's parameter signature defines the order and type of the parameters that will be passed to a method (such as int, String, or boolean). A method's parameter list is more specific for it also names these variables (for example, int size, String name, boolean running). While declaring a method with the same name but a different parameter signature will overload a method, declaring a new method with the same parameter signature but a different parameter list will not.
Declaring two methods with the same name and the same parameter signature in the same class is illegal. Look at the example shown in the following table:
Illegal Legal public void testme(int size) { .... } public void testme(int size, String name) { ... } private void testme(int height) { ... } public void testme(String name, int size) { ... }
While creating two methods with the same name and parameter signature within the same class is illegal, one of purposes of extending a class is to create a new class with added functionality. Although this may be handled by creating new methods and variables, you may want to change some of the methods in your old class as well. Reusing your Elevator class from before:
class Elevator { ... private boolean running = true; ... void shutDown() { running = false; } }
you are still able to extend the Elevator class, maintain most of its properties, but change some as well. Consequently, if you wanted to create a SaferElevator class which made sure that the elevator car was empty before stopping, you could do so with the following code:
class SaferElevator extends Elevator { ... void shutDown() { if ( isEmpty() ) running = false; else printErrorMessage(); } }
Note that overriding is accomplished only if the new method has the same name and parameter 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.
Also note that methods may not be overridden to become more protected.
Simply put, the parameter list is the list of information that will be passed to the method. It is in the form:
DataType VariableName, DataType VariableName, ...
and may consist of as many parameters as you want.
Do note, however, that if you have no parameters, Java requires that you simply leave the parentheses empty. (This is unlike other languages which permit you to omit a parameter list, or C which requires the keyword void.) Therefore, a method that took no parameters would have a declaration resembling:
public static final void cleanBoard()
Exceptions provide you with an excellent way of handling the various run-time problems that you may encounter in the execution of your Java program. A good program will be flexible enough to recover from user mistakes and other problems. In fact, most statements in the API that can encounter problems throw exceptions, thereby requiring that you surround it with appropriate error-handling routines.
... try { queue.deQueue(); queue.displayStatus(); } catch (Exception e) { queue.reset(); }
The above code, attempts to perform some operations on a queue. If, however, there is a problem, the problem is dealt with properly since the possibly problematic statements are enclosed in a try-catch block. If any of the statements within this block "throws" an exception, it is properly "caught" by the catch statement and handled. (We will deal with the try-catch construct and exceptions in detail in chapter 15.) However, in order to appropriately deal with thrown exceptions, you must first learn how they are thrown.
The process of creating a method that throws an exception is two tiered. First, you must list those types of exceptions that may be thrown. This is accomplished by placing these exceptions in the exception list of our method declaration.
Instead of listing all the types of exceptions that may be thrown, you may also list a more general type of exception (such as java.lang.Exception). This would enable you to throw any exception that was derived from java.lang.Exception.
However, it is considered bad practice to list a very general exception if you are able to choose a more specific one instead. By listing a more detailed exception you provide readers of your code and the compiler with more information - enhancing the readablity and efficiency of your code.
The second step is the act of throwing the exception. This is performed by using the throw statement followed by an appropriate exception somehow derived from java.lang.Throwable. (The exception may be either one of the ones native to Java or one that you have created yourself.) Consequently, we could rewrite the shutDown() method from the SafeElevator class in the following manner:
See "Exceptions" (Ch. 10)
void shutDown() throws NotEmptyException{ if ( isEmpty() ) running = false; else throw new NotEmptyException(); } }
where NotEmptyException was a throwable Exception that you had created yourself.
Notice the use of the new operator in the throw statement. As you will see later, the new operator creates a new object of the type specified. Therefore, the above throw statement will create a new NotEmptyException object which it will then throw to the calling statement.
If, however, you first catch an exception before throwing it, there is no need to use the new operator in your throw statement, as seen below. (The reason for this is that in order for the exception to have been thrown, it must have already be created somewhere.)
void closeConnection(Socket connection) throws java.lang.Exception{ try { connection.close(); } catch (Exception e) { // any handling code would go here throw e; } }
Constructors are a very special methods with unique properties and a unique purpose. Constructors are utilized to set certain properties and perform certain tasks when instances of the class are created.
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 will be used to return the instance of the class itself. (For more information on this, see the section on the new operator.)
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 manners. For example, by creating several constructors, each with a different set of parameters, you could enable yourself to create a instance of the GameBoard class specifying the name of the game, the values of the board, both, or neither. You will note that this practice is prevalent in the Java libraries themselves. As a result can create most data types (such as java.lang.String and java.net.Socket) while specifing varring degrees and types of information.
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 be able to actually 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.
Finally note that constructors cannot be declared to be native, abstract, static, synchronized, or final.
(c)Referring to Parts of Classes
Now that we have begun to develop classes, let us examine how they may be used in other classes. As discussed earlier in "Why Use Classes", Java classes may contain instances of other classes, which are treated as variables. However, you may also deal with the fields and methods of these class type reference variables. To do so, Java utilizes the standard dot notation used in most OOP languages. Examine 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); }
You can see that if board is an instance is the GameBoard class, the variable myname is referenced by board.myname.
Notice that the variable myname is referred to as board.myname not as GameBoard.myname. If you try to do so, you will 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 earlier, when you deal with board, we 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.
Note, however, that you are able to change static variables in this manner.
You have seen how to refer to other classes. However, what if you want the class to refer to itself? While the reasons to do so may not seem so obvious at first, being able to refer to itself is a capability very important for classes. Consequently, in Java code, the variable this is used whenever it is necessary to explicitly refer to the class itself.
In general, there are two situations which warrant use of the this variable. The first is the case where there are two variables in your class with the same name-one belonging to the class and one belonging to a specific method As you will see later in "Scope", using the this.variablename syntax enables you to refer to the variable belonging to the class.
Another situation which lends itself nicely to using the this variable is when a class needs to pass itself as an argument to a method. Often when you create applets that employ other classes, it is desirable to provide those classes with access to such methods as showStatus(). For example, if you are creating a Presentation applet class and wanted to utilize a simple TextScroll class to display some text across the status bar at the bottom of the screen, you would need to provide the TextScroll class with some means of utilizing the showStatus() method belonging to the applet. The best way to enable this is to do this to create the TextScroll class with a constructor method that accepted an instance of the Presentation applet class as one of its arguments. As seen in the following example, the TextScroll class would then be able to display the information across the bottom of the Presentation class' 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?" (Ch. 25)
While the concepts of threads and their uses will be 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.
Along the same lines as this, the special variable super provides access to a class's superclass. This is useful when overriding a method, for when doing so you may want to use code from the old method as well. For example, if we were creating a new class NewGameBoard that extended the game board class and were overriding the setSquare() method, we might employ the super variable to use the former code without recopying all of it.
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 is used to refer to the original version of the setSquare() method, found in the GameBoard class. By doing so, you save yourself the headaches of recopying the entire method.
You should also examine how you do the same thing if you were dealing with the constructor method of the NewGameBoard() class. While it is not much different, 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, since GameBoard() is the name of the original constructor method, it may be referred to as super().
See "Literals" (Ch. 9)
Obviously, variables are a integral part of programs and thus classes as well. In chapter 8, you examined the various types of variables, but you must also consider how they are employed in your programs and the different roles they may assume. Therefore, 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.
Class Fields vs. 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 considered to be fields of the class. These variables are referred to as fields of the class and accessible to all methods of the class.
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 therefore cannot be accessed by other classes. Consequently, you cannot apply any access modifiers to method variables.
While it is possible to make every field accessible to every class, this is not a prudent practice. First of all, you would be defeating a great deal of the purpose of creating your program from classes. Why do you choose appropriate class names instead of class1, class2, class3, and so on? Simply to create a clean program that is easy to code, follow, and debug. For the same reason, by creating various levels of protection, you capsulize your code into self-sufficient and more logical chunks.
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 use of it.) For example, in creating a Circle class, there will most likely be several fields that will 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 { 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 effect the area field. As a result, you would be supplying the paintBall() method with incorrect information.
In the next few sections you will examine the various ways of regulating access and solving the above problem.
Like the modifiers for classes and methods, access modifiers determine how accessible certain variables will be to other classes. However, it is important to realize that access modifiers apply only to the global fields of the class-those variables declared before any methods. It makes little sense to speak of access modifiers for variables within methods because they exist only while the method is executing and then "collected" to free up memory for other variables.
Why not make all variables fields?
Inasmuch as all fields of a class are accessible to all methods in a given class, why shouldn't you make all variables fields-global to all methods in the class?
The first reason is that by doing so, 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 were 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 where the variables were obtaining their values since there would be no logical path of values being passed from method to method.
Example: int size;
By default, fields are assigned the "friendly" level of access. This means that while accessible to other classes within the same package, they are not accessible to subclasses of the current class nor classes outside of the current package.
Example: public int size;
Identical to the public access modifier for methods, the public modifier makes fields visible to all classes, regardless of their package, as well as all subclasses. Again, one should make an effort to limit public fields.
Example: protected int size;
Protected fields may be accessed by all subclasses of the current class, but are not visible to classes outside of the current package.
Example: private int size;
The highest degree of protection, private fields are accessible to all methods within the current class. They are however not accessible to any other classes nor are they accessible to the subclasses of the current class.
Example: private protected int size;
Private protected fields, like private protected methods, are accessible within the class itself as well as within subclasses of the current class.
Two Birds of a Feather
Like the private protected modifier for classes, the private protected modifier for fields does not work as of the 1.0 JDK for a similar reason. Although you are able to re-declare private protected fields in subclasses and assign them new values, if you create an instance of the new class, the field will be created with the value from the superclass, not the new value.
Again, this problem should be resolved as of the 1.01 release.
See "Static Methods,"
Example: static int size;
As with methods, placing the modifier static in front of the field declaration makes the field a static, commonly referred to as a class, field. Static fields are fields of the class whose value is the same in all instances of the class. Consequently, changing a static field in one class will effect that field in all instances of the given class. Static fields may be modified in both static and non-static methods.
Example: final int SIZE = 5;
Although Java does not have pre-processor #define-type statements nor constants, there is a very simple way of creating constants, fields whose values will cannot change while the program is running. By placing the modifier final in front of a field declaration, you tell the compiler that the value of the field may not change during execution. Furthermore, because it cannot change elsewhere, it is necessary to set the value of all final fields when they are declared as seen in the example above.
If the value cannot change, why not use the value itself within the program? The answer to this is twofold. First, while the value of constants cannot change within your code, you may later change the initial value of a constant without having to change the value of each use of the constant. Furthermore, by using constants, your code becomes a lot cleaner and easy to follow. For example, in the GameBoard class, using 0 as a check for an empty space would not always make sense to a reader of your code. However, using the final field EMPTY and assigning it the value 0 makes the code a lot easier to follow.
By convention, all letters of constants are capitalized.
Furthermore, to save memory, constants are usually made static as well.
Although not ignored in the 1.0 release, there are two additional modifiers for fields.
When dealing with many threads, there are several problems that can result when multiple threads attempt to access the same data at the same time. While a majority of these problems can be solved by making certain methods synchronized, in future release of Java, you will be able to declare certain fields as threadsafe. Such fields would be handled extra carefully by the Java runtime environment. In particular, the validity of each volatile field will be checked before and after each use.
The other heralded keyword, transient, is related closely with Sun's plans to enable the creation of persistent Java applets. In such an environment, transient fields would not be part of the persistent object.
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 these 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 static int PI = 3.1415; private int radius, area; private Color border_color; public void setRadius(int update_radius) { radius = update_radius; area = PI * radius * radius; } 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 negatives. As a result, it is a very widely used approach and is extremely prevalent in the Java API libraries on which Java is heavily dependent.
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
There are several things to note from the table. We will start with the simplest variable, log. log is a field of the CashRegister class and is therefore visible throughout the entire class. Every method in the class (as well as other classes in the same package) may access log. Similarly simple is value. Although it is declared as a parameter, it is nevertheless local to the method sellItem() in which it was declared. While all statements in sellItem() may access value, it may not be accessed by any other methods. Slightly more confusing is the variable i, which is declared not at the beginning of a method but within a for statement. Like log and value which exist only within the block in which they were defined, i is exists only within the for statement in which it was defined.
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 toalSales() 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.
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 chose a different (and more descriptive) identifier such as total_sales to avoid this problem.
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.
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 " 'This' Special Variable."
Memory management is a topic very important to all computer languages. Whenever you create a new instance of a class, the Java runtime system will set aside a portion of memory in which to store the information pertaining to the class. However, when the object "falls out of scope," or is no longer needed by the program, this portion of memory is freed to be used again by other objects.
While Java hides most of these operations from the programmer, it does provide you with some chances to optimize your code by performing certain additional tasks. While requiring you to explicitly allocate memory for each new object with the new operator, it also allows you specialize your new object (by using its constructor methods), and to ensure that it leaves no loose ends when it is destroyed.
Memory Management-C vs. Java
Unlike C and C++, which provide the programmer with a great deal of control over memory management, Java performs many of these tasks for you. Most notably, in its aptly called "garbage collection," Java automatically frees objects when there are no references to the given object, thereby making a the C++ free() method unnecessary.
Furthermore, when creating a new object, you do not need to specify the amount of memory requested, but rather the name of the object requested. This also means that when the new statement returns a reference to the allocated portion of memory, it will already have been assigned the proper data type and thus will not require a cast as in C.
When creating an instance of a class, it is necessary to set aside a piece of memory to store various information. However, when you declare the instance at the beginning of a class, you are merely telling the compiler that a variable with a certain name will be used in the class. Consequently, it is necessary to actually create the variable using the new operator. Examine the following code used earlier:
public class Checkers { private GameBoard board; public Checkers() { board = new GameBoard("Checkers"); board.cleanBoard(); } ...
You will see that although we declared the variable board to be of type GameBoard in the third line, we must also create it in the fifth line using the new operator. The syntax of a statement involving the new operator is:
instanceofClass = new ClassName(optional_parameters);
Quite simply, the line tells the compiler to allocate memory for an instance of the class, and assigns the name of the instance to the section of memory. In doing so, the compiler also calls the class' constructor method if one has been defined. If a specific constructor method has been defined, it passes the appropriate parameters to it as seen in the game board example above. If however the constructor does not take any parameters or if no constructor method has been defined, no parameters are necessary. For example, when you create a new instance of a java.lang.String, you employ the same procedure, but need only write:
title = new String();
Pointers: Fact or Fiction?
Java claims not to possess pointers, and as a result prevents the programmer from making some of the mistakes associated with pointer handling. Nevertheless, while it chooses not to adopt the a pointer-based mindset, Java is forced to deal with the same issues of allocating memory and creating references to these locations in memory.
Thus, while assigned a different name, references are Java's version of pointers. Although you cannot perform some of the intrusive operations with pointers as you can with C, you will notice the striking parallels between pointer assignment and object creation. Like declaring a pointer, you must first declare a reference, which is not a complete variable inasmuch as it does not refer to a portion of memory yet. Then you must allocate adequate memory and assign the reference to it. Furthermore, since you may later decide to set a reference equal to another type of the same variable, Java's system of references is extremely analogous to C's system of pointers-and inherently should be inasmuch as they accomplish the same task.
While Java's implementation effectively hides the behavior of pointers from the programmer and shields him from their pitfalls, it is nevertheless a good idea to consider what is occurring behind the scenes when you create and refer to a variable.
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, one could place the code to close the sockets in the finalize() method. Once the instance of the class was no longer being used in the program and was destroyed, this method would be invoked to close the sockets as required.
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."); } }
Note that finalize() is declared to be protected in java.lang.Object and thus must remain protected or become less restricted.
While the finalize() method is a legitimate tool, it should not be relied upon too heavily inasmuch as 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.
While synchronized methods can handle the problem of multiple threads attempting to access the same information, a similar problem can arise when multiple threads attempt to access the same information through an explicit reference. To solve problems created by this scenario, you may employ the synchronized keyword again, this time to create a synchronized block.
class Game { ScoreKeeper recorder; ... synchronized (recorder) { System.out.println("Player One Score: " + recorder.score[1]); System.out.println("Player Two Score: " + recorder.score[2]); } }
Like synchronized methods, synchronized blocks lock up the specified object while executing. In addition, like synchronized methods, synchronized blocks will not start if another synchronized process is running.
While synchronized methods lock the current class, synchronized blocks lock the specified class during execution. In the above example, the instance named recorder, not the Game class would be locked.
Classes are the building blocks of all Java programs. Consisting of fields and methods which may employ these fields in accomplishing a given task, classes enable you to encapsulate an object's behavior and state in a self-sustaining unit. Employing the object-oriented programming view of code, Java's classes can be used by other classes both as a foundation for new classes and as a class type variables.
When creating the class itself, as well as the class's fields and methods, there are several modifiers that you may use to specify how they will interact with other classes. These modifiers will determine how the class, method, or field will interact with other classes.
Modifier | Effect on Classes | Effect on Methods | Effect on Fields |
---|---|---|---|
default (friendly) | Visible to subclasses and class within the same package | Can be called by methods belonging to classes within the same package | Accessible only to classes within the same package |
public | Visible to subclasses and classes regardless of their package | Can be called by methods in subclasses and all classes regardless of their package | Accessible only to subclasses and all classes regardless of their package |
private | Classes cannot be private | Can only be called by methods within the current class | Accessible only to methods within the current class |
private protected | Not applicable to classes | Can only be called by methods within the current class and subclasses. | Accessible only to methods within the current class and subclasses. |
static | Not applicable to classes. | Method is shared by all instances of the current class. | Field is shared by all instances of the current class. |
abstract | Some methods are not defined. These methods must be implemented in subclasses. | Contains no body and must be overridden in subclasses. | Not applicable to fields. |
final | The class may not be used as a superclass. | The method may not be overridden in any subclasses. | Variable's value cannot be changed. |
native | Not applicable to classes. | This method's implementation will be defined by code written in another language. | Not applicable to fields. |
synchronized | Not applicable to classes | This method will seize control of the class while running. If another method has already seized control, it will wait until the first has completed. | Not applicable to fields. |
Table 10.1 Table of Modifiers for Classes, Methods, and Fields
In addition to modifiers, class declarations have the form
modifiers class NewClass extends NameofSuperClass [ic:ccc]implements NameofInterface
and may utilize the final two options to add pre-existing functionality to the class.
Methods are the OOP equivalent of functions and provide the class with its functionality. Method declarations have the form
access_specifier modifier return_value nameofmethod (parameters) [ic:ccc]throws ExceptionList
The access specifiers and modifiers are listed above. The return type is the data type (or void) that will be returned from the method after execution. Parameters are the information that will be passed to a method when it is called. Finally, the exception list specifies those exceptions that may be thrown by the method. The actual throwing of exceptions is performed with the throw statement.
Constructors are special methods that share the name of the class and that do not specify a return type. They are called when an instance of the class is created.
Two important facets of the naming methods are the ability to overload and override methods. Method overloading is the process of creating several methods with the same name but different parameters to enable you to perform the same task with different types of data. Method overriding is the act of creating a new method with the same name and parameter list as a method in the class's superclass. Consequently, the method in the subclass, not the superclass, will be used when the method is called.
Fields are variables declared outside of the scope of individual methods but within a class. These variables are said to define the "state" of the instance, and may be accessed by all methods in the class as well as other classes depending on the access modifier used when declaring the field.
In addition to fields, you may also declare variables within your methods. These variables however exist only while the method is executing and thus cannot be accessed outside of the particular method.
To the runtime system , objects are portions of memory set aside to keep track of the various properties of the object, such as its fields and methods. Therefore, it is necessary to not only declare instances of classes, but also to create them (by reserving sufficient memory) with the new command. The syntax for such a command is instanceofClass = new ClassName(optional_arguments);
Note that class constructors are called when the object is created. Therefore, this is when you are able to pass information to these constructors by way of any paramaters specified in the constructor.
For technical support for our books and software contact support@mcp.com
Copyright ©1996, Que Corporation