Chapter 1. A Quick Tour

 

See Europe! Ten Countries in Seventeen Days!

 
 --Sign in a travel agent's window

This chapter is a whirlwind tour of the Java programming language that gets you started writing code quickly. We briefly cover the main points of the language, without slowing you down with full-blown detail. Subsequent chapters contain detailed discussions of specific features.

Getting Started

In the Java programming language, programs are built from classes. From a class definition, you can create any number of objects that are known as instances of that class. Think of a class as a factory with blueprints and instructions to build gadgets—objects are the gadgets the factory makes.

A class contains members, the primary kinds being fields and methods. Fields are data variables belonging either to the class itself or to objects of the class; they make up the state of the object or class. Methods are collections of statements that operate on the fields to manipulate the state. Statements define the behavior of the classes: they can assign values to fields and other variables, evaluate arithmetic expressions, invoke methods, and control the flow of execution.

Long tradition holds that the first sample program for any language should print “Hello, world”:

class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world");
    }
}

Use your favorite text editor to type this program source code into a file. Then run the compiler to compile the source of this program into bytecodes, the “machine language” for the Java virtual machine (more on this later in this chapter). Details of editing and compiling source vary from system to system—consult your system manuals for specific information. On the system we use most often—the Java 2 Platform, Standard Edition (J2SE) Development Kit (JDK) provided free of charge by Sun Microsystems—you put the source for HelloWorld into a file named HelloWorld.java. To compile it you type the command

javac HelloWorld.java

To run the program you type the command

java HelloWorld

This executes the main method of HelloWorld. When you run the program, it displays

Hello, world

Now you have a small program that does something, but what does it mean?

The program declares a class called HelloWorld with a single member: a method called main. Class members appear between curly braces { and } following the class name.

The main method is a special method: the main method of a class, if declared exactly as shown, is executed when you run the class as an application. When run, a main method can create objects, evaluate expressions, invoke other methods, and do anything else needed to define an application's behavior.

The main method is declared public—so that anyone can invoke it (in this case the Java virtual machine)—and static, meaning that the method belongs to the class and is not associated with a particular instance of the class.

Preceding the method name is the return type of the method. The main method is declared void because it doesn't return a value and so has no return type.

Following the method name is the parameter list for the method—a sequence of zero or more pairs of types and names, separated by commas and enclosed in parentheses ( and ). The main method's only parameter is an array of String objects, referred to by the name args. Arrays of objects are denoted by the square brackets [] that follow the type name. In this case args will contain the program's arguments from the command line with which it was invoked. Arrays and strings are covered later in this chapter. The meaning of args for the main method is described in Chapter 2 on page 73.

The name of a method together with its parameter list constitute the signature of the method. The signature and any modifiers (such as public and static), the return type, and exception throws list (covered later in this chapter) form the method header. A method declaration consists of the method header followed by the method body—a block of statements appearing between curly braces.

In this example, the body of main contains a single statement that invokes the println method—the semicolon ends the statement. A method is invoked by supplying an object reference (in this case System.out—the out field of the System class) and a method name (println) separated by a dot (.).

HelloWorld uses the out object's println method to print a newline-terminated string on the standard output stream. The string printed is the string literal "Hello,world" , which is passed as an argument to println. A string literal is a sequence of characters contained within double-quotes " and ".

Exercise 1.1Enter, compile, and run HelloWorld on your system.

Exercise 1.2Try changing parts of HelloWorld and see what errors you might get.

Variables

The next example prints a part of the Fibonacci sequence, an infinite sequence whose first few terms are

1
1
2
3
5
8
13
21
34

The Fibonacci sequence starts with the terms 1 and 1, and each successive term is the sum of the previous two terms. A Fibonacci printing program is simple, and it demonstrates how to declare variables, write a simple loop, and perform basic arithmetic. Here is the Fibonacci program:

class Fibonacci {
    /** Print out the Fibonacci sequence for values < 50 */
    public static void main(String[] args) {
        int lo = 1;

        int hi = 1;

       System.out.println(lo);
       while (hi < 50) {
           System.out.println(hi);
           hi = lo + hi;       // new hi
           lo = hi - lo;       /* new lo is (sum - old lo)
                                  that is, the old hi */
       }
   }
}

This example declares a Fibonacci class that, like HelloWorld, has a main method. The first two lines of main are statements declaring two local variables: lo and hi. In this program hi is the current term in the series and lo is the previous term. Local variables are declared within a block of code, such as a method body, in contrast to fields that are declared as members of a class. Every variable must have a type that precedes its name when the variable is declared. The variables lo and hi are of type int, 32-bit signed integers with values in the range –231 through 231–1.

The Java programming language has built-in “primitive” data types to support integer, floating-point, boolean, and character values. These primitive types hold numeric data that is understood directly, as opposed to object types defined by programmers. The type of every variable must be defined explicitly. The primitive data types are:

boolean

either true or false

char

16-bit Unicode UTF-16 character (unsigned)

byte

8-bit integer (signed)

short

16-bit integer (signed)

int

32-bit integer (signed)

long

64-bit integer (signed)

float

32-bit floating-point (IEEE 754)

double

64-bit floating-point (IEEE 754)

For each primitive type there is also a corresponding object type, generally termed a “wrapper” class. For example, the class Integer is the wrapper class for int. In most contexts, the language automatically converts between primitive types and objects of the wrapper class if one type is used where the other is expected.

In the Fibonacci program, we declared hi and lo with initial values of 1. The initial values are set by initialization expressions, using the = operator, when the variables are declared. The = operator (also called the assignment operator), sets the variable named on the left-hand side to the value of the expression on the right-hand side.

Local variables are undefined prior to initialization. You don't have to initialize them at the point at which you declare them, but if you try to use local variables before assigning a value, the compiler will refuse to compile your program until you fix the problem.

As both lo and hi are of the same type, we could have used a short-hand form for declaring them. We can declare more than one variable of a given type by separating the variable names (with their initialization expressions) by commas. We could replace the first two lines of main with the single equivalent line

int lo = 1, hi = 1;

or the much more readable

int lo = 1,
    hi = 1;

Notice that the presence of line breaks makes no difference to the meaning of the statement—line breaks, spaces, tabs, and other whitespace are purely for the programmer's convenience.

The while statement in the example demonstrates one way of looping. The expression inside the while is evaluated—if the expression is true, the loop's body is executed and the expression is tested again. The while is repeated until the expression becomes false. If it never becomes false, the loop will run forever unless something intervenes to break out of the loop, such as a break statement or an exception.

The body of the while consists of a single statement. That could be a simple statement (such as a method invocation), another control-flow statement, or a block—zero or more individual statements enclosed in curly braces.

The expression that while tests is a boolean expression that has the value true or false. Boolean expressions can be formed with the comparison operators (<, <=, >, >=) to compare the relative magnitudes of two values or with the == operator or != operator to test for equality or inequality, respectively. The boolean expression hi< 50 in the example tests whether the current high value of the sequence is less than 50. If the high value is less than 50, its value is printed and the next value is calculated. If the high value equals or exceeds 50, control passes to the first line of code following the body of the while loop. That is the end of the main method in this example, so the program is finished.

To calculate the next value in the sequence we perform some simple arithmetic and again use the = operator to assign the value of the arithmetic expression on the right to the variable on the left. As you would expect, the + operator calculates the sum of its operands, and the - operator calculates the difference. The language defines a number of arithmetic operators for the primitive integer and floating-point types including addition (+), subtraction (-), multiplication (*), and division (/), as well as some other operators we talk about later.

Notice that the println method accepts an integer argument in the Fibonacci example, whereas it accepted a string argument in the HelloWorld example. The println method is one of many methods that are overloaded so that they can accept arguments of different types. The runtime system decides which method to actually invoke based on the number and types of arguments you pass to it. This is a very powerful tool.

Exercise 1.3Add a title to the printed list of the Fibonacci program.

Exercise 1.4Write a program that generates a different sequence, such as a table of squares.

Comments in Code

The English text scattered through the code is in comments. There are three styles of comments, all illustrated in the Fibonacci example. Comments enable you to write descriptive text alongside your code, annotating it for programmers who may read your code in the future. That programmer may well be you months or years later. You save yourself effort by commenting your own code. Also, you often find bugs when you write comments, because explaining what the code is supposed to do forces you to think about it.

Text that occurs between /* and */ is ignored by the compiler. This style of comment can be used on part of a line, a whole line, or more commonly (as in the example) to define a multiline comment. For single line and part line comments you can use // which tells the compiler to ignore everything after it on that line.

The third kind of comment appears at the very top, between /** and */. A comment starting with two asterisks is a documentation comment (“doc comment” for short). Documentation comments are intended to describe declarations that follow them. The comment in the previous example is for the main method. These comments can be extracted by a tool that uses them to generate reference documentation for your classes. By convention, lines within a documentation comment or a /*...*/ comment have a leading asterisk (which is ignored by documentation tools), which gives a visual clue to readers of the extent of the comment.

Named Constants

Constants are values like 12, 17.9, and "StringsLike This". Constants, or literals as they are also known, are the way you specify values that are not computed and recomputed but remain, well, constant for the life of a program.

The Fibonacci example printed all Fibonacci numbers with a value less than 50. The constant 50 was used within the expression of the while loop and within the documentation comment describing main. Suppose that you now want to modify the example to print the Fibonacci numbers with values less than 100. You have to go through the source code and locate and modify all occurrences of the constant 50. Though this is trivial in our example, in general it is a tedious and error-prone process. Further, if people reading the code see an expression like hi< 50 they may have no idea what the constant 50 actually represents. Such “magic numbers” hinder program understandability and maintainability.

A named constant is a constant value that is referred to by a name. For example, we may choose the name MAX to refer to the constant 50 in the Fibonacci example. You define named constants by declaring fields of the appropriate type, initialized to the appropriate value. That itself does not define a constant, but a field whose value could be changed by an assignment statement. To make the value a constant we declare the field as final. A final field or variable is one that once initialized can never have its value changed—it is immutable. Further, because we don't want the named constant field to be associated with instances of the class, we also declare it as static.

We would rewrite the Fibonacci example as follows:

class Fibonacci2 {
    static final int MAX = 50;
    /** Print the Fibonacci sequence for values < MAX */
    public static void main(String[] args) {
        int lo = 1;
        int hi = 1;
        System.out.println(lo);
        while (hi < MAX) {
            System.out.println(hi);
            hi = lo + hi;
            lo = hi - lo;
        }
    }
}

Modifying the maximum value now requires only one change, in one part of the program, and it is clearer what the loop condition is actually testing.

You can group related constants within a class. For example, a card game might use these constants:

class Suit {
    final static int CLUBS    = 1;
    final static int DIAMONDS = 2;
    final static int HEARTS   = 3;
    final static int SPADES   = 4;
}

To refer to a static member of a class we use the name of the class followed by dot and the name of the member. With the above declaration, suits in a program would be accessed as Suit.HEARTS, Suit.SPADES, and so on, thus grouping all the suit names within the single name Suit. Notice that the order of the modifiers final and static makes no difference—though you should use a consistent order. We have already accessed static fields in all of the preceding examples, as you may have realized—out is a static field of class System.

Groups of named constants, like the suits, can often be better represented as the members of an enumeration type, or enum for short. An enum is a special class with predefined instances for each of the named constants the enum represents. For example, we can rewrite our suit example using an enum:

enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }

Each enum constant is a static field that refers to the object for that value—such as Suit.HEARTS. By representing each named constant as an object rather than just an integer value, you improve the type-safety and so the robustness, of your program. Enums are covered in detail in Chapter 6.

Exercise 1.5Change the HelloWorld application to use a named string constant as the string to print. (A string constant can be initialized with a string literal.)

Exercise 1.6Change your program from Exercise 1.3 to use a named string constant for the title.

Unicode Characters

Suppose we were defining a class that dealt with circles and we wanted a named constant that represented the value π. In most programming languages we would name the constant “pi” because in most languages identifiers (the technical term for names) are limited to the letters and digits available in the ASCII character set. In the Java programming language, however, we can do this:

class Circle {
    static final double π = 3.14159265358979323846;
    // ...
}

The Java programming language moves you toward the world of internationalized software: you write code in Unicode, an international character set standard. Unicode basic[1] characters are 16 bits, and together with the supplemental characters (21 bits) provide a character range large enough to write the major languages used in the world. That is why we can use π for the name of the constant in the example. π is a valid letter from the Greek section of Unicode and is therefore valid in source. Most existing code is typed in ASCII, a 7-bit character standard, or ISO Latin-1, an 8-bit character standard commonly called Latin-1. But these characters are translated into Unicode before processing, so the character set is always Unicode.

Flow of Control

“Flow of control” is the term for deciding which statements in a program are executed and in what order. The while loop in the Fibonacci program is one control flow statement, as are blocks, which define a sequential execution of the statements they group. Other control flow statements include for, ifelse, switch, and do–while. We change the Fibonacci sequence program by numbering the elements of the sequence and marking even numbers with an asterisk:

class ImprovedFibonacci {

    static final int MAX_INDEX = 9;

    /**
    * Print out the first few Fibonacci numbers,
    * marking evens with a '*'
    */
   public static void main(String[] args) {
       int lo = 1;
       int hi = 1;
        String mark;

        System.out.println("1: " + lo);
        for (int i = 2; i <= MAX_INDEX; i++) {
            if (hi % 2 == 0)
                mark = " *";
            else
                mark = "";
            System.out.println(i + ": " + hi + mark);
            hi = lo + hi;
            lo = hi - lo;
        }
    }
}

Here is the new output:

1: 1
2: 1
3: 2 *
4: 3
5: 5
6: 8 *
7: 13
8: 21
9: 34 *

To number the elements of the sequence, we used a for loop instead of a while loop. A for loop is shorthand for a while loop, with an initialization and increment section added. The for loop in ImprovedFibonacci is equivalent to this while loop:

int i = 2;   // define and initialize loop index
while (i <= MAX_INDEX) {
    // ...generate the next Fibonacci number and print it...
    i++;   // increment loop index
}

The use of the for loop introduces a new variable declaration mechanism: the declaration of the loop variable in the initialization section. This is a convenient way of defining loop variables that need exist only while the loop is executing, but it applies only to for loops—none of the other control-flow statements allow variables to be declared within the statement itself. The loop variable i is available only within the body of the for statement. A loop variable declared in this manner disappears when the loop terminates, which means you can reuse that variable name in subsequent for statements.

The ++ operator in this code fragment may be unfamiliar if you're new to C-derived programming languages. The ++ operator increments by one the value of any variable it abuts—the contents of variable i in this case. The ++ operator is a prefix operator when it comes before its operand, and postfix when it comes after. The two forms have slightly different semantics but we defer that discussion until Chapter 9. Similarly, minus-minus (--) decrements by one the value of any variable it abuts and can also be prefix or postfix. In the context of the previous example, a statement like

i++;

is equivalent to

i = i + 1;

Expressions where the value assigned to a variable is calculated from the original value of the variable are common enough that there is a short-hand for writing them. For example, another way to write i= i+ 1 is to write

i += 1;

which adds the value on the right-hand side of the += operator (namely 1) to the variable on the left-hand side (namely i). Most of the binary operators (operators that take two operands) can be joined with = in a similar way (such as +=, -=, *=, and /=).

Inside the for loop body we use an ifelse statement to see whether the current hi value is even. The if statement tests the boolean expression between the parentheses. If the expression is true, the statement (which can be a block) in the body of the if is executed. If the expression is false, the statement in the body of the else clause is executed. The else part is optional. If the else is not present, nothing is done when the expression is false. After figuring out which (if any) clause to execute, and then actually executing it, control passes to the code following the body of the if statement.

The example tests whether hi is even using the %, or remainder, operator (also known as the modulus operator). It produces the remainder after dividing the value on the left side by the value on the right. In this example, if the left-side value is even, the remainder is zero and the ensuing statement assigns a string containing the even-number indicator to mark. The else clause is executed for odd numbers, setting mark to an empty string.

The println invocations appear more complex in this example because the arguments to println are themselves expressions that must be evaluated before println is invoked. In the first case we have the expression "1: "+ lo, which concatenates a string representation of lo (initially 1) to the string literal "1: "—giving a string with the value "1: 1". The + operator is a concatenation operator when at least one of its operands is a string; otherwise, it's an addition operator. Having the concatenation operation appear within the method argument list is a common short-hand for the more verbose and tedious:

String temp = "1: " + lo;
System.out.println(temp);

The println invocation within the for loop body constructs a string containing a string representation of the current loop count i, a separator string, a string representing the current value of hi and the marker string.

Exercise 1.7Change the loop in ImprovedFibonacci so that i counts backward instead of forward.

Classes and Objects

The Java programming language, like many object-oriented programming languages, provides a tool to solve programming problems using the notions of classes and objects. Every object has a class that defines its data and behavior. Each class has three kinds of members:

  • Fields are data variables associated with a class and its objects. Fields store results of computations performed by the class.

  • Methods contain the executable code of a class. Methods are built from statements. The way in which methods are invoked, and the statements contained within those methods, are what ultimately directs program execution.

  • Classes and interfaces can be members of other classes or interfaces (you will learn about interfaces soon).

Here is the declaration of a simple class that might represent a point on a two-dimensional plane:

class Point {
    public double x, y;
}

This Point class has two fields representing the x and y coordinates of a point and has (as yet) no methods. A class declaration like this one is, conceptually, a plan that defines what objects manufactured from that class look like, plus sets of instructions that define the behavior of those objects.

Members of a class can have various levels of visibility or accessibility. The public declaration of x and y in the Point class means that any code with access to a Point object can read and modify those fields. Other levels of accessibility limit member access to code in the class itself or to other related classes.

Creating Objects

Objects are created by expressions containing the new keyword. Creating an object from a class definition is also known as instantiation; thus, objects are often called instances.

Newly created objects are allocated within an area of system memory known as the heap. All objects are accessed via object references—any variable that may appear to hold an object actually contains a reference to that object. The types of such variables are known as reference types, in contrast to the primitive types whose variables hold values of that type. Object references are null when they do not reference any object.

Most of the time, you can be imprecise in the distinction between actual objects and references to objects. You can say, “Pass the object to the method” when you really mean “Pass an object reference to the method.” We are careful about this distinction only when it makes a difference. Most of the time, you can use “object” and “object reference” interchangeably.

In the Point class, suppose you are building a graphics application in which you need to track lots of points. You represent each point by its own concrete Point object. Here is how you might create and initialize Point objects:

Point lowerLeft = new Point();
Point upperRight = new Point();
Point middlePoint = new Point();

lowerLeft.x = 0.0;
lowerLeft.y = 0.0;

upperRight.x = 1280.0;
upperRight.y = 1024.0;

middlePoint.x = 640.0;
middlePoint.y = 512.0;

Each Point object is unique and has its own copy of the x and y fields. Changing x in the object lowerLeft, for example, does not affect the value of x in the object upperRight. The fields in objects are known as instance variables, because there is a unique copy of the field in each object (instance) of the class.

When you use new to create an object, a special piece of code, known as a constructor, is invoked to perform any initialization the object might need. A constructor has the same name as the class that it constructs and is similar to a method, including being able to accept arguments. If you don't declare a constructor in your class, the compiler creates one for you that takes no arguments and does nothing. When we say “newPoint() ” we're asking that a Point object be allocated and because we passed in no arguments, the no-argument constructor be invoked to initialize it.

Static or Class Fields

Per-object fields are usually what you need. You usually want a field in one object to be distinct from the field of the same name in every other object instantiated from that class.

Sometimes, though, you want fields that are shared among all objects of that class. These shared variables are known as class variables—variables specific to the class as opposed to objects of the class.

Why would you want to use class variables? Consider, for example, the Sony Walkman factory. Each Walkman has a unique serial number. In object terms, each Walkman object has its own unique serial number field. However, the factory needs to keep a record of the next serial number to be assigned. You don't want to keep that number with every Walkman object. You'd keep only one copy of that number in the factory, or, in object terms, as a class variable.

You obtain class-specific fields by declaring them static, and they are therefore commonly called static fields. For example, a Point object to represent the origin might be common enough that you should provide it as a static field in the Point class:

public static Point origin = new Point();

If this declaration appears inside the declaration of the Point class, there will be exactly one piece of data called Point.origin that always refers to an object at (0.0, 0.0). This static field is there no matter how many Point objects are created, even if none are created. The values of x and y are zero because that is the default for numeric fields that are not explicitly initialized to a different value.

You can probably see now why named constants are declared static.

When you see the word “field” in this book, it generally means a per-object field, although the term non-static field is sometimes used for clarity.

The Garbage Collector

After creating an object with new, how do you get rid of the object when you no longer want it? The answer is simple—stop referring to it. Unreferenced objects are automatically reclaimed by a garbage collector, which runs in the background and tracks object references. When an object is no longer referenced, the garbage collector can remove it from the storage allocation heap, although it may defer actually doing so until a propitious time.

Methods and Parameters

Objects of the previously defined Point class are exposed to manipulation by any code that has a reference to a Point object, because its fields are declared public. The Point class is an example of the simplest kind of class. Indeed, some classes are this simple. They are designed to fit purely internal needs for a package (a group of cooperating classes) or to provide simple data containers when those are all you need.

The real benefits of object orientation, however, come from hiding the implementation of a class behind operations performed on its data. Operations of a class are declared via its methods—instructions that operate on an object's data to obtain results. Methods access internal implementation details that are otherwise hidden from other objects. Hiding data behind methods so that it is inaccessible to other objects is the fundamental basis of data encapsulation.

If we enhance the Point class with a simple clear method (to clear, or reset the coordinates to zero), it might look like this:

public void clear() {
    x = 0.0;
    y = 0.0;
}

The clear method has no parameters, hence the empty () pair after its name; clear is declared void because it does not return any value. Inside a method, fields and other methods of the class can be named directly—we can simply say x and y without an explicit object reference.

Invoking a Method

Objects in general do not operate directly on the data of other objects, although, as you saw in the Point class, a class can make its fields publicly accessible. Well-designed classes usually hide their data so that it can be changed only by methods of that class.

To invoke a method, you provide an object reference to the target object and the method name, separated by a dot. Arguments are passed to the method as a comma-separated list of values enclosed in parentheses. Methods that take no arguments still require the parentheses, with nothing between them.

A method can return only a single value as a result. To return more than one value from a method, you must create an object whose purpose is to hold return values in a single unit and then return that object.

When a method is invoked, the flow of execution leaves the current method and starts executing the body of the invoked method. When the invoked method has completed, the current method continues execution with the code after the method invocation. When we start executing the body of the method, the object that was the target of the method invocation is now the current or receiver object, from the perspective of that method. The arguments passed to the method are accessed via the parameters the method declared.

Here is a method called distance that's part of the Point class shown in previous examples. The distance method accepts another Point object as a parameter, computes the Euclidean distance between itself and the other point, and returns a double-precision floating-point result:

public double distance(Point that) {
    double xdiff = x - that.x;
    double ydiff = y - that.y;
    return Math.sqrt(xdiff * xdiff + ydiff * ydiff);
}

The return statement causes a method to stop executing its body and return execution to the invoking method. If an expression is part of the return statement then the value of that expression is returned as the value of the method invocation. The type of the expression must be compatible with the return type defined for the method. In the example we use the sqrt method of the Math library class to calculate the square root of the sum of the squares of the differences between the two x and y coordinates.

Based on the lowerLeft and upperRight objects created previously, you could invoke distance this way:

double d = lowerLeft.distance(upperRight);

Here upperRight is passed as an argument to distance, which sees it as the parameter that. After this statement executes, the variable d contains the Euclidean distance between lowerLeft and upperRight.

The this Reference

Occasionally, the receiving object needs to know its own reference. For example, the receiving object might want to add itself to a list of objects somewhere. An implicit reference named this is available to methods, and this is a reference to the current (receiving) object. The following definition of clear is equivalent to the one just presented:

public void clear() {
     this.x = 0.0;
     this.y = 0.0;
}

You usually use this as an argument to other methods that need an object reference. The this reference can also be used to explicitly name the members of the current object. Here's another method of Point named move, which sets the x and y fields to specified values:

public void move(double x, double y) {
     this.x = x;
     this.y = y;
}

The move method uses this to clarify which x and y are being referred to. Naming the parameters x and y is reasonable, because you pass x and y coordinates to the method. But then those parameters have the same names as the fields, and therefore the parameter names hide the field names. If we simply wrote x= x we would assign the value of the x parameter to itself, not to the x field as required. The expression this.x refers to the object's x field, not the x parameter of move.

Exercise 1.8Add a method to the Point class that sets the current object's coordinates to those of a passed in Point object.

Static or Class Methods

Just as you can have per-class static fields, you can also have per-class static methods, often known as class methods. Class methods are usually intended to do operations specific to the class itself, usually on static fields and not on specific instances of that class. Class methods are declared using the static keyword and are therefore also known as static methods.

As with the term “field”, the word “method” generally means a per-object method, although the term non-static method is sometimes used for clarity.

Why would you need static methods? Consider the Sony Walkman factory again. The record of the next serial number to be assigned is held in the factory, not in every Walkman. A method that returned the factory's copy of the next available serial number would be a static method, not a method to operate on specific Walkman objects.

The implementation of distance in the previous example uses the static method Math.sqrt to calculate a square root. The Math class supports many methods that are useful for general mathematical manipulation. These methods are declared as static methods because they do not act on any particular instance of the Math class; instead, they group a related set of functionality in the class itself.

A static method cannot directly access non-static members. When a static method is invoked, there's no specific object for the method to operate on, and so no this reference. You could work around this by passing an explicit object reference as an argument to the static method. In general, however, static methods perform class-related tasks and non-static methods perform object-related tasks. Asking a static method to work on object fields is like asking the Walkman factory to change the serial number of a Walkman hanging on the belt of a jogger in Golden Gate Park.

Arrays

Simple variables that hold one value are useful but are not sufficient for many applications. A program that plays a game of cards would want a number of Card objects it could manipulate as a whole. To meet this need, you use arrays.

An array is a collection of variables all of the same type. The components of an array are accessed by simple integer indexes. In a card game, a Deck object might look like this:

public class Deck {
    public static final int DECK_SIZE = 52;
    private Card[] cards = new Card[DECK_SIZE];
    public void print() {
        for (int i = 0; i < cards.length; i++)
            System.out.println(cards[i]);
    }
    // ...
}

First we declare a constant called DECK_SIZE to define the number of cards in a deck. This constant is public so that anyone can find out how many cards are in a deck. Next we declare a cards field for referring to all the cards. This field is declared private, which means that only the methods in the current class can access it—this prevents anyone from manipulating our cards directly. The modifiers public and private are access modifiers because they control who can access a class, interface, field, or method.

We declare the cards field as an array of type Card by following the type name in the declaration with square brackets [ and ]. We initialize cards to a new array with DECK_SIZE variables of type Card. Each Card element in the array is implicitly initialized to null. An array's length is fixed when it is created and can never change.

The println method invocation shows how array components are accessed. It encloses the index of the desired element within square brackets following the array name.

You can probably tell from reading the code that array objects have a length field that says how many elements the array contains. The bounds of an array are integers between 0 and length-1, inclusive. It is a common programming error, especially when looping through array elements, to try to access elements that are outside the bounds of the array. To catch this sort of error, all array accesses are bounds checked, to ensure that the index is in bounds. If you try to use an index that is out of bounds, the runtime system reports this by throwing an exception in your program—an ArrayIndexOutOfBoundsException. You'll learn about exceptions a bit later in this chapter.

An array with length zero is an empty array. Methods that take arrays as arguments may require that the array they receive is non-empty and so will need to check the length. However, before you can check the length of an array you need to ensure that the array reference is not null. If either of these checks fail, the method may report the problem by throwing an IllegalArgumentException. For example, here is a method that averages the values in an integer array:

static double average(int[] values) {
    if (values == null)
        throw new IllegalArgumentException();
    else
        if (values.length == 0)
            throw new IllegalArgumentException();
        else {
            double sum = 0.0;
            for (int i = 0; i < values.length; i++)
                sum += values[i];



           return sum / values.length;
        }

}

This code works but the logic of the method is almost completely lost in the nested ifelse statements ensuring the array is non-empty. To avoid the need for two if statements, we could try to test whether the argument is null or whether it has a zero length, by using the boolean inclusive-OR operator (|):

if (values == null | values.length == 0)
    throw new IllegalArgumentException();

Unfortunately, this code is not correct. Even if values is null, this code will still attempt to access its length field because the normal boolean operators always evaluate both operands. This situation is so common when performing logical operations that special operators are defined to solve it. The conditional boolean operators evaluate their right-hand operand only if the value of the expression has not already been determined by the left-hand operand. We can correct the example code by using the conditional-OR (||) operator:

if (values == null || values.length == 0)
    throw new IllegalArgumentException();

Now if values is null the value of the conditional-OR expression is known to be true and so no attempt is made to access the length field.

The binary boolean operators—AND (&), inclusive-OR (|), and exclusive-OR (^)—are logical operators when their operands are boolean values and bitwise operators when their operands are integer values. The conditional-OR (||) and conditional-AND (&&) operators are logical operators and can only be used with boolean operands.

Exercise 1.9Modify the Fibonacci application to store the sequence into an array and print the list of values at the end.

Exercise 1.10Modify the ImprovedFibonacci application to store its sequence in an array. Do this by creating a new class to hold both the value and a boolean value that says whether the value is even, and then having an array of object references to objects of that class.

String Objects

A String class type deals specifically with sequences of character data and provides language-level support for initializing them. The String class provides a variety of methods to operate on String objects.

You've already seen string literals in examples like the HelloWorld program. When you write a statement such as

System.out.println("Hello, world");

the compiler actually creates a String object initialized to the value of the specified string literal and passes that String object as the argument to the println method.

You don't need to specify the length of a String object when you create it. You can create a new String object and initialize it all in one statement, as shown in this example:

class StringsDemo {
    public static void main(String[] args) {
        String myName = "Petronius";

        myName = myName + " Arbiter";
        System.out.println("Name = " + myName);
    }
}

Here we declare a String variable called myName and initialize it with an object reference to a string literal. Following initialization, we use the String concatenation operator (+) to make a new String object with new contents and store a reference to this new string object into the variable. Finally, we print the value of myName on the standard output stream. The output when you run this program is

Name = Petronius Arbiter

The concatenation operator can also be used in the short-hand += form, to assign the concatenation of the original string and the given string, back to the original string reference. Here's an upgraded program:

class BetterStringsDemo {
    public static void main(String[] args) {
        String myName = "Petronius";
        String occupation = "Reorganization Specialist";

        myName += " Arbiter";

        myName += " ";
        myName += "(" + occupation + ")";
        System.out.println("Name = " + myName);
    }
}

Now when you run the program, you get this output:

Name = Petronius Arbiter (Reorganization Specialist)

String objects have a length method that returns the number of characters in the string. Characters are indexed from 0 through length()-1, and can be accessed with the charAt method, which takes an integer index and returns the character at that index. In this regard a string is similar to an array of characters, but String objects are not arrays of characters and you cannot assign an array of characters to a String reference. You can, however, construct a new String object from an array of characters by passing the array as an argument to a String constructor. You can also obtain an array of characters with the same contents as a string using the toCharArray method.

String objects are read-only, or immutable: The contents of a String never change. When you see statements like

str = "redwood";
// ... do something with str ..
str = "oak";

the second assignment gives a new value to the variable str, which is an object reference to a different string object with the contents "oak". Every time you perform operations that seem to modify a String object, such as the += operation in BetterStringsDemo, you actually get a new read-only String object, while the original String object remains unchanged. The classes StringBuilder and StringBuffer provide for mutable strings and are described in Chapter 13, where String is also described in detail.

The equals method is the simplest way to compare two String objects to see whether they have the same contents:

if (oneStr.equals(twoStr))
    foundDuplicate(oneStr, twoStr);

Other methods for comparing subparts of strings or ignoring case differences are also covered in Chapter 13. If you use == to compare the string objects, you are actually comparing oneStr and twoStr to see if they refer to the same object, not testing if the strings have the same contents.

Exercise 1.11Modify the StringsDemo application to use different strings.

Exercise 1.12Modify ImprovedFibonacci to store the String objects it creates into an array instead of invoking println with them directly.

String Conversion and Formatting

The concatenation operator converts primitive values to strings using the toString method of the corresponding wrapper class. This conversion doesn't give you any control over the format of the resulting string. Formatted conversion can be done with the java.util.Formatter class. A Formatter can write its output to a string, a file, or some other output device. For convenience the System.out.printf method (for “print formatted”) uses a formatter to write to System.out. Formatted output uses a format string together with a set of values to format. The format string contains normal text together with format specifiers that tell the formatter how you want the subsequent values to be formatted. For example, you can print the value of Math.PI to three decimal places using

System.out.printf("The value of Math.PI is %.3f %n", Math.PI);

which prints

The value of Math.PI is 3.142

whereas using println and string concatenation you'd get:

The value of Math.PI is 3.141592653589793

A format specifier consists of at least two parts: it starts with a % character, and it ends with a conversion indicator. The conversion identifies both the type of the value to format, and the basic form. For example, %f says to format a floating-point value in the usual decimal form, as used for Math.PI above, whereas %e says to format a floating point value in scientific notation—for example, 3.142e+00. Integer values can be formatted by %d for normal decimal, or %x for hexadecimal form. A string can be formatted by %s.

The %n conversion causes insertion of the correct line-separator character, something that println does automatically for you. The line-separator depends on the current platform and may not be a simple (newline) character. If you are familiar with printf from the C programming language you need to get used to using %n instead of .

A format specifier can provide additional formatting information. You can provide a width that indicates the minimum number of characters to print—useful for aligning columns of data. If the formatted value has fewer characters than the width, it is padded with spaces to fit the minimum size. This lets you align values easily. Some conversions also allow a precision value to be given, which is written as . followed by a non-negative number. For floating-point values using %f the precision indicates how many decimal places to round to—in the example above the value of Math.PI is rounded to 3 decimal places because of the .3 in the format specifier. If both a width and precision are given they are written in the form width.precision. Additional flags in the format specifier can, among other things, request zero padding (instead of spaces) or left-justification (the default is right justification).

Formatted output is covered in detail in Chapter 22.

Exercise 1.13Rewrite the ImprovedFibonacci program using printf instead of println.

Extending a Class

One of the major benefits of object orientation is the ability to extend, or subclass, the behavior of an existing class and continue to use code written for the original class when acting on an instance of the subclass. The original class is known as the superclass. When you extend a class to create a new class, the new extended class inherits fields and methods of the superclass.

If the subclass does not specifically override the behavior of the superclass, the subclass inherits all the behavior of its superclass because it inherits the fields and methods of its superclass. In addition, the subclass can add new fields and methods and so add new behavior.

Consider the Walkman example. The original model had a single jack for one person to listen to the tape. Later models incorporated two jacks so two people could listen to the same tape. In the object-oriented world, the two-jack model extends, or is a subclass of, the basic one-jack model. The two-jack model inherits the characteristics and behavior of the basic model and adds new behavior of its own.

Customers told Sony they wanted to talk to each other while sharing a tape in the two-jack model. Sony enhanced the two-jack model to include two-way communications so people could chat while listening to music. The two-way communications model is a subclass of the two-jack model, inherits all its behavior, and again adds new behavior.

Sony created many other Walkman models. Later models extend the capabilities of the basic model—they subclass the basic model and inherit features and behavior from it.

Let's look at an example of extending a class. Here we extend our former Point class to represent a pixel that might be shown on a screen. The new Pixel class requires a color in addition to x and y coordinates:

class Pixel extends Point {
    Color color;

    public void clear() {
        super.clear();
        color = null;
    }
}

Pixel extends both the data and behavior of its Point superclass. Pixel extends the data by adding a field named color. Pixel also extends the behavior of Point by overriding Point's clear method.

Pixel objects can be used by any code designed to work with Point objects. If a method expects a parameter of type Point, you can hand it a Pixel object and it just works. All the Point code can be used by anyone with a Pixel in hand. This feature is known as polymorphism—a single object like Pixel can have many ( poly-) forms (-morph) and can be used as both a Pixel object and a Point object.

Pixel's behavior extends Point's behavior. Extended behavior can be entirely new (adding color in this example) or can be a restriction on old behavior that follows all the original requirements. An example of restricted behavior might be Pixel objects that live inside some kind of Screen object, restricting x and y to the dimensions of the screen. If the original Point class did not forbid restrictions for coordinates, a class with restricted range would not violate the original class's behavior.

An extended class often overrides the behavior of its superclass by providing new implementations of one or more of the inherited methods. To do this the extended class defines a method with the same signature and return type as a method in the superclass. In the Pixel example, we override clear to obtain the proper behavior that Pixel requires. The clear that Pixel inherited from Point knows only about Point's fields but obviously can't know about the new color field declared in the Pixel subclass.

Invoking Methods of the Superclass

To make Pixel do the correct “clear” behavior, we provide a new implementation of clear that first invokes its superclass's clear using the super reference. The super reference is a lot like the this reference described previously except that super references things from the superclass, whereas this references things from the current object.

The invocation super.clear() looks to the superclass to execute clear as it would for an object of the superclass—namely, Point. After invoking super.clear() to clear out the Point part of the object, we add new functionality to set color to a reasonable empty value. We choose null, a reference to no object.

What would happen had we not invoked super.clear() in the previous example? Pixel's clear method would set color to its null value, but the x and y variables that Pixel inherited from Point would not be set to any “cleared” values. Not clearing all the values of a Pixel object, including its Point parts, is probably a bug.

When you invoke super.method (), the runtime system looks back up the inheritance hierarchy to the first superclass that contains the required method . If Point didn't have a clear method, for example, the runtime system would look at Point's superclass for such a method and invoke that, and so on.

For all other references, invoking a method uses the actual class of the object, not the type of the object reference. Here is an example:

Point point = new Pixel();
point.clear();  // uses Pixel's clear()

In this example, Pixel's version of clear is invoked even though the variable that holds the Pixel is declared as a Point reference. But if we invoke super.clear() inside one of Pixel's methods, that invocation will use the Point class's implementation of the clear method.

The Object Class

Classes that do not explicitly extend any other class implicitly extend the Object class. All objects are polymorphically of class Object, so Object is the general-purpose type for references that can refer to objects of any class:

Object oref = new Pixel();
oref = "Some String";

In this example, oref is correctly assigned references to Pixel and String objects even though those classes have no relationship except that both have Object as a superclass. The Object class also defines several important methods that you'll learn about in Chapter 3.

Type Casting

The following code fragment seems quite reasonable (if not particularly useful) but results in a compile-time error:

String name = "Petronius";
Object obj = name;
name = obj;    // INVALID: won't compile

We declare and initialize a String reference which we then assign to a general-purpose Object reference, and then we try to assign the reference to a String back to the String reference. Why doesn't this work? The problem is that while a String is always an Object, an Object is not necessarily a String, and even though you can see that in this case it really is a String, the compiler is not so clever. To help the compiler you have to tell it that the object referred to by obj is actually a String and so can be assigned to name:

name = (String) obj;    // That's better!

Telling the compiler that the type of an expression is really a different type is known as type casting or type conversion. You perform a type cast by prefixing the expression with the new type in parentheses. The compiler doesn't automatically trust you when you do this and so it checks that you are telling the truth. A smart compiler may be able to tell at compile time that you are telling the truth; otherwise, it will insert a run time check to verify that the cast really is allowed. If you lie to the compiler and the run time check fails, the runtime system reports this by throwing a ClassCastException. As the Java programming language is strongly typed there are strict rules concerning assignments between types.

Exercise 1.14Sketch out a set of classes that reflects the class structure of the Sony Walkman product family we have described. Use methods to hide the data, making all the data private and the methods public. What methods would belong in the Walkman class? Which methods would be added for which extended classes?

Interfaces

Sometimes you want only to declare methods an object must support but not to supply the implementation of those methods. As long as their behavior meets specific criteria—called the contract—implementation details of the methods are irrelevant. These declarations define a type, and any class that implements those methods can be said to have that type, regardless of how the methods are implemented. For example, to ask whether a particular value is contained in a set of values, details of how those values are stored are irrelevant. You want the methods to work equally well with a linked list of values, a hashtable of values, or any other data structure.

To support this, you can define an interface. An interface is like a class but has only empty declarations of its methods. The designer of the interface declares the methods that must be supported by classes that implement the interface and declares what those methods should do. Here is a Lookup interface for finding a value in a set of values:

interface Lookup {
    /** Return the value associated with the name, or
     *  null if there is no such value */
    Object find(String name);
}

The Lookup interface declares one method, find, that takes a String and returns the value associated with that name, or null if there is no associated value. In the interface no implementation can be given for the method—a class that implements the interface is responsible for providing a specific implementation—so instead of a method body we simply have a semicolon. Code that uses references of type Lookup (references to objects that implement the Lookup interface) can invoke the find method and get the expected results, no matter what the actual type of the object is:

void processValues(String[] names, Lookup table) {
    for (int i = 0; i < names.length; i++) {
        Object value = table.find(names[i]);
        if (value != null)
            processValue(names[i], value);
    }
}

A class can implement as many interfaces as you choose. This example implements Lookup using a simple array (methods to set or remove values are left out for simplicity):

class SimpleLookup implements Lookup {
    private String[] names;
    private Object[] values;

    public Object find(String name) {
        for (int i = 0; i < names.length; i++) {
            if (names[i].equals(name))
                return values[i];
        }
        return null;    // not found
    }

    // ...
}

An interface can also declare named constants that are static and final. Additionally, an interface can declare other nested interfaces and even classes. These nested types are discussed in detail in Chapter 5. All the members of an interface are implicitly, or explicitly, public—so they can be accessed anywhere the interface itself is accessible.

Interfaces can be extended using the extends keyword. An interface can extend one or more other interfaces, adding new constants or new methods that must be implemented by any class that implements the extended interface.

A class's supertypes are the class it extends and the interfaces it implements, including all the supertypes of those classes and interfaces. So an object is not only an instance of its particular class but also of any of its supertypes, including interfaces. An object can be used polymorphically with both its superclass and any superinterfaces, including any of their supertypes.

A class's subtypes are the classes that extend it, including all of their subtypes. An interface's subtypes are the interfaces that extend it, and the classes that implement it, including all of their subtypes.

Exercise 1.15Write an interface that extends Lookup to declare add and remove methods. Implement the extended interface in a new class.

Generic Types

Classes and interfaces can be declared to be generic types. A generic class or interface represents a family of related types. For example, in

interface List<T> {
    // ... methods of List ...
}

List<T> (read as “list of T”) declares a generic list that can be used for any non-primitive type T. You could use such a declaration to have a list of Point objects (List<Point>), a list of String objects (List<String>), a list of Integer objects (List<Integer>), and so on. In contrast to a raw List, which can hold any kind of Object, a List<String> is known to hold only String objects, and this fact is guaranteed at compile time—if you try to add a plain Object, for example, you'll get a compile-time error.

Consider the Lookup interface from the previous section, it could be declared in a generic form:

interface Lookup<T> {
    T find(String name);
}

Now, rather than returning Object, the find method returns a T, whatever T may be. We could then declare a class for looking up integers, for example:

class IntegerLookup implements Lookup<Integer> {
    private String[] names;
    private Integer[] values;

    public Integer find(String name) {
        for (int i = 0; i < names.length; i++) {
            if (names[i].equals(name))
                return values[i];
        }
        return null;    // not found
    }

    // ...
}

Class IntegerLookup is not itself a generic class, rather it implements a generic interface, providing a concrete type—Integer—in place of the abstract type parameter T. Compared to the SimpleLookup class, this class can store items in an array of Integer and find returns an Integer.

How would we modify processValues to work with generic implementations of Lookup? Here's one attempt to do so:

void processValues(String[] names, Lookup<Object> table) {
    for (int i = 0; i < names.length; i++) {
        Object value = table.find(names[i]);
        if (value != null)
            processValue(names[i], value);
    }
}

This compiles okay, and appears reasonable—table is an instance of a class that implements Lookup for any kind of Object. As Integer instances are Object instances then we should be able to do this:

Lookup<Integer> l = new IntegerLookup();
// ... add entries to l ...
String[] names = { "One", "two" };
processValues(names, l);    // INVALID: won't compile

But we find that this code won't compile. The problem is that, as defined, processValues will only accept an instance of a class that implements Lookup<Object>, and IntegerLookup does not do that. Even though Integer is a subtype of Object, Lookup<Integer> is not a subtype of Lookup<Object> and therefore an instance of the former can not be used where an instance of the latter is required. Is there a way to declare that we want to deal with an instance that implements Lookup of anything? Yes—we use what is called a wildcard to specify the type parameter:

void processValues(String[] names, Lookup<?> table) {
    for (int i = 0; i < names.length; i++) {
        Object value = table.find(names[i]);
        if (value != null)
            processValue(names[i], value);
    }
}

The wildcard is written as ? and is read as “of unspecified type”, or just as “of some type”: table is an instance of a class that implements a lookup of some type. We don't know what type, and in this example we don't care. The only thing we know about the type we can lookup is that it must at least be an Object, so we can invoke table.find and store the return value as an Object. Now we can use IntegerLookup with processValues without a problem.

Suppose in our application we knew that we would always be looking up Integer or Long or Double, etc.—all the subclasses of the Number class. We know, from the previous example, that we can't define processValues in terms of Lookup<Number>, but there is a way to limit the types that the wildcard can represent:

void processValues(String[] names,
                   Lookup<? extends Number> table) {
    for (int i = 0; i < names.length; i++) {
        Number value = table.find(names[i]);
        if (value != null)

            processValue(names[i], value);
    }
}

Where ? on its own is the unbounded wildcard, that can represent any type, “?extends X” is a bounded wildcard: It can only represent the type X or any type that extends or implements X (depending on whether X is a class or interface). This time in processValues we know that, at worst, table.find will return a Number, so we can store that return value in a variable of type Number.

There is a lot more to generic types and you'll learn about their full power in Chapter 11. Meanwhile, you know enough about them to understand the simple usages that will occur in the early chapters of this book.

Exceptions

What do you do when an error occurs in a program? In many languages, error conditions are signaled by unusual return values like –1. Programmers often don't check for exceptional values because they may assume that errors “can't happen.” On the other hand, adding error detection and recovery to what should be a straightforward flow of logic can complicate that logic to the point where the normal flow is completely obscured. An ostensibly simple task such as reading a file into memory might require about seven lines of code. Error checking and reporting expands this to 40 or more lines. Making normal operation the needle in your code haystack is undesirable.

Checked exceptions manage error handling. Checked exceptions force you to consider what to do with errors where they may occur in the code. If a checked exception is not handled, this is noticed at compile time, not at run time when problems have been compounded because of the unchecked error.

A method that detects an unusual error condition throws an exception. Exceptions can be caught by code farther back on the calling stack—this invoking code can handle the exception as needed and then continue executing. Uncaught exceptions result in the termination of the thread of execution, but before it terminates the thread's UncaughtExceptionHandler is given the opportunity to respond to the exception as best it can—perhaps doing nothing more than reporting that the exception occurred. Threads and UncaughtExceptionHandlers are discussed in detail in Chapter 14.

An exception is an object, with type, methods, and data. Representing exceptions as objects is useful, because an exception object can include data, methods, or both to report on or recover from specific kinds of exceptions. Exception objects are generally derived from the Exception class, which provides a string field to describe the error. All exceptions must be subclasses of the class Throwable, which is the superclass of Exception.

The paradigm for using exceptions is the try–catch–finally sequence: you try something; if that something throws an exception, you catch the exception; and finally you clean up from either the normal code path or the exception code path, whichever actually happened.

Here is a getDataSet method that returns a set of data read from a file. If the file for the data set cannot be found or if any other I/O exception occurs, this method throws an exception describing the error. First, we define a new exception type BadDataSetException to describe such an error. Then we declare that the method getDataSet throws that exception using a throws clause in the method header:

class BadDataSetException extends Exception { }

  class MyUtilities {
    public double[] getDataSet(String setName)
        throws BadDataSetException
    {
        String file = setName + ".dset";
        FileInputStream in = null;
        try {
            in = new FileInputStream(file);
            return readDataSet(in);
        } catch (IOException e) {
            throw new BadDataSetException();
        } finally {
            try {
                if (in != null)
                    in.close();
            } catch (IOException e) {
                ;    // ignore: we either read the data OK
                     // or we're throwing BadDataSetException
            }
        }
    }
    // ... definition of readDataSet ...
}

First we turn the data set name into a file name. Then we try to open the file and read the data using the method readDataSet. If all goes well, readDataSet will return an array of doubles, which we will return to the invoking code. If opening or reading the file causes an I/O exception, the catch clause is executed. The catch clause creates a new BadDataSetException object and throws it, in effect translating the I/O exception into an exception specific to getDataSet. Methods that invoke getDataSet can catch the new exception and react to it appropriately. In either case—returning successfully with the data set or catching and then throwing an exception—the code in the finally clause is executed to close the file if it was opened successfully. If an exception occurs during close, we catch it but ignore it—the semicolon by itself forms an empty statement, which does nothing. Ignoring exceptions is not a good idea in general, but in this case it occurs either after the data set is successfully read—in which case the method has fulfilled its contract—or the method is already in the process of throwing an exception. If a problem with the file persists, it will be reported as an exception the next time we try to use it and can be more appropriately dealt with at that time.

You will use finally clauses for cleanup code that must always be executed. You can even have a try-finally statement with no catch clauses to ensure that cleanup code will be executed even if uncaught exceptions are thrown.

If a method's execution can result in checked exceptions being thrown, it must declare the types of these exceptions in a throws clause, as shown for the getDataSet method. A method can throw only those checked exceptions it declares—this is why they are called checked exceptions. It may throw those exceptions directly with throw or indirectly by invoking a method that throws exceptions. Exceptions of type RuntimeException, Error, or subclasses of these exception types are unchecked exceptions and can be thrown anywhere, without being declared.

Checked exceptions represent conditions that, although exceptional, can reasonably be expected to occur, and if they do occur must be dealt with in some way—such as the IOException that may occur reading a file. Declaring the checked exceptions that a method throws allows the compiler to ensure that the method throws only those checked exceptions it declared and no others. This check prevents errors in cases in which your method should handle another method's exceptions but does not. In addition, the method that invokes your method is assured that your method will not result in unexpected checked exceptions.

Unchecked exceptions represent conditions that, generally speaking, reflect errors in your program's logic and cannot be reasonably recovered from at run time. For example, the ArrayIndexOutOfBoundsException thrown when you access outside the bounds of an array tells you that your program calculated an index incorrectly, or failed to verify a value to be used as an index. These are errors that should be corrected in the program code. Given that you can make errors writing any statement it would be totally impractical to have to declare or catch all the exceptions that could arise from those errors—hence they are unchecked.

Exercise 1.16Add fields to BadDataSetException to hold the set name and the I/O exception that signaled the problem so that whoever catches the exception will know details about the error.

Annotations

Annotations provide information about your program, or the elements of that program (classes, methods, fields, variables, and so forth), in a structured way that is amenable to automated processing by external tools. For example, in many organizations all code that gets written must be reviewed by a programmer other than the author. Keeping track of what code has been reviewed, by whom, and when requires a lot of bookkeeping and is a task most suited to an automated tool—and indeed some code management systems will support this kind of task. Annotations allow you to provide this review information with the code that is being reviewed. For example, here is how you could annotate a class with review information:

@Reviewed(reviewer = "Joe Smith", date = 20050331)
public class Point {
    // ...
}

An annotation is considered a modifier (like public or static) and should appear before other modifiers, on a line of its own. It is indicated by an @ character followed by the name of an annotation type—in this case Reviewed. An annotation type is a special kind of interface, whose members are known as elements. Here is the definition of our Reviewed annotation type:

@interface Reviewed {
    String reviewer();
    int date();
}

This is very similar to an interface declaration, except the interface keyword is again preceded by the @ character. When you apply an annotation to a program element, you supply values for all of the elements of the annotation type as shown above: The string "JoeSmith" is used to set the value of the reviewer element, while the int value 20050331 is used to set the value of the date element. A tool could extract these values from either the source file, or (more often) a compiled class file, and determine, for example, whether a class has been reviewed since it was last modified.

While the language defines the syntax of annotation types, their actual purpose is determined by the tools that recognize them. We won't have anything else to say about annotations until they are discussed in Chapter 15, except to mention where they can be applied.

Packages

Name conflicts are a major problem when you're developing reusable code. No matter how carefully you pick names for classes, someone else is likely to use that name for a different purpose. If you use simple, descriptive names, the problem gets worse since such names are more likely to be used by someone else who was also trying to use simple, descriptive names. Words like “list,” “event,” “component,” and so on are used often and are almost certain to clash with other people's uses.

The standard solution for name collision in many programming languages is to use a package prefix at the front of every class, type, global function, and so on. Prefix conventions create naming contexts to ensure that names in one context do not conflict with names in other contexts. These prefixes are usually a few characters long and are usually an abbreviation of the product name, such as Xt for the X-Windows Toolkit.

When code uses only a few packages, the likelihood of prefix conflict is small. However, since prefixes are abbreviations, the probability of a name conflict increases with the number of packages used.

The Java programming language has a formal notion of package that has a set of types and subpackages as members. Packages are named and can be imported. Package names are hierarchical, with components separated by dots. When you use part of a package, either you use its fully qualified name—the type name prefixed by the package name, separated by a dot—or you import all or part of the package. Importing all, or part, of a package, simply instructs the compiler to look in the package for types that it can't find defined locally. Package names also give you control over name conflicts. If two packages contain classes with the same name, you can use the fully qualified class name for one or both of them.

Here is an example of a method that uses fully qualified names to print the current day and time using the utility class Date (documented in Chapter 22), which, as with all time-based methods, considers time to be in milliseconds since the epoch (00:00:00 GMT, January 1, 1970):

class Date1 {
    public static void main(String[] args) {
        java.util.Date now = new java.util.Date();
        System.out.println(now);
    }
}

And here is a version that uses import to declare the type Date:

import java.util.Date;

class Date2 {
    public static void main(String[] args) {
        Date now = new Date();
        System.out.println(now);
    }
}

When the compiler comes to the declaration of now it determines that the Date type is actually the java.util.Date type, because that is the only Date type it knows about. Import statements simply provide information to the compiler; they don't cause files to be “included” into the current file.

The name collision problem is not completely solved by the package mechanism. Two projects can still give their packages the same name. This problem can be solved only by convention. The standard convention is to use the reversed Internet domain name of the organization to prefix the package name. For example, if the Acme Corporation had the Internet domain acme.com, it would use package names starting with com.acme, as in com.acme.tools.

Classes are always part of a package. A package is named by providing a package declaration at the top of the source file:

package com.sun.games;

class Card {
    // ...
}

If a package is not specified via a package declaration, the class is made part of an unnamed package. An unnamed package may be adequate for an application (or applet) that is not loaded with any other code. Classes destined for a library should always be written in named packages.

The Java Platform

The Java programming language is designed to maximize portability. Many details are specifically defined for all implementations. For example, a double is a 64-bit IEEE 754 floating-point number. Many languages leave precise definitions to particular implementations, making only general guarantees such as minimum range, or they provide a way to ask the system what the range is on the current platform.

These portable definitions for the Java programming language are specific all the way down to the machine language into which code is translated. Source code is compiled into Java bytecodes, which are designed to be run on a Java virtual machine. Bytecodes are a machine language for an abstract machine, executed by the virtual machine on each system that supports the Java programming language.[2] Other languages can also be compiled into Java bytecodes.

The virtual machine provides a runtime system, which provides access to the virtual machine itself (for example, a way to start the garbage collector) and to the outside world (such as the output stream System.out). The runtime system checks security-sensitive operations with a security manager or access controller. The security manager could, for example, forbid the application to read or write the local disk, or could allow network connections only to particular machines. Exactly what an application is allowed to do, is determined by the security policy in force when the application runs.

When classes are loaded into a virtual machine, they will first be checked by a verifier that ensures the bytecodes are properly formed and meet security and safety guarantees (for example, that the bytecodes never attempt to use an integer as a reference to gain access to parts of memory).

These features combined give platform independence to provide a security model suitable for executing code downloaded across the network at varying levels of trust. Source code compiled into Java bytecodes can be run on any machine that has a Java virtual machine. The code can be executed with an appropriate level of protection to prevent careless or malicious class writers from harming the system. The level of trust can be adjusted depending on the source of the bytecodes—bytecodes on the local disk or protected network can be trusted more than bytecodes fetched from arbitrary machines elsewhere in the world.

Other Topics Briefly Noted

There are several other features that we mention briefly here and cover later in more detail:

  • Threads—. The language has built-in support for creating multithreaded applications. It uses per-object and per-class monitor-style locks to synchronize concurrent access to object and class data. See Chapter 14 for more details.

  • Reflection—. The reflection mechanism (known as runtime type information —RTTI—in some languages) allows browsing of class types and their members, and programmatic manipulation of objects. See Chapter 16 for more information about reflection.

  • I/O—. The java.io package provides many different kinds of input and output classes. See Chapter 20 for specifics of the I/O capabilities.

  • Collections—. You will find many useful collection classes, such as List and HashMap in the java.util package. See Chapter 21 for more information about collections.

  • Utility interfaces and classes—. The java.util package has many other useful classes, such as BitSet, Scanner, and Date. See Chapter 22 for more information about these utility classes.

  • Internationalization—. If your application is available to people all over the world, you need to be able to adapt the user interface to support interacting with the user in their language, and using their conventions—such as how dates are presented, or how numbers are formatted. There are a number of classes, mainly in the java.util and java.text packages that aid you in this. Internationalization and localization are discussed in Chapter 24.

 

Careful—we don't want to learn from this!

 
 --Calvin and Hobbes


[1] Basic Multilingual Plane, or BMP, in Unicode terminology.

[2] A system can, of course, implement the Java virtual machine in silicon — that is, using a special-purpose chip. This does not affect the portability of the bytecodes; it is just another virtual machine implementation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.195.29