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.
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.
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]
Java has a number of built-in scalar (in this context, nonobject) types. We discuss these below.
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.
Java provides two different precisions of floating point numbers. They are:
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.
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 byte
s and char
s 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.
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
Explanation | |
---|---|
unary operators: negate, increment, decrement, logical-not, bitwise-not | |
| 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 | |
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 | |
Assignment; those with an operator, as in | |
[*] In Java there are two ways to do a boolean AND operation: using [**] XOR is exclusive or, where the result of “a XOR b” is |
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.
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.
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.
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);
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 String
s with respect to the plus sign (+
). It can be used to concatenate two String
s into a third, new String
.
String phrase = "That is" String fullsent = phrase + " all.";
It is worth noting that String
s 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 String
s, 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
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 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).
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.
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.
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
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(); }
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.
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.”
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 case
s 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.
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.
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 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
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.
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 String
s 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.
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.
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.
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).
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; }
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 String
s 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 String
s 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 String
s 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]
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.
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.
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
.
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.
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.
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).
The classic troika of OOP buzzwords is “encapsulation, inheritance, and polymorphism.” How does Java do each of these things?
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).
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).
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.
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
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;
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.*;
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 };
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
.
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 (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.
Programmers familiar with C++ may be wondering how in the world an OOP language without templates can be useful.
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.
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.
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.
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])); } } }
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.
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]
[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.
18.217.248.255