Section 3.2. Fundamental Language Elements


3.2. 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]

[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.

3.2.1. Scalar Types

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

3.2.1.1 Integer Types

Java defines four integer typesbyte, 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 bitseverywherehelps 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 succeededC was an easy language to implement, and it spread widely.

[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!

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 

3.2.1.2 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.

3.2.1.3 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.

3.2.1.4 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.

3.2.1.5 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.

3.2.2. 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 keywordsthat is, they took the default values. Usually you'll want to specify something. See Section 3.4.1.

3.2.2.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); 

3.2.2.2 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 changethey 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 Stringones 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.


3.2.2.3 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 Javadoconline 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.

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 waywith Javadocbut 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 descriptionsome 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).

Figure 3.2. Javadoc display of class information


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 filebut 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.

3.2.3. 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.

[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.

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++ commentsa 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.

3.2.3.1 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 behaviorbut 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 optionalyou 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 breakbut we show it here in the example, and use it ourselves in our code. Why? Well, code gets editedfor 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.


3.2.3.2 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 sameyou 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 logicshould 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 placewithin 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.

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

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]

[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.

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.

3.2.3.3 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.

3.2.4. 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 errorthough 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 errorin 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 awrythat 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 methodexecution is about to leave the local methodthere 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 MidModuleon line 7in 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 catcheven 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; } 

3.2.5. 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]

[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.

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.

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

  • 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\n"; System.out.printf(format, adjective, noun); 

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

 String format = "le %s %s\n"; 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:

[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.

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

The format field %2$s says to use the second argument from the argument listin this case, adjectiveas 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 dateday, 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\n", 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]

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

Note

Don't forget the trailing \n 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.




    Java Application Development with Linux
    Java Application Development on Linux
    ISBN: 013143697X
    EAN: 2147483647
    Year: 2004
    Pages: 292

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net