| Click here to support our advertisers

SHOPPING
JOB BANK
CLASSIFIEDS
DIRECTORIES
REFERENCE Online Library
LEARNING CENTER
JOURNAL
NEWS CENTRAL
DOWNLOADS
COMMUNITY
CALENDAR
ABOUT US
Journal:
Get the weekly email highlights from the most popular journal for developers! Current issue
> developer.com developerdirect.com htmlgoodies.com javagoodies.com jars.com intranetjournal.com javascripts.com | All Categories : Java # Chapter 2 # Object-Oriented Development in Java ### CONTENTS Introduction to Java Classes and Objects Basic Structure of a Class Creating an Object Instance Using Methods Overloading Methods Constructors Inheritance in Java Subclassing Method Overriding Calling Superclass Methods Calling Superclass Constructors Important Core Classes The Object Base Class String Classes Type Wrappers More about Classes Access Modifiers Class Methods and Class Variables The final Modifier The null Keyword and More about Garbage Collection Scoping Rules Casting Rules Other Keywords Introduction to Exception Handling Structure of an Exception Handler When to Catch Exceptions Exception Handlers and Exception Classes Nested Exceptions Organizing Projects in Java Abstract Methods Interfaces Packages The Java Developer's Kit Summary Now that you're familiar with the general architecture of Java and its basic programming constructs, you're ready to look at Java's tools for object-oriented development. This chapter first looks at the simple, yet powerful, object-oriented features of the Java programming language. You then see how you can organize Java classes into libraries by using the package mechanism. Finally, you're introduced to the suite of class libraries provided with Java, known as the Java Developer's Kit (JDK) or the Java API. ## Introduction to Java Classes and Objects The starting place of object-oriented programming in Java is classes, which provide templates for specifying the state and behavior of an object at runtime. An object is said to be an instance of a class. For a given class and runtime session, there can be multiple object instances. ### Basic Structure of a Class A class generally consists of variables and methods. The following is a rudimentary example of a class called FirstClass: `class FirstClass { int firstVariable = 0; // Initially set to 0 // Set the value of the variable... public void setValue(int newValue) { firstVariable = newValue; } // Get the variable value... public int getValue() { return firstVariable; } }` This class consists of a single variable, called `firstVariable`, which is initially set to `0`. The class has two methods. The `setValue()` method is used to set `firstVariable` to the specified value. The `getValue()` method returns the current value of `firstVariable`. The variable `firstVariable` is called an instance variable. This type of variable is defined for an instance of an object. It differs from a local variable, which is defined inside a block of code or a method. Suppose that a new method is defined to get half the value of `firstVariable`: `public int getHalf() { int half; // A local variable half = firstVariable / 2; return half; }` The variable `half` is a local variable; it will not exist after the `getHalf()` method is finished. However, the `firstVariable` instance variable hangs around until the object instance is destroyed. Note that the `getHalf()` local variable isn't really needed. This method could have gotten the same results in a single `return` statement: `public int getHalf() { return firstVariable / 2; }` ### Creating an Object Instance After defining a class, you can use it as a runtime object. Use the `new` operator to create an instance of a class. You can use the following code to create an object instance of the FirstClass class: `FirstClass firstObject = new FirstClass();` The `new` operator does a couple of things. First, it allocates memory for the object. Recall that Java has automatic memory management; consequently, you don't have to explicitly allocate memory. The `new` operator also initializes instance variables. In this sample class, `firstVariable` is set to an initial value of zero. However, the explicit initialization to zero is not necessary; Java automatically sets instance variables to initial values such as zero or null. The `new` operator also calls a constructor. A constructor is a method called when an object is instantiated. You can use a constructor to provide additional initialization. Constructors are discussed in greater detail in the "Constructors" section of this chapter. ### Using Methods A method has two parts: a definition and a body. The definition must have at least three components: a return value, a name, and a parameter list. These three components define the signature of a method. As you will see in the next section, signatures are important because you can use a method name multiple times in a class. The return value of a method can be a primitive data type (such as `int`), a class name (such as String), or `void` if there is no return value. A method has zero or more parameters. If there are no parameters, the method consists of empty parentheses; otherwise, the parameters can be primitive data types or classes, separated by commas. It's easy to call object methods. The following code instantiates a FirstClass object and manipulates its internal variable values: `FirstClass firstObject; // Object not instantiated yet firstObject = new FirstClass(); // Object instantiated! // See what the default value of the object is... int val = firstObject.getValue(); System.out.println("Value of firstObject = " + val); // Set it to a new value... firstObject.setValue(1000); System.out.println("Value of firstObject = " + firstObject.getValue()); System.out.println("Half Value of firstObject = " + firstObject.getHalf());` Recall that `System.out.println` prints a string to standard output. In this example, the first `Print` statement outputs a value of `0`, which was the initialized value of `firstVariable`. The code then sets that variable to a value of `1000` by using the `setValue()` method. When its value prints again, a value of `1000` is output. Finally, the code calls the `getHalf()` method to print half the instance variable's value, namely `500`. ### Overloading Methods As mentioned earlier, a method has a signature. This is important because Java provides a mechanism for repeatedly using the same method name; this mechanism is called overloading. An overloaded method has the same name but different parameters. To illustrate overloading, a class is created that defines two instance variables. This class, called SecondClass, illustrates overloading by providing two methods of the same name to set the instance variables: `class SecondClass { int var1 = 1; int var2 = 2; // Set only the first variable public void setVar(int newVal1) { var1 = newVal1; } // Overloaded! Set both variables... public void setVar(int newVal1,int newVal2) { var1 = newVal1; var2 = newVal2; } // Get variable values... public int getVar1() { return var1; } public int getVar2() { return var2; } }` The version of `setVar()` with one parameter sets the value of variable `1`; the `setVar()` version with two parameters sets both variable values. The following code shows how you can use this class: `SecondClass secondObject = new SecondClass(); System.out.println("Var1=" + secondObject.getVar1() + " Var2= " + secondObject.getVar2()); secondObject.setVar(1000); System.out.println("Var1=" + secondObject.getVar1() + " Var2= " + secondObject.getVar2()); secondObject.setVar(secondObject.getVar1()/2, secondObject.getVar1()); System.out.println("Var1=" + secondObject.getVar1() + " Var2= " + secondObject.getVar2());` After you call the first `print` method, the output is `1` and `2`, the initialized values of the two variables. The code then uses the first version of `setVar()` to set the first variable to a value of `1000`. The second `print` call prints the values `1000` and `2`. Finally, the other `setVar()` method is called to set both variables. The first variable is set to half its existing value, and the second variable is set to half the existing value. Parameters resolve fully before passing to the called method. This means that half of the existing first variable value of `500` passes to the first parameter, but the full value of `1000` passes to the second parameter. The consequent printing displays `500` and `1000` for SecondClass object variables `var1` and `var2`, respectively. Periodically, you might hear method invocation called "message passing" or "sending a message." This is object-oriented "lingo" for talking about how two objects communicate. The reason that message passing is more than a buzzword is because of what goes on behind the scenes when a method is invoked. Because a method can be overloaded (and overridden, as you'll see in the section on "Method Overriding"), an invocation of an object actually results in the receiving object performing a lookup to see which method should be called. Consequently, it is more correct to call the procedure a "message passing" because the method invocation is not a direct call (such as calling a function in C) but a request for action. ### Constructors As stated earlier, constructors are a special type of method called when an object is initialized; their syntax is similar to that of methods, except they don't return values. The name of a constructor is the same as its class. A constructor is automatically called when an object is instantiated. Now, rework the SecondClass class to illustrate constructors. First, add a default constructor: `class SecondClass { int var1; int var2; // Main constructor... public SecondClass() { var1 = 1; var2 = 2; } // ... OTHER METHODS GO HERE... }` Rather than setting the values of the instance variables in their declaration, set them in the constructor. This constructor is called if you create an instance of SecondClass, as follows: `SecondClass secondObject = new SecondClass();` As with methods, you can overload constructors. Here is a constructor that defines the variable `var1` at creation: `// Another constructor... public SecondClass(int var1) { this.var1 = var1; var2 = 2; }` To instantiate an object with this constructor, use the following: `SecondClass secondObject = new SecondClass(100);` This call creates an object with `var1` set to `100`; `var2` is set to `2`. You may have noticed the curious way `var1` is set in the new constructor. `this.var1 = var1;` In this code, `this` refers to the current instance of the object, so use `this` to differentiate the `var1` of the object from that of the parameter. Because local variables and parameter variables are first in scope, they will be used unless the `this` keyword is used. Effectively, `this` appears in front of every call to an object's variable, so this is the `var2` initialization of the constructor: `this.var2 = 2;` To round out this example, you can use a third constructor to set both the variable's initial values: `// Define both values at initialization public SecondClass(int var1,int var2) { this.var1 = var1; this.var2 = var2; }` You can call this constructor with the following: `SecondClass secondObject = new SecondClass(100,200);` This call creates an object with `var1` set to `100`; `var2` is set to `200`. What happens if there is no constructor, as in the earlier examples? In this case, the constructor of the class's superclass is called. This brings up the subject of inheritance in Java. ## Inheritance in Java One of the distinguishing features of object-oriented languages is inheritance, a mechanism you can use to create a new class by extending the definition of another class. It gives you a powerful way to increase the reusability of your code. You can take an old class and use it to create a new class by defining the differences between your new class and the old one. The old extended class is the superclass; the new extended class is the subclass. In Java, the process of extending one class to create a new class is called subclassing. ### Subclassing Java uses the `extends` keyword to indicate the creation of a new class as a subclass of an existing class. To illustrate subclassing, the following code creates a simple class that keeps a record of people's first and last names. It has a default constructor that simply initializes the two names, a constructor for storing String parameters, and a `list()` method that dumps the current values to standard output. `class SimpleRecord { String firstName; String lastName; // Default constructor public SimpleRecord() { firstName = ""; lastName = ""; } // Constructor... public SimpleRecord(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } // List the elements of the record... public void list() { System.out.println("First Name: " + firstName); System.out.println("Last Name: " + lastName); } }` To instantiate the class with a name and dump its contents, use the following: `SimpleRecord simple = new SimpleRecord("Thomas","Jefferson"); simple.list();` You can use the `extends` operator to derive from the SimpleRecord class a new class with additional address information: `class AddressRecord extends SimpleRecord { String address; // Default constructor public AddressRecord() { firstName = ""; lastName = ""; address = ""; } // Constructor... public AddressRecord(String firstName, String lastName, String address) { this.firstName = firstName; this.lastName = lastName; this.address = address; } }` Notice how this new AddressRecord class inherits the `firstName` and `lastName` variables from the SimpleRecord class. It also inherits the `list()` method. Therefore, if you call `record = new AddressRecord("Thomas","Jefferson", "Monticello"); record.list();` you create a fully initialized AddressRecord object. However, the `list()` method prints only the name variables because this call ultimately results in calling the SimpleRecord `list()` method. The reason for this is because you haven't explicitly defined a `list()` method in the AddressRecord. However, you can address this limitation through method overriding. ### Method Overriding Use method overriding when you need a subclass to replace a method of its superclass. Recall that each method has a signature. When you override a method, you are defining a new method that replaces the method in the superclass that has the same signature. Whenever you call a method, Java looks for a method of that signature in the class definition of the object. If Java doesn't find the method, it looks for a matching signature in the definition of the superclass. Java continues to look for the matching method until it reaches the topmost superclass. To add a `list()` method to the AddressRecord class that displays the address, all you need to do is add this code to the class definition: `// List the elements of the record... public void list() { System.out.println("First Name: " + firstName); System.out.println("Last Name: " + lastName); System.out.println("Address: " + address); }` The `list()` method call from the previous example will now print the following results: `First Name: Thomas Last Name: Jefferson Address: Monticello` ### Calling Superclass Methods You may have noticed that the AddressRecord `list()` method duplicates some code of the SimpleRecord `list()` method. Namely, it duplicates the following: `System.out.println("Last Name: " + lastName); System.out.println("Address: " + address);` You can eliminate this redundancy. Recall that one of the points of inheritance is that a subclass takes an existing class and extends its definition. Because the `list()` method of SimpleRecord already has some of the behavior of the AddressRecord class, you should be able to use the SimpleRecord `list()` method as part of the base behavior of the AddressRecord class. Use the `super` keyword to refer to a superclass. The `super` keyword is appropriate for the current problem being discussed because calling the superclass of AddressRecord has some of the behavior the `list()` method needs. Consequently, the `list()` method of AddressRecord is modified to call the SimpleRecord `list()` method before specifying additional behavior: `public void list() { super.list(); System.out.println("Address: " + address); }` This code now prints the full name and address, as mentioned in the preceding section's definition of `list()`. ### Calling Superclass Constructors You can use the `super` keyword to call the constructor of the superclass. The only difference from the previous use of `super` is that you call it without any method definition. For example, the two constructors of AddressRecord are modified to call the SimpleRecord constructor before performing its own initialization: `// Default constructor public AddressRecord() { super(); address = ""; } // Constructor... public AddressRecord(String firstName, String lastName, String address) { super(firstName,lastName); this.address = address; }` You can call the default constructor (no parameters) or another constructor with `super` as long as the constructor with the matching signature exists. You can use the `this` keyword in a similar manner to call methods within your class. For example, suppose that you need a constructor that just adds an address to the AddressRecord class, with the other fields set to empty strings. You can do this by using the following code: `public AddressRecord(String address) { this(); this.address = address; }` This code calls the default constructor of the AddressRecord class, which in turn calls the SimpleRecord default constructor. At this point, all the fields are set to empty strings. The code then assigns the specified String to the address field, and you're ready to go. You might wonder what the superclass of SimpleRecord is; the answer is in the next section. ## Important Core Classes Now that you have seen the fundamentals of classes in Java, it's time to see how some of the basic classes in Java work. Because these classes are used throughout Java, you should be familiar with them. ### The Object Base Class The Object class is the base class of all classes in Java-the ultimate superclass of all classes. Because of its primary nature, the methods of the Object class are present in all other classes, although custom methods often override them. The Object class has a variety of interesting methods. Each Object has its own String representation. This may be a system-generated name or something generated by the class. For example, a String object's representation is the string itself. You get an Object's String representation in two ways: `Object o = new Object(); System.out.println("Object = " + o); System.out.println("Object = " + o.toString());` In the second line of the preceding example, the Object itself is provided as part of the printout. The Object's `toString()` method is actually called when an Object is provided as part of the `print` statement. Consequently, the second and third lines are functionally equivalent. The `toString()` method should be overridden if you want a custom String representation; it is overriden throughout the Java class libraries. The `finalize()` method is the closest thing Java objects have to a destroy method. Recall that Java has automatic memory management so you don't have to explicitly delete objects. The garbage collector removes unreferenced objects from memory. When an object is about to get "garbage collected," its `finalize()` method is called. This can be overridden if you need to do some custom cleanup, such as closing a file or terminating a connection. However, because garbage collection doesn't happen at predictable times, you need to use the `finalize()` method carefully. You can also call the method manually, as in right before removing a reference. Each class in Java has a class descriptor. The Class class represents this descriptor, which you access by using the `getClass()` method of the Object class. You cannot modify the returned class descriptor. However, you can use it to get all kinds of useful information. For example, the `getSuperclass()` method returns the class descriptor of a class's superclass. For example, to get the superclass of a String, you can call the following: `String s = "A string"; System.out.println("String superclass = " + s.getClass().getSuperclass());` The `getName()` method of Class returns the name of the class. You may notice the `wait()` and `notify()` methods of the Object class. These are complex methods related to threaded processing, which is discussed in detail in Chapter 8, "Adding Threads to Applets." ### String Classes As stated in Chapter 1, string literals are implemented as objects of the String class. You can create strings in a variety of ways. First, you can assign a String to a literal, as has been shown. You can also use String arithmetic to concatenate one String to another. For example, the second string in `String s1 = "A string"; String s2 = s1 + " with some additional text";` is set to `"A string with some additional text"`. You can also use other operators, such as `+=`, in String arithmetic. String concatenation also works with primitive data types. For example, this is a valid operation: `String s = "This is number " + 6;` Although you cannot change String objects after creating them, you can apply methods to them to yield new String objects. For example, you can use the `substring()` method to return a String starting at a specific index. You can also apply other operations. The `length()` method returns the length of the String. For example, the following methods applied on the above String objects print `with some additional text`: `System.out.println(s2.substring(s1.length()));` A series of overloaded `valueOf()` methods is particularly useful. These methods take a primitive data type and return a String representation. The following code sets the String variable `s` to `100`. `int i = 100; String s = String.valueOf(i);` Notice that the preceding example didn't use any instance of the String class to invoke the `valueOf()` method. This is because `valueOf()` is a class method, which means that the method is global to the class, not to a specific instance of the class. Class methods (sometimes called static methods) are discussed in the section "Class Methods and Class Variables" later, but keep this syntax in mind when you see it. The `equals()` method returns a boolean indicating whether two String objects have identical internal strings. The call `s2.equals(s);` using the String variables from the previous example returns `false`. The String class has many methods; you'll see many of these used in the examples and chapter projects of this book. Another type of string class is StringBuffer. Unlike the String class, you can modify a StringBuffer object after you've created it. You typically construct a StringBuffer object with a String as the lone parameter, although other variations are possible. After construction, you can use operations like `append()` to modify its state. Using the two String variables again in an example, the following code creates the same String value as variable `s2`, except with a period concatenated to the end: `StringBuffer sb = new StringBuffer(s2); sb.append('.'); System.out.println(sb);` The StringBuffer class has some of the same methods as String, and you usually use it in close conjunction with String objects. ### Type Wrappers As pointed out in Chapter 1, "The Java Development Environment," Java had some important design decisions to make regarding primitive data types like integers and floating point numbers. On one hand, designers of Java wanted it to be "objects all the way down," while on the other hand, it needed good performance. In other object-oriented languages, such as Smalltalk, everything is an object, including a number. To perform a mathematical operation would then actually be an application of a method. However, this purity comes at the expense of performance; methods calls are relatively expensive compared to, say, an addition of two integers. Java uses a middle-ground approach. You can work with the primitive data types directly so that `int x = 2 + x2;` does not result in an object method call. However, if you want to use numbers or other data types of objects, you can use instances of a special set of classes called type wrappers. Each of the primitive data types has a type wrapper class, as shown in Table 2.1. Table 2.1. Type wrapper classes.
> Class | Description
> `Boolean` | Object wrapper for the `boolean` data type
> `Character` | Object wrapper for the `char` data type
> `Double` | Object wrapper for `double` data type
> `Float` | Wrapper for `float` data type
> `Integer` | Wraps `integer` data type
> `Long` | Type wrapper for `long` data type
> `Number` | A superclass that defines methods of numeric type wrappers
> You can create wrappers in a variety of ways depending on the data type. You can create instances of the Integer class in two ways: `Integer I1 = new Integer(6); Integer I2 = new Integer("6");` Once created, you can apply a suite of methods. You can use some of these to convert the internal value to a new data type. For example, the preceding code returns a double value for the integer `6`: `double dbl = I1.doubleValue();` As with the String class, you can employ class methods to perform operations on primitive data types without creating an instance of a class. For example, the following code converts a string to an integer: `int i = Integer.parseInt("6");` If the String cannot be converted to a number, a NumberFormatException object is thrown. Exceptions are discussed later in the section "Introduction to Exception Handling." Finally, the type wrappers provide public variables that give information about such things as the upper and lower bounds of a data type. To get the maximum value a `double` primitive type can have, you may call the following: `System.out.println("Max double value: " + Double.MAX_VALUE);` The `MAX_VALUE` public variable is a class variable of the Double class and is declared as static as in class methods. You learn more about these types of variables in the section "Class Methods and Class Variables."
> Note
> Now that you have seen some of Java's core classes, you can gain some further insight into Java arrays; as stated earlier, arrays are first-class objects. Because Object is the base class, this implies that arrays are subclasses of the Object class. Actually there is a class called Array, which is a direct subclass of Object. For each primitive type and class, there is a subclass of Array. Thus, there is a String[] and int[] class. Inheritance relationships are also maintained in this Array hierarchy. Turning back to the first inheritance example, the AddressRecord[] class is a subclass of SimpleRecord[]. Because arrays are subclasses of Object, this means that you can apply Object methods to it. For example, this is legal: `String s[] = new String[4]; System.out.println("S Superclass: " + s.getClass().getSuperclass());` Therefore, you can assign arrays to Objects. These are all valid calls: `String s[] = new String[4]; Object oArray[] = s; int numbers[] = new int[4]; System.out.println(numbers); Object o = numbers;` However, you cannot explicitly subclass an array.
> ## More about Classes It is time to go back to the mechanics of using Java classes. However, with the basics and a description of some core classes behind you, you're ready for some more advanced class constructs. ### Access Modifiers Java uses access control modifiers to specify the level of visibility a method or variable has to other classes. Java has four levels of access: `public`, `private`, `protected`, and `package`. The first three are straightforward and may be familiar; the `package` access level is a little more involved. The `public` modifier indicates that you can access a variable by any class or method. Constructors are usually `public`, as in the AddressRecord example from earlier: `class AddressRecord extends SimpleRecord { // Default constructor public AddressRecord() { //... } }` You can also declare a variable as `public`. For example, you can extend the AddressRecord class to have two `public`/FONT> variables to indicate whether the address is for the Internet or is physical: `class AddressRecord extends SimpleRecord { public int INTERNET_ADDRESS = 0; public int PHYSICAL_ADDRESS = 1; // ... Constructors and methods... } }` You can use the following code to print its values: `AddressRecord a = new AddressRecord(); System.out.println("Physical Address = " + a.PHYSICAL_ADDRESS); // which is 1` You use the `protected` accessor to restrict access only to subclasses of the protected class. This accessor allows you to design classes so that you can specify methods only of use to subclasses. For example, you can make the name variables of SimpleRecord `protected`. This restricts their use to a subclass, such as AddressRecord. This is how the variable portion of SimpleRecord is defined: `class SimpleRecord { protected String firstName; protected String lastName; // ... Constructors and methods... }` The AddressRecord class can continue to use these variables, but outside classes cannot access them. The `private` accessor indicates that a variable or method is not available to any other class except the one in which it appears. For example, recall that the `list()` method of SimpleRecord is called by the AddressRecord subclass. This can happen because access to the method is allowed. However, if the method is declared as `private`, `private void list()` then AddressRecord will not be able to access the method. A compilation error will therefore arise when you try to compile the AddressRecord class. The last and default form of access, `package`, does not directly correspond to an accessor keyword. Packages, as discussed in the section of the same name, are a way of creating libraries of classes. If you do not specify an access control modifier, a method or variable is accessible to all classes in the `package`. The `package` level of access is a way of saying that the method or variable is accessible to all classes that are "friends" with, or the same `package` as, the class they're contained in. ### Class Methods and Class Variables Sometimes you may need to have information that is global to a class and shared by every instance of that class. One reason you might want to do this is for values that do not change, as in defining mathematical constants, like pi. Or you may want to define a version number for a class. Finally, if your class is providing a service that is used throughout an applet, you may want to make its data global to the class so that objects can share the information. Class methods and class variables are employed to define data that is local to a class and not an object. For this reason, class variables are different from instance variables. Class methods and class variables are declared by using the `static` keyword, which sometimes results in them being referred to as static methods and static variables. From the ever present AddressRecord example, you can make the two public address flags into class variables by adding the `static` keyword: `class AddressRecord extends SimpleRecord { public static int INTERNET_ADDRESS = 0; public static int PHYSICAL_ADDRESS = 1; // ... Constructors and methods... } }` This is more efficient than the previous use that just declared the addresses as `public`. By adding the `static` keyword, you have indicated that these flags are global to the class and not the particular instance. Class methods are even more interesting. In Part III of this book, you will create a class that keeps track of images that have been loaded in memory. This class information is kept across invocations of applets, so some of this class information doesn't need to be tied to a particular instance. In fact, there is only one instance of the class, and it is invoked by the class itself! The class does this through a private constructor. Here are some of the code highlights: `public class MediaLoader { // Cache is hashtable... static Hashtable cache = new Hashtable(cacheSize); // The loader is static and so can be created only once private static MediaLoader loader = new MediaLoader(); // Private internal constructor private MediaLoader() { // ... various initilization goes on here... } // Return reference to this MediaLoader object public static MediaLoader getMediaLoader() { return loader; } // ... internal methods.. }` A lot of interesting things are going on here. A `cache`, which is used to store images, is declared as a class variable. One of the neat things about Java is the flexibility with which you can structure your code. Note how this initialization is not within a method but is part of the class definition; remember that because the `cache` variable is `static`, it is a class variable and thus is not tied to an object instance. The next step is even more unusual. The loader class initializes itself! The constructor is `private` and not `public`, so no other object can create an instance of the loader. Instead, the class calls the `private` constructor and stores the instantiated loader object into a private class variable. In effect, the class is saying, "Make only one instance of this class, and only I know it!" How do other objects use this MediaLoader object? They do so by calling the last method listed, `getMediaLoader()`. It is a class method and so is not tied to any instance. The job of the method is to return a reference to the `private` instance of the object. The other objects can then use this reference to call the `public` methods of the loader class. These methods will not be declared as `static`. To keep things simple, suppose that the loader has a `public` method called `getImage()`, with no parameters. Another object calls the method as follows: `MediaLoader ref = MediaLoader.getMediaLoader(); // Get the reference! ref.getImage(); // Call the method!` Note how you call the class method `getMediaLoader()` by prefacing it with the name of the class. Because class methods are global to the class, you do not need an instance of the class. The preceding call also could have been accomplished in one line of code: `MediaLoader.getMediaLoader().getImage();` This method-chaining technique prevents you from creating short-lived variables; you can use the object returned from one method call as the object to be used in the next method call. While class methods are extremely powerful, they have some restrictions. In particular, they can work only on class variables. If you think about it, the reason for this is obvious: They cannot work on instance variables because there may not be an instance for them to work with! The various techniques you have seen in the MediaLoader class are used throughout the Java Developer's Kit (JDK). You sometimes see classes that you cannot figure out how to use. In this situation, look for methods named something like `getReference` or `getDefault`; these may return a reference to an object instance of that class. You would then employ them in a fashion similar to the MediaLoader class. ### The `final` Modifier You use the `final` modifier to indicate that something cannot be changed or overridden. If the `final` modifier is applied to a variable, it is effectively made into a constant. In the AddressRecord example, the address flags are set to their best form by adding the `final` modifier: `class AddressRecord extends SimpleRecord { public static final int INTERNET_ADDRESS = 0; public static final int PHYSICAL_ADDRESS = 1; // ... Constructors and methods... } }` Without the `final` modifier, other classes could change the value of the variable by calling something like `AddressRecord.INTERNET_ADDRESS = 6;` With the `final` modifier attached to `INTERNET_ADDRESS`, however, this line of code would generate a compiler error. This is because `final` variables cannot be modified. If a method is declared as `final`, it cannot be subclassed. Because `private` methods cannot be subclassed, they are effectively `final`. Note, however, that the converse does not hold. It is useful to declare methods as `final` whenever it is appropriate. This allows the Java compiler to make some optimizations, thus improving the performance of your code. If the compiler knows that a method cannot be subclassed, it can do such things as "in-lining" the method wherever it is called. Because a `final` method cannot be subclassed, runtime lookups for matching method signatures as part of the subclassing mechanism are not necessary. To declare a method as `final`, place the modifier as follows: `public final void list()` You can also declare classes as `final`. This means that the class cannot be subclassed. The main reason you may want to declare a class as `final` is related to security. In the JDK, many classes are `final`. The reason for this is clear if you think about it. For example, suppose that you could subclass the System class! It would then be relatively easy to subvert the security of a client. Final classes may also be subject to some compiler optimizations, thus providing an additional reason for declaring a class as `final`.
> Note
> The term accessor methods refers to methods used to set and retrieve a variable. This is the preferred way of constructing a class rather than declaring everything as `public`. In the SimpleRecord class, the preferred way to set or retrieve the `firstName` variable of an object is to create methods like `public void setFirstName(String firstName) { this.firstName = firstName; } public String getFirstName() { return firstName; }` where the variable `firstName` is not declared as `public`. While writing a `set` or `get` method for every accessible variable may seem tedious, it is better than declaring everything `public`. If you make instance variables `public`, you're compromising the principle of encapsulation. By exposing `public` instance variables to objects that use your class, you're making the outside world aware of how your class works. You may then lose the freedom to modify the inner workings of your class. For example, if you want to rename or delete a `public` instance variable, you may not be able to do so because all kinds of classes are using this variable! This very bad practice departs from all the good principles of object-oriented programming. So while it may seem painful now to write large numbers of `set` or `get` methods, you're saving yourself time down the road. If you have a good editor, it won't take that much time anyway. The term accessor class is sometimes used to refer to classes that do nothing more than hold data reachable through accessor methods; the classes have no behavior per se. For C programmers, you will often want to write an accessor class where you would normally create a structure.
> ### The `null` Keyword and More about Garbage Collection The `null` keyword indicates that something has no instance. By default, an uninitialized class variable is set to `null`. Sometimes you might want to do this explicitly-simply for code readability-if your variable will not be initialized for a while. The biggest use of `null` occurs when you no longer need an object. For example, suppose that you create a local variable in a long and involved method that refers to an instance of the Integer class. You use the Integer object for a while, and then you want to indicate that it is no longer needed. You can do this with the `null` keyword: `Integer I = new Integer(6); // ... do something with the object... I = null;` You can achieve the same result in other ways. You can enclose the variable in a block statement; when the variable goes out of scope, the object will no longer be referenced. If you reuse the variable for another object reference, the old object will no longer be referenced: `Integer I = new Integer(6); // ... do something with the object... I = new Integer(9); // Old Integer reference is gone...` Why would you want to set something to `null`? Recall that Java's memory management is through a garbage collector. This garbage collector removes objects from memory that are no longer referenced. If your variable was the only reference to the object, then after the reference is removed, the object is a candidate for garbage collection. Remember that the garbage collector is a low-priority thread, so the object may not be cleaned up immediately. If you are really tight on memory, you can call the garbage collector explicitly via the System class: `System.gc();` Like all methods in the System class, `gc()` is a class method, so you don't have to create any instance to use it. The kind of situations in which you might want to call the garbage collector explicitly is after involved operations that consume a large number of objects and system resources. Do not call the collector in a loop, however. The `gc()` operation runs the full collector, so the collector's execution will take a moment or so. ### Scoping Rules When a variable is referenced inside a method, Java first looks in an enclosing block for a declaration. If a declaration is not found, Java travels up the nested blocks until it reaches the method definition. If the variable is found anywhere inside a method, the variable has priority over a similarly named instance or class variable. This is why you often see code like the following: `public SimpleRecord(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }` The `this` keyword is used here to differentiate the instance variable from the local variable. If the referenced variable is not found inside the method, Java searches the class. If the reference variable still is not found, Java travels up the class hierarchy (until the Object class is reached), inspecting the superclasses for the variable. You have seen how this works in the "Inheritance in Java" section. ### Casting Rules Use casting when you need to convert an object or primitive of one type to another type. For example, you may want to convert a `double` to an `int`, or a subclass to its superclass. Casting allows you to do this, although the rules of casting can be complicated. The key thing to remember about casting is that it does not affect the object or value being cast. However, the receiver of the cast constitutes a new object or a new type. Casting primitive data types occurs quite often, such as when you are reading in a stream of data. How the cast occurs depends on the precision of the types involved in the cast. Precision relates to how much information the type can contain; for example, a floating point type such as `double` or `float` has more precision than a simple number like `int`. Likewise, `double` has more precision than `float` because it is 64-bit as opposed to 32-bit. Whenever you transfer data from a less precise type to a more precise type, explicit casting is not required: `int i = 3; double pi = i + .14159;` On the other hand, transferring data from a more precise type to a less precise type requires casting. This is because data may be lost in the casting. Java forces you to explicitly cast because it wants you to be aware of the possible danger of such a conversion: `double pi = 3.14159; int i = (int)pi; // This is set to 3 (you lost the .14159!)` Casting between objects is a little more complicated. To illustrate casting, look at the Hashtable class of the JDK. This class takes instances of the Object class and places them into a hash table via a method called `put()`, where a String is used as a key. To retrieve them from the table, you invoke `get()`, which takes a String key and returns the corresponding Object. Recall that Object is a superclass of all classes; every class is a subclass of Object. Suppose that you have a class called MyClass and a String that will be used as a key, placed in a variable `key`. You can place it in a Hashtable object, indicated by the variable `hash`, as follows: `MyClass MyObject; hash.put(key,MyObject);` Because you want to keep your object, MyObject, as an instance of MyClass, don't cast it when you call `put()`. This is acceptable because MyClass is a subclass of Object. Suppose that you want to actually store MyObject as a proper Object. In other words, you want to cast a subclass to a superclass. You would then cast it as follows: `hash.put(key,(Object)MyObject);` By doing this, however, you lose the functionality of MyObject. Suppose that you store the MyObject in the original example, without casting. When you retrieve the object, it is returned as an Object. You can convert the Object returned by `get()` to the original MyObject by casting: `MyClass MyObject; hash.put(key,MyObject); // ... do something... MyClass MyObject2 = (MyClass)hash.get(key); // Get back original MyObject` In this case, you're using casting to convert a superclass to a subclass. You can then use the variable `MyObject2` as an instance of MyClass. It will be identical to the original `MyObject` variable. If you structure the code in the following way, you will have problems: `MyClass MyObject; hash.put(key,(Object)MyObject); // ... do something... MyClass myObject2 = (MyClass)hash.get(key); // Don't do this!` Problems will occur because MyObject was stored in this case as an Object. When you retrieve it, even after casting, it is not really an instance of MyObject any more because that data was lost. If you try to use a `MyObject` method or variable after this, you get a runtime exception. You cannot cast indiscriminately. If you try to convert two sibling classes (they are not derived from each other), you get a compilation error: `AddressRecord a = new AddressRecord("Thomas","Jefferson","Monticello"); String s = (String)a; // THIS WILL NOT COMPILE!` You cannot cast primitive data types to objects or vice versa. However, you can effectively perform these conversions by using the type wrapper classes discussed earlier in this chapter. ### Other Keywords You use the `instanceof` operator to test if a class is an instance of another. A subclass will be an instance of its superclass, but not vice versa. For example, the first three of the following print statements will display `true`. (Recall that AddressRecord is a subclass of SimpleRecord.) Only the last test will be `false`. `AddressRecord a = new AddressRecord(); System.out.println((a instanceof SimpleRecord)); // true System.out.println((a instanceof AddressRecord)); // true SimpleRecord b = new SimpleRecord(); System.out.println((b instanceof SimpleRecord)); // true System.out.println((b instanceof AddressRecord)); // false` Another important modifier is `synchronized`. You will see this modifier in code throughout this book and in the JDK. The `synchronized` modifier is related to coordinating thread processing; it is discussed in depth in Part III of the book. ## Introduction to Exception Handling One of the great challenges for programmers is how to handle runtime errors in a graceful and efficient manner. In traditional programming, developers manage problems by passing success or failure codes in `return` statements. The calling functions then check the return code in an `if...else` statement. If the function succeeds, one chain of action is called; otherwise, another course of action is taken. There are a couple of problems with this approach. First, this approach results in bloated code. To have to put an `if...else` check around every function call increases the size of your code by several factors. Even worse, this traditional approach does not enforce strong error checking. Because of sloppiness or overconfidence, programmers often ignore return codes. If there is a problem in an unchecked function call, however, the program may end up reaching a dangerous state that eventually culminates in an abnormal termination. The ensuing search for the cause of the error would probably prove to be quite painful because the source of the problem is not readily apparent. Fortunately, Java's strongly enforced implementation of exception handling makes it easier to track down errors. In fact, many errors are caught by the compiler instead of at runtime. If the problem does happen at runtime, however, a stack trace makes the difficulty easy to spot. Furthermore, Java's error-handling mechanism does not result in the kind of code bloat typical of traditional programming. Java's way of handling errors is through a programming construct called an exception handler. ### Structure of an Exception Handler An exception handler is often called a try-catch block because of its structure. It is typified by a block of code called a try block, which is where you attempt to execute your normal course of action. The code marches right through the block if there is no problem. If there is an error, however, Java or the called method may generate an object that may indicate the problem. This object is called an exception object and is passed off to the runtime system in search of a way to handle the error. The act of passing an exception object to the runtime system is called throwing an exception. The job of the catch block of an exception handler is to catch any exception objects thrown from within the try block. It can then perform any cleanup or message notification as a consequence of the error. A simple example can illustrate the basics of writing an exception handler. Suppose that you need to divide two numbers. Division, of course, can be the cause of a frequent problem: divide-by-zero errors. However, the following code handles this problem in a graceful manner: `int z = 0; try { z = x/y; System.out.println("Z = " + z); } catch (Exception e) { System.out.println("Divide by zero error."); }` If the variable `y` in this example is not `0`, the division goes fine, and the result is printed. However, if the variable is `0`, an exception is thrown. The Java runtime system will then try to find an exception handler to manage the error. Fortunately, Java will not have to look very far. The catch clause in the exception handler catches the thrown exception. This handler prints the fact that there is a divide-by-zero error to standard output. An exception handler has an optional block placed at the end of the code called the finally block, which provides code to be executed regardless of whether an exception occurs. `int z = 0; try { z = x/y; System.out.println("Z = " + z); } catch (Exception e) { System.out.println("Divide by zero error."); } finally { System.out.println("Finally: Z = " + z); }` Regardless of what happens in the division operation, the last print statement is invoked. ### When to Catch Exceptions When should you catch an exception? Sometimes exceptions are generated as a result of a normal runtime violation, such as a division error or an array out of bounds. However, methods can also explicitly throw exceptions. A method can declare that it can throw an exception via the `throws` clause. For example, it was stated earlier that the Integer class has a method, called `parseInt()`, that takes a String and converts it to an `int` data type. When the String cannot be converted to a number, however, the `parseInt()` method throws a NumberFormatException object. The following code tries to convert some Strings to integers and prints their values: `String s = "100"; String s2 = "Not a number"; try { System.out.println("The first number is: " + Integer.parseInt(s)); System.out.println("The second number is: " + Integer.parseInt(s2)); } catch (NumberFormatException e) { System.out.println("Cannot convert String to number!"); }` The first conversion succeeds, but the second fails. The NumberFormatException generated is caught by the exception handler, and an error message is printed. The calling code knows that `parseInt()` throws this kind of exception because of how the method is declared: `public static int parseInt(String s) throws NumberFormatException` The `throws` section of the declaration indicates that the method may throw a certain type of exception if an operation cannot be performed. Whenever you use a method that has a `throws` clause, you should probably catch the type of exception the method throws. Depending on the type of exception thrown, an exception handler may or may not be required for a successful compile. Categories of exceptions are discussed in more detail in the next chapter. To throw an exception, you use the `throw` keyword. The `throw` statement passes a throwable object that follows it to the runtime environment, which then begins trying to find an exception handler to catch the object. The method that throws the exception is no longer executed after the `throw` statement is invoked. Exceptions, like everything else in Java, are objects. Consequently, you can generate an exception object by instantiating it with the `new` operator. You would then use the `throw` keyword to throw the object. Because the `parseInt()` method throws a NumberFormatException, it's likely that there is a line of code in the method similar to the following: `throw new NumberFormatException();` ### Exception Handlers and Exception Classes As indicated in the previous section, exceptions have different types, or more specifically, classes. For example, NumberFormatException refers to a specific class. When an exception is generated, the Java runtime system looks for an appropriate handler to catch the exception. The details of this process are explained in the next chapter, but remember that whether or not a handler is appropriate has to do with the class of the exception. To clarify this, consider a couple of exception classes. The ArithmeticException class represents errors caused by illegal arithmetic operations, usually divided by zero. Objects of class ArrayIndexOutOfBoundsException are thrown when an invalid array index is accessed. The Exception class is used to represent the general class of exceptions; the other exception classes that have been discussed are actually subclasses of Exception. The following example shows how these exceptions are used: `try { int x = Integer.parseInt(s); int y =Integer.parseInt(s2); int index =Integer.parseInt(s3);` \`int a[] = new int[5]; a[index] = x/y; System.out.println("Everything worked!"); } catch (ArithmeticException e) { System.out.println("Arithmetic exception"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Array index out of bounds exception"); } catch (NumberFormatException e) { System.out.println("Cannot convert String to number!"); } catch (Exception e) { System.out.println("Generic exception"); }` The first part of the code takes three input Strings and tries to convert them into integers; if the conversion fails, a NumberFormatException object is thrown. However, the third `catch` clause will catch the thrown object. If the clause were not there, the last `catch` clause would catch the problem because NumberFormatException is a subclass of Exception. The general rule is this: Java searches the `catch` clauses in the order they are declared, looking for a handler that either matches the class of the exception thrown or is a superclass of it. The runtime system will use the first handler that matches. If no appropriate handler is found, Java will go up the runtime stack and look at the next method. Java looks for an appropriate handler in the same way. This process repeats until the top of the stack is reached or until an appropriate handler is found. If there is no appropriate handler, Java dumps the stack trace to standard output. Depending on the nature of the class being thrown, the program may terminate abnormally. After the Strings are converted to numbers, the program tries to divide the numbers. If the program divides by zero, an ArithmeticException object is thrown. The object is caught by the ArithmeticException `catch` clause, although the `Exception` clause also would have handled it. Finally, the code assigns the divided number to an array index. If the index is illegal, an ArrayIndexOutOfBoundsException is thrown. However, there is also an appropriate handler for it, so the code finishes gracefully. ### Nested Exceptions You can also nest exceptions. To illustrate this, look at the following rework of the preceding example: `int y = 0; int z = 0; int z = 0; int index = 0; int a[] = new int[5]; try { x = Integer.parseInt(s); y =Integer.parseInt(s2); index =Integer.parseInt(s3); a[index] = x/y; System.out.println("Everything worked!"); } catch (ArithmeticException e) { System.out.println("Arithmetic exception. Try to assign Zero"); try { a[index] = x/z; // Try another division to the index } catch (ArithmeticException e2) { System.out.println("Another Arithmetic exception"); } } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Array index out of bounds exception"); } catch (NumberFormatException e) { System.out.println("Cannot convert String to number!"); } catch (Exception e) { System.out.println("Generic exception"); }` If the `int` variable `y` evaluates to `0`, an ArithmeticException is thrown. When it is caught, the code attempts to perform yet another division. Unfortunately, this is also a divide-by-zero error. Fortunately, this is caught in a nested ArithmeticException catch block. If there were no divide-by-zero problem but the index were illegal, the outer ArrayIndexOutOfBoundsException `catch` clause would catch the exception. You learn more about exception handling in the next chapter. You see how Java organizes its exception classes, what methods to use, and how to use them. The following chapter also discusses how to write your own exception classes within the framework established by Java. ## Organizing Projects in Java You've seen a wide variety of constructs that Java uses to help you develop classes. These are useful for quickly getting an applet up and running. But what happens if you are working on a large-scale project that requires a carefully designed and organized class hierarchy? How will Java help you manage all of the classes required in such an environment? Fortunately, Java provides a variety of constructs that help you develop large-scale software as well as small applets. These constructs range from those that help you lay out your design to those that help you pull the pieces together. With these techniques in hand, you can use Java to design applets that can meet your current requirements and can be used as a foundation for future development. ### Abstract Methods Suppose that you're defining a class that manages files on your hard disk. You can define most of the high-level methods, such as how to list files and their attributes; however, you can't define low-level behavior, such as how you actually get the file attributes. Because this is platform dependent, you want to leave this behavior as undefined so it can be implemented for practical purposes. In Java, you use abstract methods and abstract classes to define a template for a class that is well defined except for a few methods. A class is abstract if one or more methods are defined by the `abstract` keyword. The following case shows how you might structure the discussed file manager as an abstract class: `abstract class FileManager { // Abstract class that enumerates files... public abstract String enumerateFiles(String file); // Practical implementation. List all files // using abstract enumerateFiles... public void dir() { // ... call enumerateFiles... } // ... other methods }` In this example, the only abstract method is `enumerateFiles()`, which provides the low-level implementation of getting a file attribute. However, this is enough to make the class abstract. All the other methods are well defined, so if you create a class that provides a practical implementation of `enumerateFiles()`, the class will be ready to use. As long as a class is abstract, though, it cannot be instantiated. You need to subclass the FileManager class to create a class that can be implemented. Here is one way to do it: `class MyFileManager extends FileManager { // Enumerate all files... public String enumerateFiles(String file) { // ... do the platform specific operation... } }` This class uses all the methods of FileManager and is ready to be instantiated. There are a couple of restrictions in creating abstract methods. Constructors and `private` or `static` methods cannot be declared as abstract. (If you think about the semantics of these, it should be clear why they cannot be abstract.) You also cannot replace a `superclass` method with an `abstract` method. ### Interfaces If you have a class that is nothing but abstract methods, it's better not to use the `abstract` keyword. Java provides a technique called interfaces, which you can use to define a template of behavior. Like abstract methods, interfaces cannot be instantiated. Interfaces differ from abstract methods, however, in that no interface methods can have a body. Interfaces are strictly templates. By their nature, interfaces are a way of defining a design. It specifies the kinds of behaviors that something needs to have. Classes that implement the interface are said to provide the implementation of the design. Interfaces are also Java's way of dealing with the limitations of single inheritance. Unlike C++, Java avoids multiple inheritance because of all the problems related to it, such as name ambiguity. Interfaces are a way of adding behavior to a class without compromising its fundamental behavior. However, a class can implement multiple interfaces and so simulate multiple inheritance. The following example shows how interfaces are used. Suppose that you're working for a printer company. You have a well-defined and tested Printer class and a Document class that represents what is to be printed. However, your company wants to move into the fax and copier markets, so a multifunction printer will be developed. You define two interfaces for copiers and fax machines as follows: `interface Copier { public void copyDocument(Document d); } interface Fax { public void transmitDocument(Document d); }` These interfaces define the behavior that a copier and fax machine should have, respectively. You can also add this behavior to the Printer class to get the desired multifunction printer. You can implement these interfaces in a class that is a subclass of Printer: `class MultiFunctionPrinter extends Printer implements Copier, Fax { public void copyDocument(Document d) { // Practical implementation of Copier... } public void transmitDocument(Document d) { // Practical implementation of Fax... } }` You now have a multifunction Printer class! It should be noted that interfaces can have variables but cannot have modifiers. ### Packages Packages are a mechanism for grouping classes and interfaces. Every class or interface has a package. You can explicitly name the package in which it appears by using the `package` statement: `package myPackage;` If no package is specified, a default package is used (usually represented by the current directory). Java has a hierarchical naming convention for naming packages, known as a dot notation. The top level of the notation represents the producer of the source file, such as "java." After that, you would name subhierarchies. For example, the JDK has a package called "lang" that is used to house classes related to the basic mechanics of programming in Java. The String class is in the "lang" package. You can therefore refer to the class from anywhere in your source code as `java.lang.String`. This dot structure is reflected in the directory organization of the source code files that make up the package. Thus, the String.java file will be located off a java/lang subdirectory. Given this, the `package` declaration for the String class would be `package java.lang;` The `package` declaration must be the first non-comment, non-white space line in the file. The `import` statement allows you to use a class defined in another package or source file. You can use the `import` statement in several ways. You can define the full reference to the class, as in the following: `import java.lang.String;` You can also bring in all classes in a package by specifying a wildcard: `import java.lang.*;` Anything imported is thereafter treated as part of the current package for the purposes of compilation. You can also reference a class in the code by using the full package reference: `java.lang.String s = "A string";`
> Note
> The Java runtime system looks for classes by the path specified by the `CLASSPATH` environment variable. You can specify multiple directories in the path. For example: `CLASSPATH=\java\classes;.` This path looks first in a Java directory for the class files and then in the current directory. If the runtime system cannot find the class requested, it may look in the current directory, even if it was not specified in `CLASSPATH`.
A final note about compilation units: A compilation unit is a source code file that has at most one public class or interface. The file must be the same name as the `public` declaration, followed by the .java suffix. So if your source code declares a public class SimpleRecord, the file it appears in must be named SimpleRecord.java. While this convention may seem annoying at first, it will prove to be helpful because your files will give a quick listing of your public classes. The convention also helps you write more modular and easier-to-maintain code. ## The Java Developer's Kit You have now seen all of the fundamentals of Java programming. However, one last thing needs to be discussed. The Java Developer's Kit (JDK) is a series of class libraries organized as packages that give you a set of tools for creating software based on Java. The JDK consists of over 150 classes that provide functionality ranging from manipulating Strings to working with network sockets. A large number of these classes are used and explained throughout this book. Many of them are subject to extended tutorials, while others are explained in the course of describing a chapter project. A quick overview of the JDK will get you ready for the in-depth coverage that follows. Eight packages make up the JDK. These packages are represented by the "java." notation mentioned earlier: java.lang: This package contains classes related to the basic workings of the Java environment. Such activities include wrappers for basic data types and strings, classes for managing threads, a class for mathematical operations, a wide variety of classes for describing exceptions, the System class, and the Object base class. The classes in this package appear throughout this book. java.io: Classes for controlling file and stream operations. The fundamentals of using this package are introduced in Chapter 4, "Enhancing the Spreadsheet Applet." The java.io classes for stream operations are employed in many of the chapter projects. java.util: This package provides a series of utility classes: a class for manipulating dates; basic data structure classes, such as hash tables and stacks; a class and interface for writing classes that use the model-view paradigm; and a tool for parsing Strings. Classes in the java.util package are used throughout this book. java.applet: This package contains the Applet class that ties your applet to its working environment, such as a browser. It also has a class that allows you to more directly exploit the native browser's capabilities. Classes of the java.applet package are explored in detail in Chapter 6, "Building a Catalog Applet," and are employed throughout this book. java.awt: The AWT (Abstract Window Toolkit) package provides simple and advanced tools used to direct the visual interface of a Java applet. The most important of the AWT tools is a set of standard controls that allow you to interact with the applet, such as buttons and text fields. Because AWT is one of the cornerstones of Java applet programming, Part II of this book is dedicated to exploring the package. Furthermore, various techniques for using AWT classes appear throughout the remainder of this book. java.awt.image: This "sub-package" of AWT provides the underpinnings for using images in Java. Although it's a complex package, it is also very powerful. This package is explored in depth in Part III of this book and in Chapter 14, "Advanced Image Processing." java.awt.peer: This package is used to tie AWT widgets to the underlying platform. Because you will be more concerned with the AWT classes than with the inner workings of these peers, this package is not discussed in the following chapters. java.awt.net: The classes in this package are used to write Java network programs. This package includes sockets classes for both the client and the server and classes for referencing URLs. Part IV of this book explores this package in depth and uses its classes to build a Java-based HTTP server. ## Summary This first part of this book has introduced you to the fundamentals of Java. You're now ready to dive head first into the world of Java programming. Wear your safety belt! Each chapter that follows will have an involved project that explores a feature of Java in depth. Some of these projects are quite involved and touch on many of the classes in the JDK. By the end of Chapter 4, you will have been presented with almost every major aspect of writing an applet in Java (and more!). You're encouraged to explore these projects on your computer. Modify the code to give extra functionality or to add print statements to help you understand what is going on. The emphasis of this book is learning by example, so there will be no shortage of code to look at! Contact reference@developer.com with questions or comments. Copyright 1998 EarthWeb Inc., All rights reserved. PLEASE READ THE ACCEPTABLE USAGE STATEMENT. Copyright 1998 Macmillan Computer Publishing. All rights reserved. |