Chapter 3. An Experienced Programmer’s Introduction to Java

Here the reader is rapidly acquainted with the manner in which Java implements the OO (Object-Oriented) concepts. The language’s statements are unceremoniously presented. Much deference is paid to other texts in print and on the Web, since this is well traveled ground. We then present a simple sample Java application that will be used throughout the rest of this introductory part as an example that can be easily built in all of the Java environments available for Linux.

What You Will Learn

  • Java syntax and semantics for the familiar (to an experienced programmer) programming constructs.

  • How Java implements the OO: buzzwords of inheritance, encapsulation, and polymorphism.

  • How Java deals with the absence of C++-style multiple inheritance.

  • Why the absence of templates in Java is not as crippling as a C++ programmer might suppose.

  • How final is better than virtual and how interfaces are often better than multiple inheritance.

This is going to be a whirlwind tour. Our book assumes that you already know programming in general, and have had some exposure to OO programming. We are going to distill into a single chapter material that comprises significant portions of other books. In particular, if there are concepts here that you are not already familiar with, look at Chapters 1–9 of Bruce Eckel’s wonderful book, Thinking in Java, 3rd ed., published by Prentice Hall PTR (ISBN 0-131-00287-2). It is, genuinely, one of the best books on the market for learning the Java language and the design principles Java embodies.

If you are somewhat new to programming, but technically quite adept (maybe a system administrator or database administrator with little formal programming background), you may want to supplement your reading with a book that, unlike Eckel’s, is targeted more toward the novice programmer. We like Java Software Solutions: Foundations of Program Design, 3rd ed., by John Lewis and William Loftus, Addison-Wesley, 2003 (ISBN 0-201-78129-8). It will introduce the concepts behind the programming constructs, whereas we will assume that you know these concepts so we can focus only on the Java syntax.

Fundamental Language Elements

Before the object-oriented structures, Java (like C) has a small number of fundamental statements and (again, like C and C++) some fundamental “nonobject” data types.[1]

Scalar Types

Java has a number of built-in scalar (in this context, nonobject) types. We discuss these below.

Integer Types

Java defines four integer types—byte, short, int, and long. Unlike some languages, Java defines the precision, that is, the bit size, of these types.

  • byte: 8 bits

  • short: 16 bits

  • int: 32 bits

  • long: 64 bits

For Java, the goal is “compile once, run anywhere.” Defining that int means 32 bits—everywhere—helps to achieve this goal. By contrast, when C language was first defined, its goal was different: to be available quickly on a variety of architectures, not to produce object code that would be portable between architectures. Thus, for C, the choice was up to the compiler developer to choose a size that was most “natural” (i.e., convenient) for that particular architecture.[2] This would make it easiest on the compiler writer. It succeeded—C was an easy language to implement, and it spread widely.

Back to Java. Note that all these values are signed. Java has no “unsigned” type.

Note also that byte is listed here. It can be treated as a numeric value, and calculations performed on it. Note especially that it is a signed number (i.e., values range from –128 to 127 and not from 0 to 255). Be careful when promoting a byte to an int (or other numeric value). Java will sign-extend on the promotion. If the value in the byte variable was a character (e.g., an ASCII value) then you really wanted it treated like an unsigned value. To assign such a value to an int you’ll need to mask off the upper bits, as in Example 3.1.

You may never encounter such a situation, but if you are ever working with bytes (e.g., byte arrays) and start to mess with the individual bytes, don’t say we didn’t warn you.

Example 3.1. Coercing a byte to int as if the byte were unsigned

byte c;
int ival;
...
ival = ((int) c) && 0xFF; // explicit cast needed

Floating Point Types

Java provides two different precisions of floating point numbers. They are:

  • float: 32 bits

  • double: 64 bits

The float type is not very useful at that precision, so double is much more commonly seen. For other situations where precision is important, but you can spare some cycles, consider the BigDecimal and BigInteger object classes.

Java floating point numbers are specified to follow the IEEE floating point standard, IEEE 754.

Other Types

Java also has a boolean type, along with constants true and false. In Java, unlike C/C++, boolean values are a distinct type, and do not convert to numeric types. For example, it is common in C to write:

if (strlen(instr)) {
 strcpy(buffer, instr);
}

In this case, the integer result of strlen() is used as a boolean, where 0 is false and any other value is true. This doesn’t work in Java. The expression must be of a boolean type.

Java also has a char type, which is not the same as a byte. The char is a character, and in Java, characters are represented using Unicode (UTF-16). They take two bytes each.

For more discussion on the differences between bytes and chars and about Unicode, read the Java tutorial on the java.sun.com Web site or visit www.unicode.org, the international standard’s Web site.

Operators

Before we move on to the topic of arrays (which are sort of a hybrid scalar/object type in Java), let’s spend a moment on the operators that can be used in expressions (Table 3.1). Most deal with numeric or boolean operands. For completeness, we’ll include the operators that deal exclusively with arrays (the “[]”) and classes (“.”, new, and instanceof), even though we haven’t discussed them yet.

Table 3.1. Arithmetic and logical Java operators in order of precedence

Operators

Explanation

[] .

array indexing, member reference

- ++ -- ! ~

unary operators: negate, increment, decrement, logical-not, bitwise-not

(type) new

coercion, or casting to a different type; creating a new object

* / %

multiplication, division, remainder

+ -

addition, subtraction

<< >> >>>

shift-left, shift-right-sign-extend, shift-right-zero-fill

< > <= >= instanceof

less-than, greater-than, less-or-equal, greater-or-equal, comparing object types

== !=

equal, not-equal

&

bitwise-and (boolean for boolean operands with no short-circuit)[*]

^

bitwise-xor (with boolean operands it is a boolean-xor)[**]

|

bitwise-or (boolean for boolean operands with no short-circuit)[*]

&&

logical-and (with short-circuit)[*]

||

logical-or (with short-circuit)[*]

?:

Inline if expression, e.g., a ? b : c says, if a is true, then the value is b, else it is c.

= += -= *= /= %= <<= >>= >>>= &= ^= |=

Assignment; those with an operator, as in a op= b will perform the operation a op b then assign the result back to a.

[*] In Java there are two ways to do a boolean AND operation: using & or &&. Remember that for “a AND b”, if either is false, then the result is false. That means that if “a” is false, there is no need to evaluate “b” because it will not affect the result. Skipping the evaluation of “b” in this case is called short-circuiting. Java will use short-circuit evaluation when using the && operator, but not &. The same applies to the OR operators || and | where Java can short-circuit on a true evaluation of the first operand for ||. This is an important distinction when “a” and “b” are not just simple variable references but rather method calls or other complex expressions, especially ones with side effects.

[**] XOR is exclusive or, where the result of “a XOR b” is true if “a” or “b” is true, but not both. For bitwise operands, “a” and “b” refer here to bits in the operand; for boolean operands it is the one value. Examples: 5^6 is 3; true^false is true but true^true is false.

Operators listed on the same row in the table have the same precedence. Operators with the same precedence, except for the unary operators, group from left to right. Unary operators group from right to left.

Arrays

Example 3.2 demonstrates the array syntax in Java.

Example 3.2. Example array syntax

int [] oned = new int[35];               // array = new type[size]
int alta [] = {1, 3, 5, 14, 11, 6, 24};  // alternative syntax plus
                                         // initialization
int j=0;

for(int i=0; i<35; i++) {
  oned[i] = valcomp(i, prop, alta[j]);   // array[index]
  if (++j > alta.length) {               // array.length
    j = 0;
  }
}

The array can be declared with the [] on either side of the variable name. While our example uses the primitive type int, array syntax looks just the same for any objects.

Note that in Java, one doesn’t declare the size of the array. It’s only in creating the array with a new that the array gets created to a particular size. (The {...} syntax is really just a special compiler construct for what is essentially a new followed by an assignment of values.)

Multidimensional arrays follow the syntax of simple arrays, but with additional adjacent square brackets, as shown in Example 3.3.

Example 3.3. Example two-dimensional array syntax

int [][] ragtag = new int[35][10];

for (int i=0; i<35; i++) {
  for (int j=0; j<10; j++) {
    ragtag[i][j] = i*j;
  } // next j
} // next i

Multidimensional arrays are built as arrays of arrays. Therefore, we can actually allocate it in a piecemeal fashion and have ragged-edged arrays, where each row has a different number of columns, as shown in Example 3.4.

Example 3.4. Ragged two-dimensional array syntax

int [][] ragtag = new int[17][];

for (int i=0; i<17; i++) {
  ragtag[i] = new int[10+i];
} // next i

for (int i=0; i<17; i++) {
  System.out.println("ragtag["+i+"] is "+ragtag[i].length+" long.");
} // next i

For a fuller discussion of arrays, see Chapter 9 of Eckel or Chapter 6 of Lewis&Loftus.

Object Types

The real power in Java, or any object-oriented language, comes not from the scalar types, cool operators, or powerful control statements it provides (see below), but from its objects.

Object-oriented programming is a relatively recent innovation in software design and development. Objects are meant to embody the real world in a more natural way; they give us a way to describe, in our programs, the real-world objects with which we deal. If you are programming a business application, think of real-world business objects such as orders, customers, employees, addresses, and so on. Java is an object-oriented programming language and thus has some significant syntax related to OO concepts.

If you are new to object-oriented programming, be sure to read Chapter 1 of Eckel’s Thinking in Java.

In Java, we define a class to represent the objects about which we want to program. A class consists of the data and the methods to operate on that data. When we create a new instance of some class, that instance is an object of that type of class. Example 3.5 shows a simple class.

Example 3.5. Simple class

class
PairInt
{
  // data
  int i;
  int j;

  // constructors
  PairInt() { i=0; j=0; }
  PairInt(int ival, int jval) { i=ival; j=jval; }

  // methods
  setI(int val) { i=val; }
  setJ(int val) { j=val; }
  int getI() { return i; }
  int getJ() { return j; }
}

Note that this class defines both data (i, j) and methods (setI(), getJ(), and so on). We put all this into a file named PairInt.java to match the name of the class definition.

If some other Java code wanted to create and use a PairInt object, it would create it with the new keyword followed by a call to a constructor (Example 3.6).

This example shows only a snippet of code, not the entire PairInt class. That class, though, would likely reside in its own source file (named for its class name). In Java you normally create lots of files, one for each class. When it’s time to run the program, its various classes are loaded as needed. We’ll discuss grouping classes together and how Java locates them in Section 3.3.1.

Example 3.6. Using simple class

// declare a reference to one:
PairInt twovals;

// now create one:
twovals = new PairInt(5, 4);

// we can also declare and create in one step:
PairInt twothers = new PairInt(7, 11);

In Java, each source file contains one class and the file is named after that class. It is possible to define inner classes located inside another class definition and thus inside its file, but that introduces other complexities that we wish to avoid discussing at this point. Most importantly, an inner class has access to even the private members of the enclosing class. (Read more about inner classes in any of the Java books that we recommend at the end of this chapter.)

For each of the class methods, class data declarations, and the class itself, Java has syntax to limit the scope, or visibility, of those pieces. The examples above didn’t include those keywords—that is, they took the default values. Usually you’ll want to specify something. See Section 3.4.1.

Objects as References

So far we have not explained something important about object type variables. These variables can all be thought of as pointers or references to an object. When you declare a variable of an object type, what you are declaring is a variable that is capable of referring to an object of that type. When declared, it does not point at anything. It has a value of null and any attempt to use it will result in a null pointer exception (more on those later).

Before an object variable might be used, it must be made to refer to an instance of an object. This is done by assignment. You can assign an existing object, or you can use the new operator.

Any new class will have a constructor, that is, a method whose name is the name of the class. There can be many different constructors for the same class, each with unique types of parameters. For example, the String class has many different constructors, including one which constructs a new String from a different String and another that constructs a new String from an array of bytes.

String strbystr = new String(oldstr);
String strbyarr = new String(myByteArray);

Strings

One of the most commonly used classes is the String class. It comes already defined as part of Java and has some special syntax for initialization which makes it look familiar. Whereas other objects need a new keyword and a constructor call, a String object can be created and initialized with the intuitive double quotes, as in:

String xyz="this is the stringtext";

The compiler also makes a special allowance for Strings with respect to the plus sign (+). It can be used to concatenate two Strings into a third, new String.

String phrase = "That is"
String fullsent = phrase + " all.";

It is worth noting that Strings do not change—they are immutable. When you assign a String the value of one String plus another, there’s a lot of String object creation going on behind the scenes. If you need to do a lot of concatenation of Strings, say inside loops, then you should look into the use of the StringBuffer object. See Appendix A of Thinking in Java, 3rd ed., the section titled Overloading “+” and the StringBuffer, for a full discussion of the tradeoffs here.

There are a variety of methods for String—ones that will let you make substrings, search for substrings at the start or end or anywhere in the string, or check for equality of two strings.

Table 3.2 shows some of the most useful methods associated with String objects.

Table 3.2. Useful String methods

Return type

Method

Description

int

length()

Returns the length, i.e. number of characters, in the String.

boolean

equals(Object obj)

Returns true if the object is a String object and is equal to the String. (Aside: the argument takes a generic Object type rather than only a String object because it’s meant to override the equals() method in the class Object of which String is a descendant.) This is the way to compare two Strings to see if they are both holding the same sequence of characters. Using stringA == stringB will only tell you if stringA and stringB are referencing the same object (pointing to the same location in memory). What you typically want is stringA.equals(stringB).

boolean

equalsIgnoreCase(String str)

Similar to equals(), but this one only allows a String parameter, and it ignores the upper/lower case distinction between letters. For example:

  

String sample = "abcdefg"; String sample2 = "AbCdEfG"; sample.equalsIgnoreCase(sample2) returns true.

String

toLowerCase()

Returns a string with all characters converted to lowercase.

String

toUpperCase()

Returns a string with all characters converted to uppercase.

boolean

startsWith(String substr)

Returns true if the String starts with the given substring.

boolean

endsWith(String substr)

Returns true if the String ends with the given substring.

String

substring(int index)

Returns a string starting at position index to the end of the String.

String

substring(int first, int last)

Returns a string starting at position first and up to, but not including, character position last. If last is greater than the length of the String, or last is less than first, it throws an IndexOutOfBounds exception.

Other Classes: Reading Javadoc

Java comes with a huge collection of existing classes for you to use. The simplest ones are just wrappers for the primitive classes. There is an int primitive data type, but Java provides an Integer class, so that you can have an integer as an object. Similarly, there are classes for Long, Float, Boolean, and so on. Such classes aren’t nearly as interesting as the myriad other classes that come with Java. These others provide objects for doing I/O, networking, 2D and 3D graphics, graphical user interfaces (GUIs), and distributed computing. Java provides ready-to-use classes for strings, math functions, and for special kinds of data structures like trees and sets and hash tables. There are classes to help you with the manipulation of HTML, XML, and SQL, as well as classes for sound, music, and video. All these objects can be yours to use and enjoy if you just learn the magic of reading Javadoc—online documentation for Java classes. The documentation for all these classes is viewed with a Web browser. (In a following chapter we’ll describe how you can make Javadoc documents for the classes that you write, too.)

The online version of the API documentation can be found at http://java.sun.com/j2se/1.4.2/docs/api/ for Java 1.4.2. (Similarly, put 1.5.1 or whatever version you want at the appropriate place in the URL.) When displayed, it shows a three-frame page, as seen in Figure 3.1, except that we’ve overlaid the image with three labels: A, B, and C.

The three frames of a Javadoc page

Figure 3.1. The three frames of a Javadoc page

The upper left frame of the Javadoc display, the area labeled with A in our figure, lists all the packages that are part of Java 2 Standard Edition (J2SE). While there are many other packages of classes available for Java, these classes are the standard ones available without any other class libraries, with no additional downloads necessary. Other classes are documented in the same way—with Javadoc—but they are downloaded and displayed separately.

Frame B initially lists all the classes and interfaces available in all of the packages. When you select a package in A, B will display only those interfaces and classes that are part of the chosen package.

Frame C starts out with a list and description of all packages. Once you have selected a package in A, C will show the overview of that package, showing its classes and interfaces with descriptions.

But C is most often used to display the detailed description of a class. Choose a class or interface in B and you will see C filled with its description—some opening information followed by a list of the visible members of that class, followed by the possible constructors for that class and all the methods in that class (Figure 3.2). Each method is shown with its parameters and a one-sentence description. Clicking on the method name will open a fuller description (Figure 3.3).

Javadoc display of class information

Figure 3.2. Javadoc display of class information

Javadoc display of a single method

Figure 3.3. Javadoc display of a single method

Since you will likely be referencing the Javadoc pages regularly, you may want to download a copy to your hard drive. From the same page on the java.sun.com Web site where you can download the Java SDK you can also download the API documentation.

If you agree to the licensing terms, you will download a large ZIP file. Installing the documentation, then, is just a matter of unzipping the file—but it’s best if you put it in a sensible location. If you have installed your Java SDK into a location like /usr/local/java then cd into that directory and unzip the file that you downloaded. Assuming that you saved the downloaded file into /tmp, a good place to put temporary files, and assuming that you have installed your version of Java into /usr/local/java and that you have write permission in that directory (check the permissions with ls -ld .) then you can run these commands:

$ cd /usr/local/java
$ unzip -q /tmp/j2sdk-1_4_2-doc.zip

There may be quite a pause (tens of seconds) while it unzips everything. The unzip command will spew out a huge list of filenames as it unpacks them unless you use the -q option (“quiet”) on the command line (which we did, to avoid all that). The files are all unzipped into a directory named docs. So now you can point your browser to

file:///usr/local/java/docs/api/index.html

Now you have your own local copy for quick reference, regardless of how busy the network or Sun’s Web site gets. Be sure to bookmark this page; you’ll want to reference it often. It’s your best source of information about all the standard Java2 classes.

Statements

This section is not intended to be a formal presentation of Java syntactic elements.[3] Our purpose here is merely to show you the Java way to express common programming constructs. You will find that these are fundamentally similar to the analogous statements in C and C++. For much more detail on these subjects, see Chapter 3 of Thinking in Java by Bruce Eckel.

Like C, Java has a very small set of statements. Most constructs are actually expressions. Most operations are either assignments or method calls. Those few statements that are not expressions fall into two broad categories:

  • Conditional execution statements

  • Loop control statements

By the way, you may have already noticed one of the two kinds of comments that Java supports. They are like the C/C++ comments—a pair of slashes (//) marks a comment from there to the end of the line, and a block comment consists of everything from the opening /* to the closing */ sequence.

Conditional Execution

An experienced programmer probably only needs to see examples of if and other such statements to learn them. It’s only a matter of syntax. Java breaks no new ground here; it adds no new semantics to conditional execution constructs.

The if-else statement

The if can take a single statement without any braces, but we always use the braces as a matter of good style (Example 3.7).

Example 3.7. A compound Java if-else statement

if (x < 0) {
    y = z + progo;
} else if (x > 5) {
    y = z + hmron;
    mylon.grebzob();
} else {
    y = z + engrom;
    mylon.kuggle();
}

Tip

An important thing to remember about the Java if statement (and all other conditional tests, such as while, do-while, and for) is that, unlike C/C++, its expression needs to evaluate to a boolean. In C/C++, numeric expressions are valid, any nonzero value being considered true, but not so in Java.

The switch statement

For a multiway branch Java, like C/C++, has a switch statement, though the Java version of switch is a bit more restrictive. Example 3.8 shows the syntax.

In Java, the expression in the switch statement must evaluate to either an int or a char. Even short and long are not allowed.

As in C/C++, be sure to put the break statement at the end of each case, or else control will flow right into the next case. Sometimes this is the desired behavior—but if you ever do that deliberately, be sure to add a comment.

Example 3.8. A switch statement in Java

switch (rval*k+zval)
{
  case 0:
    mylon.reset();
    break;
  case 1:
  case 4:
    // matches either 1 or 4
    y = zval+engrom;
    mylon.kuggle(y);
    break;
  default:
    // all other values end up here
    System.out.println("Unexpected value.");
    break;
}

The default case is where control goes when no other case matches the expression. It is optional—you don’t need to have one among your switch cases. Its location is also arbitrary; it could come first, but by convention programmers put it last in the sequence of cases, as a visual “catch all.”

Tip

For whichever case is last (typically default), the ending break is redundant because control will continue outside the break—but we show it here in the example, and use it ourselves in our code. Why? Well, code gets edited—for bug fixes and for feature additions. It is especially important to use break in all the cases in switch statements that have no default case, but even in those that do, we keep the break to avoid forgetting it, should another case ever be added or this last one relocated. We recommend that you do the same.

Looping and Related Statements

The while statement

Like the while construct in other computer languages, the expression inside the parentheses is evaluated, and if true, the statement following it is executed. Then the expression is evaluated again, and if still true, the looped statement is again executed. This continues until the expression evaluates to false (Example 3.9).

Example 3.9. A Java while statement

while (greble != null)
{
  greble.glib();
  greble = treempl.morph();
}

Technically, the while statement consists of the expression and a single statement, but that single statement can be replaced by a set of statements enclosed in braces (you know, the characters { and }). We will always use braces, even if there is only one statement in our while loop. Experience has shown that it’s a safer practice that leads to code that is easier to maintain. Just treat it as if the braces were required syntax, and you’ll never forget to add them when you add a second statement to a loop.

The do-while loop

To put the terminating check at the bottom of the loop, use do-while as shown in Example 3.10. Notice the need for the terminating semicolon after the expression.

Example 3.10. A Java do-while statement

do {

  greble.morph();
  xrof = treempl.glib();

} while (xrof == null);

Die-hard Pascal programmers should note that Java has no repeat-until statement. Sorry. Of course the logic of an until(condition) is equivalent to do-while(!condition).

The for loop

The for loop in Java is very similar to C/C++. It consists of three parts (Example 3.11):

  • The initializing expression, done up front before the loop begins

  • The conditional expression for terminating the loop

  • The expression that gets executed at the end of each loop iteration, just prior to retesting the conditional

Example 3.11. A Java for loop

for (i = 0; i < 8; i++) {
  System.out.println(i);
}

Unlike C/C++, Java doesn’t have the comma operator for use within arbitrary expressions, but the comma is supported as special syntax in Java for loops. It makes it possible to have multiple initializers in the opening of the for loop and multiple expressions in the portion repeated at each iteration of the loop. The result is much the same—you can initialize and increment multiple variables or objects in your for loop.

More formally, the full syntax of the for loop can be described with following meta-language as shown in Example 3.12 (where the []* means “zero or more repetitions of”).

Example 3.12. Java for loop syntax

for ( before [, before]* ; exit_condition ; each_time [, each_time]* )
  statement

The biggest difference between C and Java for loops, however, is that Java allows you to declare one or more variables of a single type in the initializing expression of the for loop (Example 3.13). Such a variable’s scope is the for loop itself, so don’t declare a variable there if you want to reference it outside the loop. It is a very handy construct, however, for enumerators, iterators, and simple counters.

Example 3.13. A Java for loop with local index

for (int i = 0; i < 8; i++) {
  System.out.println(i);
}

As in the if and while statements, the braces are optional when only a single statement is involved, but good practice compels us always to use the braces. Additional code can easily be added without messing up the logic—should one forget, at that point, the need to add braces.

Speaking of the while loop: When do you use a for and when do you use a while loop? The big advantage of the for loop is its readability. It consolidates the loop control logic into a single place—within the parentheses. Anyone reading your code can see at once what variable(s) are being used to control how many times the loop executes and what needs to be done on each iteration (e.g., just increment i). If no initialization is needed before starting the loop, or if the increment happens indirectly as part of what goes on in the body of the loop, then you might as well use a while loop. But when the initialization and iteration parts can be clearly spelled out, use the for loop for the sake of the next programmer who might be reading your code.

The for loop with iterators

As of Java 5.0, there is additional syntax for a for loop. It is meant to provide a useful shorthand when looping over the members of an iterator.[4] So what’s an iterator? Well, it has to do with collections. Uh, oh, we’re surrounded by undefined terms. One step at a time, here. Java has a whole bunch (we won’t say “collection,” it’s a loaded term) of utility classes that come with it. We mentioned these classes in our discussion of Javadoc. While not part of the language syntax, some of these classes are so useful that you will see them throughout many, if not most, Java programs.

Collection is a generic term (in fact, it’s a Java interface) for several classes that allow you to group similar objects together. It covers such classes as Lists, LinkedLists, Hashtables, Sets, and the like. They are implementations of all those things that you (should have) learned in a Data Structures course in school. Typically you want to add (and sometimes remove) members from a collection, and you may also want to look something up in the collection. (If you’re new to collections, think “array,” as they are a simple and familiar type of collection.) Sometimes, though, you don’t want just one item from the collection, but you want to look at all of the objects in the collection, one at a time. The generic way to do that, the way that hides the specifics of what kind of collection you have (linked list, or array, or map) is called an iterator.[5]

The purpose of an iterator, then, is to step through a collection one item at a time. Example 3.14 shows a collection being built from the arguments on the command line. Then two iterators are used to step through the collection and print the objects in the collection to the command window. The first iterator uses the while loop, the second one uses a for loop, but they both do the same thing.

Example 3.14. Using iterators

import java.util.*;

public class
Iter8
{
  public static void
  main(String [] args)
  {
    // create a new (empty) ArrayList
    ArrayList al = new ArrayList();

    // fill the ArrayList with args
    for(int i = 0; i < args.length; i++) {
      al.add(args[i]);
    }

    // use the iterator in the while loop
    Iterator itr1 = al.iterator();

    while(itr1.hasNext()) {
      String onearg;
      onearg = (String) (itr1.next());
      System.out.println("arg=" + onearg);
    }

    // define and use the iterator in the for loop:
    for(Iterator itr2 = al.iterator(); itr2.hasNext(); ) {
      String onearg;
      onearg = (String) (itr2.next());
      System.out.println("arg=" + onearg);
    }

  } // main

} // Iter8

As of Java 5.0, there is another way to work your way through a collection, one that requires less type casting, but more importantly one that can enforce the type of objects at compile time.

Notice in Example 3.14 that the result of the next() is coerced into type String. That’s because everything coming from the iterator (via the next() method) comes to us as a generic object. That way an iterator can handle any type of object, but that also means that it is up to the application program to know what type should be coming back from the iterator. Any typecasting error won’t be found until runtime.

With the syntax added in 5.0, not only is there a shorthand in the for loop for looping with an iterator. There is also syntax to tell the compiler explicitly what type of objects you are putting into your collection or array so that the compiler can enforce that type.

Example 3.15 may help to make this clearer.

Example 3.15. Using a for loop iterator

import java.util.*;

public class
Foreign
{
  public static void
  main(String [] args)
  {
    List <String> loa = Arrays.asList(args);

    System.out.println("size=" + loa.size());

    for(String str : loa) {
      System.out.println("arg=" + str);
    }

  } // main
} // Foreign

Here we build a List from the arguments supplied on the command line. Notice the type name inside of angle brackets (less-than and greater-than signs). This is the new syntax that tells the compiler that we are putting Strings into the List. The compiler will enforce that and give a compile time error if we try to add any other type to the List.

Now we come to the for loop. Read it as “for str in loa” or “for String values of str iterating over loa.” We will get an iterator working out of sight that will iterate over the values of loa, our List. The values (the result of the next() method) will be put in the String variable str. So we can use str inside the body of the loop, with it taking on successive values from the collection.

Let’s describe the syntax, then, as

for ( SomeType variable : SomeCollectionVariable ) {
}

which will define variable to be of type SomeType and then iterate over the SomeCollectionVariable. Each iteration will execute the body of the loop, with the variable set to the next() value from the iterator. If the collection is empty, the body of the loop will not be executed.

This variation of the for loop works for arrays as well as for these new typed collections. The syntax for arrays is the same. Example 3.16 will echo the arguments on the command line, but without loading up a List like we did in our previous example.

Example 3.16. A for loop iterator for arrays

import java.util.*;

public class
Forn
{
  public static void
  main(String [] args)
  {
    for(String str : args) {
      System.out.println("arg="+str);
    }

  } // main
} // Forn

The break and continue statements

There are two statements that will change the course of execution of the while, do-while, and for loops from within the loop. A continue will cause execution to skip the rest of the body of the loop and go on to the next iteration. With a for loop, this means executing the iteration expression, and then executing the test-for-termination expression. With the while and do-while loops, this means just going to the test expression.

You can quit out of the loop entirely with the break statement. Execution continues on the next statement after the loop.

The return statement

There is one more statement that we need to cover. The return statement is optionally followed by an expression. Execution of the current method ends at once upon executing return, and the expression is used as the return value of the method. Obviously, the type of the expression must match the return type of the method. If the method is void, there should be no return expression.

Error Handling, Java Style

Errors in Java are handled through exceptions. In some circumstances, the Java runtime will throw an exception, for example, when you reference a null pointer. Methods you write may also throw exceptions. This is quite similar to C++. But Java exceptions are classes. They descend from Object, and you can write your own classes that extend an existing exception. By so doing, you can carry up to the handler any information you would like. But we’re getting ahead of ourselves here. Let’s first describe the basics of exceptions, how to catch them, how to pass them along, and so forth.

In other programming languages a lot of code can be spent checking return codes of function or subroutine calls. If A calls B and B calls C and C calls D, then at each step the return value of the called function should be checked to see if the call succeeded. If not, something should be done about the error—though that “something” is usually just returning the error code to the next level up. So function C checks D’s return value, and if in error, returns an error code for B to check. B in turn looks for an error returned from C and returns an error code to A. In a sense, the error checking in B and C is superfluous. Its only purpose is to pass the error from its origin in D to the function that has some logic to deal with the error—in our example that’s A.

Java provides the try/catch/throw mechanism for more sophisticated error handling. It avoids a lot of unnecessary checking and passing on of errors. The only parts of a Java program that need to deal with an error are those that know what to do with it.

The throw in Java is really just a nonlocal “goto”—it will branch the execution of your program to a location which can be quite far away from the method where the exception was thrown. But it does so in a very structured and well-defined manner.

In our simple example of A calling B calling C calling D, D implemented as a Java method can throw an exception when it runs into an error. Control will pass to the first enclosing block of code on the call stack that contains a catch for that kind of exception. So A can have code that will catch an exception, and B and C need not have any error handling code at all. Example 3.17 demonstrates the syntax.

Example 3.17. A simple try/catch block

try {
  for (i = 0; i < max; i++) {
    someobj.methodB(param1, i);
  }
} catch (Exception e) {
  // do the error handling here:
  System.out.println("Error encountered. Try again.");
}
// continues execution here after successful completion
// but also after the catch if an error occurs

In the example, if any of the calls to methodB() in the for loop go awry—that is, anywhere inside methodB() or whatever methods it may call an exception is thrown (and assuming those called methods don’t have their own try/catch blocks), then control is passed up to the catch clause in our example. The for loop is exited unfinished, and execution continues first with the catch clause and then with the statements after the catch.

How does an error get thrown in the first place? One simply creates an Exception object and then throws the exception (Example 3.18).

Example 3.18. Throwing an Exception, step by step

Exception ex = new Exception("Bad News");
throw ex;

Since there is little point in keeping the reference to the object for the local method—execution is about to leave the local method—there is no need to declare a local variable to hold the exception. Instead, we can create the exception and throw it all in one step (Example 3.19).

Example 3.19. Throwing an Exception, one step

throw new Exception("Bad News");

Exception is an object, and as such it can be extended. So we can create our own unique kinds of exceptions to differentiate all sorts of error conditions. Moreover, as objects, exceptions can contain any data that we might want to pass back to the calling methods to provide better diagnosis and recovery.

The try/catch block can catch different kinds of exceptions much like cases in a switch/case statement, though with different syntax (Example 3.20).

Notice that each catch has to declare the type of each exception and provide a local variable to hold a reference to that exception. Then method calls can be made on that exception or references to any of its publicly available data can be made.

Remember how we created an exception (new Exception("message"))? That message can be retrieved from the exception with the toString() method, as shown in that example. The method printStackTrace() is also available to print out the sequence of method calls that led up to the creation of the exception (Example 3.21).

The exception’s stack trace is read top to bottom showing the most recently called module first. Our example shows that the exception occurred (i.e., was constructed) on line 6 of the class named InnerMost, inside a method named doOtherStuff(). The doOtherStuff() method was called from inside the class MidModule—on line 7—in a method named doStuff(). In turn, doStuff() had been called by doSomething(), at line 11 inside AnotherClass, which itself had been called from line 14 in the ExceptExample class’ main() method.

Example 3.20. Catching different kinds of exceptions

try {
  for (i = 0; i < max; i++) {
    someobj.methodB(param1, i);
  } // next i

} catch (SpecialException sp) {
    System.out.println(sp.whatWentWrong());

} catch (AlternateException alt) {
    alt.attemptRepair(param1);

} catch (Exception e) {
    // do the error handling here:
    System.out.println(e.toString());
    e.printStackTrace();
}
// continues execution here after any catch

Example 3.21. Output from printStackTrace()

java.lang.Exception: Error in the fraberstam.
    at InnerMost.doOtherStuff(InnerMost.java:6)
    at MidModule.doStuff(MidModule.java:7)
    at AnotherClass.doSomething(AnotherClass.java:11)
    at ExceptExample.main(ExceptExample.java:14)

We want to mention one more piece of syntax for the try/catch block. Since execution may never get to all of the statements in a try block (the exception may make it jump out to a catch block), there is a need, sometimes, for some statements to be executed regardless of whether all the try code completed successfully. (One example might be the need to close an I/O connection.) For this we can add a finally clause after the last catch block. The code in the finally block will be executed (only once) after the try or after the catch—even if the path of execution is about to leave because of throwing an exception (Example 3.22).

Example 3.22. Use of a finally clause

try {
  for (i = 0; i < max; i++) {
    someobj.methodB(param1, i);
  } // next i

} catch (SpecialException sp) {
    System.out.println(sp.whatWentWrong());

} catch (AlternateException alt) {
    alt.attemptRepair(param1);
    throw alt;    // pass it on

} catch (Exception e) {
    // do the error handling here:
    System.out.println(e.toString());
    e.printStackTrace();

} finally {
    // Continue execution here after any catch
    // or after a try with no exceptions.
    // It will even execute after the AlternateException
    // before the throw takes execution away from here.
    gone = true;
    someobj = null;
}

print(), println(), printf()

We’ve already used println() in several examples, and assumed that you can figure out what it’s doing from the way we have used it. Without going whole-hog into an explanation of Java I/O and its various classes, we’d like to say a little more about the three various output methods on a PrintStream object.[6]

Two of the methods, print() and println(), are almost identical. They differ only in that the latter one appends a newline (hence the ln) at the end of its output, thereby also flushing the output. They expect a String as their only argument, so when you want to output more than one thing, you add the Strings together, as in:

System.out.println("The answer is "+val);

“But what if val is not a String?” we hear you asking. Don’t worry, the Java compiler is smart enough to know, that when you are adding with a String argument it must convert the other argument to a String, too. So for any Object, it will implicitly call its toString() method. For any primitive type (e.g., int or boolean), the compiler will convert it to a String, too.

The third of the three output methods, printf(), sounds very familiar to C/C++ programmers, but be warned:

  • It is only available in Java 5.0[7] and after.

  • It is similar but not identical to the C/C++ version.

Perhaps the most significant enhancement to printf() is its additional syntax for dealing with internationalization. It’s all well and good to translate your Strings to a foreign language, but in doing so you may need to change the word order and thus the order of the arguments to printf(). For example, the French tend to put the adjective after rather than before the noun (as we do in English). We say “the red balloon” and they say “le balloon rouge.” If your program had Strings for adjective and noun, then a printf() like this:

String format = "the %s %s
";
System.out.printf(format, adjective, noun);

wouldn’t work if you translate just the format String:

String format = "le %s %s
";
System.out.printf(format, noun, adjective);

You’d like to be able to do the translation without changing the code in your program.[8] With the Java version of printf(), there is syntax for specifying which argument corresponds to which format field in the format string. It uses a number followed by a dollar sign as part of the format field. This may be easier to explain by example; our French translation, switching the order in which the arguments are used, would be as follows:

String format = "le %2$s %1$s
";
System.out.printf(format, noun, adjective);

The format field %2$s says to use the second argument from the argument list—in this case, adjective—as the string that gets formatted here. Similarly, the format field %1$s says to use the first argument. In effect, the arguments get reversed without having to change the call to println(), only by translating the format String. Since such translations are often done in external files, rather than by assignment statements like we did for our example, it means that such external files can be translated without modifying the source to move arguments around.

This kind of argument specification can also be used to repeat an argument multiple times in a format string. This can be useful in formatting Date objects, where you use the same argument for each of the different pieces that make up a date—day, month, and so on. Each has its own format, but they can be combined by repeating the same argument for each piece. One format field formats the month, the next format field formats the day, and so on. Again an example may make it easier to see:

import java.util.Date;
Date today = new Date();
System.out.printf("%1$tm / %1$td / %1$ty
", today);

The previous statement uses the single argument, today, and formats it in three different ways, first giving the month, then the day of the month, then the year. The t format indicates a date/time format. There are several suffixes for it that specify parts of a date, a few of which are used in the example.[9]

Note

Don’t forget the trailing at the end of the format string, if you want the output to be a line by itself.

The details for all the different format fields can be found in the Javadoc for the java.util.Formatter class, a class that is used by printf() to do its formatting, but one that you can also use by itself (C programmers: think “sprintf”).

In order to implement printf() for Java, the language also had to be extended to allow for method calls with a varying number of arguments. So as of Java 5.0, a method’s argument list can be declared like this:

methodName(Type ... arglist)

This results in a method declaration which takes as its argument an array named arglist of values of type Type. That is, it is much the same as if you declared methodName(Type [] arglist) except that now the compiler will let you call the method with a varying number of arguments and it will load up the arguments into the array before calling the method. One other implication of this is that if you have a declaration like this:

varOut(String ... slist)

then you can’t, in the same class, also have one like this:

varOut(String [] alist)

because the former is just a compiler alias for the latter.

Tip

We recommend that you avoid methods with variable argument list length. You lose the compile-time checking on the number of arguments that you supply (since it can vary). Often the type of the arguments in the list will be Object, the most general type, to allow anything to be passed in. This, too, circumvents type checking of arguments, and can lead to runtime class-cast exceptions and other problems. Methods with variable argument list length are often a lazy approach, but were necessary to make printf() work, and for that we are grateful.

Using (and Making) Java APIs

With every class you write, you define a name—the name of the class. But what if someone else has already used that name? Java programming should encourage reuse of existing code, so how do you keep straight which names are available?

This is a namespace issue—who can use which names. A classic way to solve a namespace issue is to divide the namespace up into domains. On the Internet, host names are sectioned off into domains, so that I can have a host named Pluto or www and so can lots of others—because each host is qualified by its domain (e.g., myco.com). Thus www.myco.com isn’t confused with www.otherco.com or www.hisgroup.org. Each host is named www, but each is unique because of the qualifying domain.

Java solves the problem in much the same way, but with the names in the reverse order. Think of the “host” as the class name; the “domain” name, used to sort out identical host names, is, in Java parlance, the package name. When you see a name like com.myco.finapp.Account, that can be a Java package com.myco.finapp qualifying a class named Account.

Beyond just keeping the namespace clean, Java packages serve another important function. They let you group together similar classes and interfaces to control access to them. Classes within the same package can access each others’ members and methods even when they are not declared public, provided they are not declared to be private. This level of intimacy, sometimes called package protection, means that you should group classes together that are related, but avoid grouping too many classes together. It’s tempting just to put all your classes for a project into the same package, for example, com.myco.ourproject, but you will provide better safety and perhaps promote better reuse by grouping them into several smaller packages, for example, com.myco.util, com.myco.financial, and com.myco.gui.

The package Statement

So how do you make a Java class part of a package? It’s easy—you just put, as the first (noncomment) line of the file, the package statement, naming the package to which you want this class to belong. So if you want your Account class to be part of the com.myco.financial package, your Java code would look as shown in Example 3.23.

Example 3.23. Use of a package statement

package com.myco.financial;

public class
Account
{
  // ...
}

Making a class part of a package is easy. What’s tricky is putting the class file in the right location so that Java can find it.

Think of the current directory as the root of your package tree. Each part of a package name represents a directory from that point on down. So if you have a package named com.myco.financial then you’ll need a directory named com and within that a directory named myco and within that a directory named financial. Inside that financial directory you can put your Account.class file.

When Java runs a class file, it will look in all the directories named in the CLASSPATH environment variable. Check its current value:

$ echo $CLASSPATH

$

If it’s empty, as in this example, then the only place where it will look for your classes will be the current directory. That’s a handy default, because it is just what you want when your class file has no package statement in it. With no package statement, your class becomes part of the unnamed package. That’s fine for simple sample programs, but for serious application development you’ll want to use packages.

Let’s assume that you’re in your home directory, /home/joeuser, and beneath that you have a com directory and beneath that a myco directory with two subdirectories financial and util. Then with your classes in those lower level directories, you can run your Java program from the home directory. If you want to run it from any arbitrary directory (e.g., /tmp or /home/joeuser/alt) then you need to set CLASSPATH so it can find this package tree. Try:

$ export CLASSPATH="/home/joeuser"
$

Now Java knows where to look to find classes of the com.myco.financial and com.myco.util packages.

The import Statement

Once we have put our classes into packages, we have to use that package’s name when we refer to those classes—unless we use the import statement.

Continuing our example, if we want to declare a reference to an Account object, but Account is now part of com.myco.financial, then we could refer to it with its full name, as in:

com.myco.financial.Account =
    new com.myco.financial.Account(user, number);

which admittedly is a lot more cumbersome than just:

Account = new Account(user, number);

To avoid the unnecessarily long names, Java has import statements. They are put at the beginning of the class file, outside the class definition, just after any package statement. In an import statement, you can name a class with its full name, to avoid having to use the full name all the time. So our example becomes:

import com.myco.financial.Account;
// ...
Account = new Account(user, number);

If you have several classes from that package that you want to reference, you can name them all with a “*”, and you can have multiple different import statements, as in:

import java.util.*;
import com.myco.financial.*;
// ...
Account = new Account(user, number);

Here are a few things to remember about import statements. First, they don’t bring in any new code into the class. While their syntax and placement is reminiscent of the C/C++ include preprocessor directive, their function is not the same. An import statement does not include any new code; it only aids name resolution. Secondly, the “* can only be used at the end of the package name; it is not a true wildcard in the regular expression sense of the word. Thirdly, every class has what is in effect an implicit import java.lang.* so that you don’t need to put one there. References to String or System or other core language classes can be made without the need for either the import statement or the fully qualified name (except as described in the next paragraph).

If you need to use two different classes that have the same name but come from different packages, you will still need to refer to them by their full names; import can’t help you here. As an example of this, consider the two classes java.util.Date and java.sql.Date (though with any luck you won’t need to refer to both of them within the same class).

Encapsulation, Inheritance, and Polymorphism

The classic troika of OOP buzzwords is “encapsulation, inheritance, and polymorphism.” How does Java do each of these things?

Encapsulation

Encapsulation is the grouping of data and algorithms together into units, and it’s also about hiding implementation details that are not important to the users of the unit. The basic unit of encapsulation is the class. All Java code exists in classes. A class is declared with the class keyword (Example 3.24).

Example 3.24. A sample Java class declaration that doesn’t actually do anything useful

public class
Sample
{
  private int id;

  public void method()
  {
    System.out.println(id);
  }
}

Inheritance

Inheritance is how a class places itself in a hierarchy of existing classes. In Java, each class inherits from exactly one existing class. A class names the class from which it inherits with the extends keyword. We said a Java class inherits from exactly one class, and yet our Example 3.24 doesn’t contain the extends keyword. What gives?

If a class doesn’t explicitly extend an existing class, it implicitly extends the “root” Java class, Object. The Object class has some interesting features, and the fact that all Java classes directly or indirectly extend Object has interesting consequences that we will explore later.

Persons coming to Java from another object-oriented language whose name shall remain C++ might wonder about multiple inheritance. Java has the concept of interfaces. An interface is like a class, except that it may not contain data[10] and may contain only method definitions, without any implementation (Example 3.25). An interface may not be instantiated (created with the new operator),[11] so how do you make use of interfaces? Well, a class extends exactly one existing base class, but it may implement any number of interfaces by using the implements keyword (Example 3.26).

Example 3.25. An interface

public interface
Identifiable
{
  public int getID();
}

As you can see, a class that implements an interface must provide an implementation of all the methods defined in the interface. We said that an interface cannot be instantiated, but you can declare a variable of type Identifiable and assign an instance of the Sample class to it. In fact, you could assign an instance of any class that implements the Identifiable interface to it.

Example 3.26. A class that implements an interface

class
Sample
  implements Identifiable
{
  private int id;

  public void method()
  {
    System.out.println(id);
  }

  public int getID()
  {
    return id;
  }
}

Interfaces may also have an extends keyword. In other words, an interface may inherit from an existing interface. This may be useful if you know you will want to use methods from both the extended and the base interface without having to cast the object reference. Otherwise extending an interface is unnecessary since a given class may implement as many interfaces as desired.

Inheritance and Encapsulation

Encapsulation and inheritance are related to one another and are controlled by access modifiers on classes, data members, and methods. Let’s spend a little time talking about these modifiers and what they mean.

The access modifier keywords are public, private, and protected. When a data member or method is private, it can only be accessed or called from within this specific class. Neither classes that extend this class, nor classes outside this class may access such a data member or call such a method. However, one instance of a class can access private members of another instance of the same class. We don’t encourage such use.

When a data member or method is marked protected, however, the only classes that can access such a data member or method are either 1) classes that extend this class and their descendants, or 2) other classes in this package (even if they don’t extend this class). Classes in other packages (unless they extend this class) can not get at such members.

A public data member or method may be accessed by any code in any class.

What if you do not put an access specifier on? Then the item (data member, method, or class) has package visibility. Such an item is accessible to any other class within the package, but no further. Not even derived classes, unless they are in the same package, are allowed to see it.[12]

In terms of how restrictive the access is, you can think of the terms in order of decreasing strictness as:

  • private

  • (package)[13]

  • protected

  • public

Tip

Beginner Java programmers often declare everything as public, so that they can ignore such issues. But then they get the OO religion, and having experienced reliability issues (others messing with their variables) they go to the other extreme and declare private as much as possible. The problem here is that they often don’t know how others will want to reuse their code. Restricting everything to private makes reuse more narrow. We prefer using private for data members but protected for those internal helper methods that you might otherwise make private; this hides your implementation from most other classes while allowing someone to override your methods, effectively providing a way for them to override your implementation. Allow those who would build on your work the ability to do so without having to reimplement.

Here is a simple example of each type of declaration:

private String hidn;
String pkgstr;
protected String protstr;
public String wideOpen;

The static statement

Another keyword qualifier on declarations that we need to describe is the static keyword. When a variable is declared static then there is only one instance of that variable shared among all instances of the class. Since the variable exists apart from a particular instance of the class, one refers to it with the class name followed by a dot followed by the variable name, as in System.out.

Similarly, methods can be declared static as well. This also means that you don’t need an instance of the class to call them, just the class name, as in System.getProperties().

Now with Java 5.0, you don’t even need the class name, provided that you have a static import statement at the top of your class, for example:

import static java.lang.System.*;

The final statement

Another way that static is often seen is in conjunction with the final keyword. When a variable is declared final then a value can be assigned to it once, but never changed. This can make for good constants.

Since public will make the variable visible to all other classes, static will make it a class variable (available without an instance of that class), and final will keep it from being altered (even though it is publicly available), then combining all of those gives us a good declaration for a constant, for example:

public static void long lightSpeed = 186000;    // mps

New to Java 5.0 is the explicit creation of enumerated types. Prior to 5.0, programmers would often use static final constants even when the particular value was unimportant, as a way to provide compile-time checking of the use of the constant values. Here is an example of a declaration of a set of enumerated values:

enum WallMods { DOOR, WINDOW, VENT, GLASSBLOCK };

Tip

A common technique used with public static final constants is to put them in an interface definition. (This is the exception to the rule that interfaces define method signatures but contain no data.) When a class wants to use one or more of those constants, it is declared to implement that interface:

public MyClass
  extends BigClass
  implements Comparable, LotsaConstants
{
...
}

In defining MyClass we have declared that it implements LotsaConstants (not a name that we recommend you using). That makes all the constants that we have defined inside the LotsaConstants interface available to the MyClass class. Since classes can implement many different interfaces, this doesn’t interfere with the use of other “real” interfaces, such as Comparable.

Warning

The keyword enum is new to Java 5.0, so older programs that may have used enum as a variable name and will now cause an error when recompiled for Java 5.0.

The enum will look very familiar to C/C++ programmers, but there are some important differences. In C/C++ the values of the enum elements are, in reality, integer values. Not so in Java. Here they are their own type, but can be converted to a String via the toString() method, with a value that matches the name, for easy reading and debugging.

Enumerated values can be used in == comparisons since they will be defined only once (like other static final constants) and it would only be references that are passed around. They would be referenced by the name of the enumeration followed by dot followed by the particular value (e.g., WallMods.WINDOW) and used as an object. (We have used uppercase for the names not out of any syntax requirement, but only to follow the typical naming convention for constants.)

Polymorphism

Polymorphism (from the Greek poly meaning “many” and morph meaning “shape”) refers to the language’s ability to deal with objects of many different “shapes,” that is, classes, as if they were all the same. We have already seen that Java does this via the extends and implements keywords. You can define an interface and then define two classes that both implement this interface.

Remember our Sample class (Example 3.26). We’ll now define another class, Employee, which also implements the Identifiable interface (Example 3.27).

Example 3.27. The Employee class

class
Employee
  extends Person
  implements Identifiable
{
  private int empl_id;

  public int getID()
  {
    return empl_id;
  }
}

Notice that the same method, getID(), is implemented in the Employee class, but that the field from which it gets the ID value is a different field. That’s implementation-specific—the interface defines only the methods that can be called but not their internal implementation. The Employee class not only implements Identifiable, but it also extends the Person class, so we better show you what our example Person class looks like (Example 3.28).

To make a really useful Person class would take a lot more code than we need for our example. The important part for our example is only that it is quite different from the Sample class we saw earlier.

Example 3.29 demonstrates the use of polymorphism. We only show some small relevant snippets of code; there would be a lot more code for this to become an entire, complete example. Don’t be distracted by the constructors; we made up some new ones just for this example, that aren’t in the class definitions above. Can you see where the polymorphism is at work?

Example 3.28. The Person class

class
Person
{
  String name;
  Address addr;

  public
  Person(String name, Address addr)
  {
    this.name = name;
    this.addr = addr;

  } // constructor

  // ... lots more code is here

  public String getName()
  {
    return name;
  }
}

Example 3.29. An example use of polymorphism

//...
Sample labwork = new Sample(petridish);
Employee tech = new Employee(newguy, 27);
Identifiable stuff;

//...
if (mode) {
    stuff = labwork;
} else {
    stuff = tech;
}
id = stuff.getID();

The key point here is when the call is made to getID(). The compiler can’t know at compile time which object will be referred to by stuff, so it doesn’t know whose getID() method will be called. But don’t worry—it works this all out at runtime. That’s polymorphism—Java can deal with these different objects while you, the programmer, can describe them in a generalized way.

One other related keyword should be mentioned here, abstract. When one declares a class as an abstract class, then the class itself is an incomplete definition. With an abstract class you define all the data of the class but need only write method declarations, not necessarily all the code for the methods. This makes abstract classes similar to interfaces, but in an abstract class, some of the methods can be fully written out.

If you’d like to know more about polymorphism, “late binding,” and more of this aspect of Java, read Chapter 7 of Eckel’s Thinking in Java. There is an extensive example there with much more detail than we can cover here.

O, Templates! Where Art Thou?

Programmers familiar with C++ may be wondering how in the world an OOP language without templates can be useful.

Note

Actually, something very much like templates is available in Java 5.0.[14] A new feature, which Sun calls generics, looks an awful lot like C++ templates (including similar syntax). It provides compile-time type checking and implicit casting when retrieving objects from a generic container.

Speaking as programmers who worked with C++ before it had templates, we can sympathize. Java’s previous lack of true templates does impose some limits on generic programming, but not as much as one might think. Remember that unlike C++, all Java classes inherit from exactly one base class, and that if no base class is specified, they extend the Object class. This means that every single Java class either directly or indirectly extends Object, and thus all Java classes are instances of Object. So if you need, for example, to implement a container, you can guarantee that it can contain any Java class by implementing a container for the Object type. Java also has runtime type identification features that are more than a match for anything C++ has, plus it has type-safe downcasting[15] so that in the worst case scenario, your program has a nice, clean type exception. You simply do not get the kind of “mystery bugs” that you can get in C++ when you miscast an object.[16]

Thanks to interfaces and a true single object hierarchy, many of the uses of C++ templates go away. We doubt very much that you will miss them. In many cases, such as STL algorithms and other functional programming implementations, you can use interfaces to produce similar results.

Critics of the Java language have a point when they complain that all the type casting of class references in order to expose desired interfaces tends to produce code that violates object-oriented principles. The fact that a class or interface implements all these other named interfaces is hard-coded all over the place in an application’s code. Such critics say this is a bad thing, because it violates encapsulation and implementation hiding. These critics have a point. If you find yourself frequently downcasting object references, consider using the Java 5.0 generics, or try to find another way to code what you want to do. There may be a better way. In defense of the original Java approach (before generics), all casts are runtime type safe. An exception is thrown if a class reference is improperly cast. In C++, if you miscast a pointer, it assumes you meant it. Java certainly can be awkward, but errors will get caught. Sometimes that is more important.

Virtually Final

One difficulty anyone writing about Java faces is whether or not to assume your readers are familiar with C++. In this chapter, we have tried to help those with C++ experience without requiring such knowledge. But it is in the inevitable comparisons between those languages that many subtle Java features are best discussed. We promised you that we would talk about the relative merits of virtual (a C++ concept) and final (a Java concept). To do that, we have to assume some knowledge of C++. So, let’s reverse the pattern and talk about the straight Java facts so we can let the non-C++ folks move on while we go a little deeper with you C++’ers.

In Java, a method or a class may be declared final. A method that is declared final may not be overridden in classes that extend the class containing the final implementation. A class that is declared final may not be extended at all.

Now, the comparisons to C++ require us to talk about a language feature that does not exist at all in Java. In C++, unless a method is declared virtual, when a class is used by reference to a base class (for example, when using Employee as a Person), the base class version of the method is called. If the method is declared virtual, the version of the method called is the version for the type of Person referenced (in this case, Employee). In Java, all methods are virtual. There is no such keyword in Java.

A Useful Simple Application

We will use the sample application shown in Example 3.30 in other sections of this book to illustrate the use of Java tools. This example is so simple (a single class) that it doesn’t demonstrate the object-oriented aspect of development, but it does make use of some APIs that take advantage of it. We will not walk you through this application right here, but present it as a listing of a complete Java class. Not all of the APIs used in this example will be explained, so you may want to refer to the Javadoc pages for explanations of object types or method calls that don’t seem obvious.

Review

We’ve taken a very quick look at the syntax of Java statements, classes, and interfaces. Much of the syntax is very reminiscent of C, though Java’s object-oriented features differ in significant ways from C++. We looked at how to put Java classes into packages, and at the implications of this for locating the .class files.

We also showed what the HTML-based Javadoc documentation looks like. These HTML pages will likely be a handy reference for you as you design and write your Java code.

Example 3.30. Single class example: FetchURL

import java.net.*;
import java.io.*;

public class FetchURL {
  private URL requestedURL;

  public FetchURL(String urlName)
  {
    try {
       requestedURL = new URL(urlName);
    } catch (Exception e) {
       e.printStackTrace();
    }
  }

  public String toString()
  {
    String rc = "";
    String line;
    BufferedReader rdr;

    try {
        rdr = new BufferedReader(
          new InputStreamReader(
          requestedURL.openConnection().getInputStream()
          )
        );

        while ((line = rdr.readLine()) != null)
        {
          rc = rc + line + "
";
        }
    } catch (Exception e) {
        e.printStackTrace();
        rc = null;
    }

    return rc;
  }
  public static void main(String[] args)
  {
    int i;
    FetchURL f;

    for (i = 0; i < args.length; i++)
    {
      System.out.println(args[i] + ":");
      System.out.println(new FetchURL(args[i]));
    }
  }
}

What You Still Don’t Know

We have deliberately avoided file I/O. For Java, it is a multilayered and complex topic—and with version 1.4 of Java, there is a whole new set of additional classes (java.nio.*) to consider. We refer you instead to Chapter 11 of Eckel’s Thinking in Java.

There are also a few Java keywords that we have not yet discussed, notably synchronize.

Even if you know all the Java syntax, it may still take a while to get familiar with the way that syntax is typically put to use. Experience and reading other people’s Java code will be your best teachers—but don’t assume that a particular approach is good just because someone else uses it; much new code has been written in the last several years as people have learned Java. Be sure it’s a style worth imitating, and if you find a better way to do it, use it.

Resources

  • Bruce Eckel, Thinking in Java.

  • Cay S. Horstmann and Gary Cornell, Core Java 2: Volume 1 Fundamentals, especially Chapter 3.

  • John Lewis and William Loftus, Java Software Solutions.

  • The Sun Microsystems Java Tutorial.[17]

Exercises

1.

Write a simple class with a main() method that prints out the arguments supplied on the command line used to invoke it. First use a for loop to do this, then a while loop, then a do-while. What differences do you notice? Which do you find most amenable for this task?

2.

Modify the previous class to quit echoing its arguments should it encounter an argument of length 5. (You can tell the length of a String object with the length() method, e.g., mystr.length().) Did you use break, or continue, or some other mechanism?



[1] The existence of these nonobject data types is another thing that brings up criticism of the Java language. Since Java does not have C++’s operator overloading features, you cannot use objects in standard algebraic expressions. I’m not sure if the inclusion of scalar classes was motivated by speed, or by the lack of operator overloading. Whatever the reason, like any other design compromise, it has both advantages and disadvantages, as we shall see throughout the book.

[2] In fact, C’s only rule is that a short int will not be longer than an int and a long will not be shorter than an int. It is both ANSI and K&R compatible for all integer types in a C compiler to be the same size!

[3] For those so inclined, Sun has a BNF language grammar (http://java.sun.com/docs/books/jls/second_edition/html/syntax.doc.html) on their Web site, and the Lewis and Loftus book, Appendix L, has a good set of syntax diagrams.

[4] This feature is related to the topic of templates and generics. See Section 3.5.

[5] The earliest versions of Java used an object called an Enumeration. It does much the same thing as an iterator, but with somewhat clumsier method names. Iterators also allow for a remove() method, something that Enumeration doesn’t support. The Enumeration class is still around, but less frequently used. It is only available from certain older utility classes.

[6] The mention of the PrintStream object was meant to be a hint, to tell you that you can find out more about this sort of thing on the Javadoc pages for the PrintStream object.

[7] Remember, you’ll need the -source 5.0 option on the command line.

[8] Java has good support for internationalization, another topic for which we don’t have the time. The ability to translate the strings without otherwise modifying the program is a crucial part to internationalization, and the printf() in Java 5.0 is certainly a help in this regard. In a similar vein, the Eclipse IDE, covered in Chapter 10, includes a feature to take all string constants and convert them to external properties at a stroke, making internationalization much easier to do.

[9] There are many more, familiar to C/C++ UNIX/Linux/POSIX programmers who have used the strftime() library call.

[10] Actually, an interface may contain final static data, but since we haven’t introduced these concepts yet, just pretend interfaces cannot contain data for now and we’ll put the lie to it later.

[11] Although you can do something that looks suspiciously like it with anonymous inner classes—but since we haven’t introduced these concepts yet, just pretend that you cannot instantiate an interface; you will see such use later.

[12] If you are a C++ programmer, the following description may mean something to you (if not, skip this): All classes within a package are essentially “friends.”

[13] Remember there is no keyword for package level protection, rather it is the absence of a keyword that denotes this level of protection. We had to write something in that space on the page so you’d know what we’re talking about.

[14] Java 5.0 will only be out by the time this book is completed.

[15] Don’t worry if this is all gibberish to you right now. We will revisit these topics in detail when we come upon them in the course of our sample project.

[16] Actually, we’re being a bit optimistic here. While Java programs are not subject to many mystery bugs, the Java Virtual Machines that run Java code are written in traditional languages, and there have been VMs with bugs. Time and again we see that there is no “silver bullet.” But in our experience, Java comes close. So very close.

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

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