Introduction to Java
Java was introduced by Sun Microsystems in May 1995 and quickly attracted widespread attention from both pundits and programmers. Its fairly rapid adoption has made it central to the strategies of most major software vendors , especially Lotus and IBM.
There's no mistaking it ”Lotus likes Java. This is clearly evidenced by the extensive Java support in Domino 6, which includes these new Java features:
- Improved Java development tools in Domino Designer
- Expanded Java access to the Notes Object Interface
- Java server add-ins using the runjava task
- Remote access to Domino servers and databases from Web browsers and standalone applications using Java and CORBA
As you probably know, Java is a huge subject and has warranted entire books devoted to it. This chapter is not meant to serve as a comprehensive treatise on Java; instead, it is an overview of the vast Java terrain, geared toward providing a foundation for using the Java language in Domino. For a more in-depth treatment, see the book Java 2 Platform Unleashed , by Jamie Jaworski.
Understanding Java
Java is primarily known as a general-purpose programming language, much like C++. As a language, it borrows from C and C++, so much so that you might mistake Java code for C code until you look more closely. The similarity to these popular programming languages is by design; Sun was hoping that C and C++ programmers would find the switch to Java a pleasant and painless one.
Were Java just a programming language, though, there'd be fewer programmers making the switch. More important is Java's role as a full-fledged computer platform. Java frees programmers from worrying about which computer or operating system their programs can run on. This is because Java source code is compiled into an Esperanto-like machine language called bytecode, which is independent of any computer or operating system.
Java bytecode is executed by special software known as a Java Virtual Machine (JVM). Each JVM insulates Java programs from the idiosyncrasies of individual operating systems, so the same Java bytecode can execute on Windows PCs, Macintoshes, and UNIX workstations. There's a JVM in every Domino server, Notes client, and Web browser. You can also start up your own JVM from the command line to run standalone Java programs.
Java's capability to run the same program on virtually any platform makes it especially attractive when an application must run in a network composed of many computer types, such as the Internet.
Like most programming languages and platforms, Java includes an extensive library of core classes for making graphical user interfaces and performing common tasks such as file I/O, network communication, and multithreading. And because the Java API is platform independent, you can use it to make dialog boxes and menus without regard to the operating system or hardware platform.
As Lotus and IBM increasingly turn to Java, learning how to build Domino applications with Java will pay handsome and unexpected dividends , both within and beyond the realm of Domino. The language's increasing popularity also means that your skills will remain marketable.
Writing Java Source Code
Like most programming languages, Java source code consists of a sequence of tokens separated by whitespace characters. Tokens are basic elements of the language, such as keywords, identifiers, literals, and operators. Whitespace characters include spaces, tabs, carriage returns, line feeds, and form feeds. Java compilers consider any sequence of whitespace characters identical to one single space, so you can add all the spaces, tabs, and blank lines you want without changing the logic of your program.
Comments
Comments, which are helpful descriptions to explain your code, are also considered whitespace and are treated as a single space character. There are three forms of comments to choose from:
- // comment text ” Everything from the // to the next carriage return or line feed is ignored.
- /* comment text */ ” Everything between the /* and the */ is ignored, even if it spans more than one line.
- /** comment text */ ” This indicates a documentation comment recognized by the javadoc tool.
The first two forms are the same as in C and C++. Use the first if your comment is confined to a single line of code. Use the second if you want your comment to span multiple lines, or if you want to temporarily comment out portions of your code.
NOTE
Be careful when putting comments inside other comments, though. Multiple-line comments don't nest.
The last form, the doc comment, is new to the Java language. Doc comments are used to document the purpose and public interface of your source code. Put these comments just before each code element you want to describe. You can then use a tool named javadoc to scan your source, and it automatically creates HTML documentation that can be viewed within a Web browser. You can use special tags within a doc comment to indicate the following kinds of information:
- @author ” Classes only. Indicates the author of a class.
- @version ” Classes only. Indicates the version number of the class.
- @param ” Methods only. Describes each parameter of a method.
- @return ” Methods only. Describes what the method returns.
- @exception ” Methods only. Describes each exception thrown by the method.
- @see ” Used to list other related classes, methods, or fields.
- @since ” Indicates the version in which this class, method, or field appeared.
- @deprecated ” Marks this class, method, or field as unsupported in new versions.
Unicode
Another Java innovation is built-in support for the international 16-bit Unicode character set. Unicode contains symbols and alphabets from most of the world's written languages. Nearly all other programming languages use the ubiquitous 7-bit ASCII character set, which contains only symbols and letters used in English and other Western European languages. Java lets you use the full Unicode character set for all identifiers, strings, and comments, so you can use Cyrillic and Telugu variable names as easily as English ones.
Because the majority of text editors still use ASCII, Java converts automatically between ASCII text and Unicode text. This means that you can safely ignore Java's Unicode support if you do not need it. If you're using an ASCII editor and want to use a Unicode character, use the Unicode escape sequence u , followed by the four-digit hexadecimal code for the character. For example, use u00F6 to represent .
NOTE
Unlike other escape sequences, Unicode escape sequences can be used anywhere in your source code.
Identifiers
Identifiers are the names you choose for programming elements such as variables . You can choose any sequence of letters, digits, and underscores, provided that you start with a letter or an underscore and that there is no practical limit to the length of an identifier. For example, loop_count , loopcount , and loop2count are valid identifiers, while 2loopcount and loop count are invalid. Java is a case-sensitive language, so galt and Galt refer to different variables, an important distinction if you're used to working with non “case-sensitive languages such as LotusScript or the Formula language. You're also restricted from using the following Java keywords and literals as identifiers:
abstract | default | if | private | throw |
boolean | do | implements | protected | throws |
break | double | import | public | transient |
byte | else | instanceof | return | try |
case | extends | int | short | void |
catch | final | interface | static | volatile |
char | finally | long | super | while |
class | float | native | switch | true |
const | for | new | synchronized | false |
continue | goto | package | this | null |
Working with Data Types and Variables
Variables are named storage locations in memory. Every variable has a data type, which describes the kind of information the variable can hold. Java data types are split into two types: primitive types and object types. Primitive types describe atomic values, such as numbers and characters that cannot be broken into smaller pieces. Object types describe composite data objects made up of primitive values and objects references.
Object types are sometimes called reference types because each variable declared with an object type contains a reference to an object, not the object itself. Objects are kept in a separate area of memory known as the heap. In contrast, variables declared with primitive types contain actual data values, not references.
This is an important distinction in Java: Primitive values are always used directly, and objects are always used indirectly via object references. This might confuse programmers familiar with C or C++ because no explicit reference or pointer syntax exists in Java. Instead, objects are always used via implicit object references.
Primitive Types
Table 18.1 contains the eight primitive types in Java.
Table 18.1. Primitive Data Types
Type | Description | Range |
---|---|---|
Byte | 8-bit integer value | ± 127 |
Short | 16-bit integer value | ± 32,767 |
Int | 32-bit integer value | ± 2,147,483,647 |
Long | 64-bit integer value | ± 9,223,372,036,854,775,807 |
Float | 32-bit floating-point value | ± 3.4 x with 7-digit precision 1038 |
Double | 64-bit floating-point valuewith 15-digit precision | ±1.7 x 10308 |
Char | 16-bit Unicode character | Any of 65,536 characters |
Boolean | Logical truth value | Either true or false |
These data types are the same as used in C and C++, with the exception of Byte and Boolean. There are two other important differences. First, Java specifies the size and range of each numeric type, whereas size and range in C and C++ vary from machine to machine. This was done to help keep Java platform independent. Second, all characters in Java use the international 16-bit Unicode character set.
Variables are declared by putting the type name first, adding the name of the variable, and then adding a semicolon:
int a_intCarsInLot; boolean a_bolIsEmpty;
The preceding statements declare an integer variable named a_intCarsInLot and a Boolean variable named a_bolIsEmpty . You can declare several variables of the same type on a single line by separating the names with commas:
int a_intGoodOnes, a_intBadOnes, a_intTotal; boolean a_bolIsEmpty, a_bolSheLikesMe; char a_chrFavoriteLetter, a_chrFavoriteDigit;
A variable may also be initialized when it's declared:
int a_intYearToRemember = 1984; boolean a_bolSheLikesMe = true; float a_fltPrice = 12.50F, a_fltWalletCash = 3.00F, a_fltLoanFromHer = a_fltPrice a_fltWalletCash;
The convention in Java is to start variable names with lowercase letters and to separate words within the same identifier by using capital letters. Constants are declared with the modifier final appearing before the type. By convention, names of constants use only capital letters; words within the name are separated by underscores. Here are some examples:
float a_fltTemperatureOutside = 60.5; final float BOILING_POINT_OF_DISTILLED_WATER = 100.0; final boolean SHE_LOVES_ME = true;
Literals
Literals are constant values such as 2001 , 8.88 , and true . A literal can be assigned to a variable if the literal is consistent with the variable's data type. The literal object reference null is a special value that indicates "no reference" and can be assigned to any object type. Boolean variables can be assigned the literals true and false .
Variables with one of the four integer types (Byte, Short, Int, and Long) can be assigned any number without a fraction, such as 33 and 18015 , provided that the value falls within the ranges specified in Table 18.1.
Integer constants are always considered to be of type Int, but Java is pretty smart about implicitly converting to the other integer types if it can. If an integer constant is too big to fit in your variable, your code won't compile. You can add an L to the end of an integer constant to make it a Long, as in 35L . You can also add a lowercase l to do this, but because l looks like the digit 1 , this is a very bad idea. All integer literals are assumed to be decimal numbers, unless they begin with 0x (hexadecimal) or (octal). The following integer constants are equivalent:
22, 0x16, 026.
Variables with one of the floating-point types, Float and Double, can be assigned any number, with or without a fraction, provided that its value falls within the ranges specified. Floating-point constants are numbers with either a decimal point or an E within them and are always assumed to be of type Double. Unfortunately, Java is not as smart about converting floating-point constants. You cannot assign a Double constant to a Float variable, even if its value is in range. To give a constant the type Float, add an F to the end of it. The following constants are valid:
98.6, .314e1, 22E33, 1.06F.
Character literals are enclosed by single quotes, as in 'Z' , '$' , and '3' . You can use any Unicode character as a character constant, either by typing directly if your editor supports it or by using a Unicode escape sequence, such as u2668 . You can also use the following escape sequences to represent the indicated special characters:
- ” Newline or linefeed ( u000A )
- ” Carriage return ( u000D )
- ” Tab ( u0009 )
- \ ” Backslash ( u005C )
- ' ” Single quote ( u0027 )
- " ” Double quote ( u0022 )
- ” Backspace ( u0008 )
- f ” Form feed ( u000C )
String literals are enclosed by double quotes, as in "Who is John Galt?" . As with character constants, you can use the special characters in the preceding list in String constants. This is often useful when you want to use a character that might otherwise confuse the compiler, such as a double quote or backslash:
"He said, "Whatever you do, don't turn that thing on!". "C:LibertasImagesPromPic.jpg"
String constants cannot span lines. For example, the following is invalid:
"Atlas Shrugged by Ayn Rand"
Instead, use the newline escape sequence, , to achieve the same effect:
"Atlas Shrugged by Ayn Rand"
In Java, strings are actually objects rather than primitive data types and, as such, have methods that allow you to manipulate the contents of the string, for example:
strName.toUpperCase(); strName.toLowerCase();
Arrays
Arrays are sequences of data elements that you access by relative position using an index. In Java, arrays are objects, which means that array variables, like other object variables, contain references. An array declaration consists of the data type of its elements, a pair of brackets ( [] ), the name, and a semicolon. The following statement declares an array of integer values named highScoreArray :
int[] highScoreArray;
If you want to specify the size of the array when you declare it, you must explicitly create an array with the keyword new :
boolean[] ticketsAvailable = new boolean[ 15 ];
This creates an integer array with 16 elements.
To access the elements in an array, use brackets and an index number after the variable name. All index values start with , so the first element can be accessed like this:
ticketsAvailable[0] = false;
The expression ticketsAvailable[0] is equivalent to a Boolean variable. Array elements behave just like variables of the same type.
To determine the number of elements in an array, you can use the length method. To get the last element of an array, use an expression such as the following:
ticketsAvailable[ ticketsAvailable.length 1 ]
Because array indices begin with , the upper boundary of an array is one less than the length, which, in this case, is 14 . The expression ticketsAvailable.length “ 1 evaluates to 14 . If you use an array index that's less than the lower boundary or greater than the upper boundary, Java throws an IndexOutOfBounds exception. Exceptions are runtime errors in Java and are covered later in this chapter.
You can initialize an array all at once with a comma-separated list of literals surrounded by braces. The following statement creates a five-element array, with an upper boundary of 4 and a lower boundary of :
boolean[] ticketsAvailable = { true, false, false, true, false };
As in other languages, you can have multidimensional arrays in Java. Simply add a bracket pair for each dimension:
int[][] golfScores = new int[ 3 ][ 10 ];
This statement creates an array named golfScores that has three other arrays in it, each with 10 elements. You can access the elements like this:
golfScores[ 1 ][ 5 ] = 85;
Unlike other languages, Java lets you have nested arrays of differing lengths. Say that the first dimension ( 3 ) represents the golf courses at which you play, and the second dimension is the number of times you play a month. You might want to play at the first course more often than the others, in which case you could use the following statements:
int[][] golfScores = new int[ 3 ][]; golfScores[ 0 ] = new int[ 20 ]; golfScores[ 1 ] = new int[ 10 ]; golfScores[ 2 ] = new int[ 10 ];
Notice the blank second dimension in new int[ 3 ][] . This lets you add arrays at your leisure, with differing lengths, if desired. The first dimension must be specified.
To initialize a multidimensional array all at once, nest array initializers as follows :
int[][] golfScores = { { 80, 67, 92, 54 }, { 60, 70, 75 }, { 85, 89, 95, 100, 152 } };
In this example, golfScores[ 2 ][ 4 ] evaluates to 152 .
Arrays have fixed length in Java. To resize an array, you must make a new array and copy each element to the new array. Because of this, most people find it more convenient to use the Vector class, which allows varying lengths.
Objects and Classes
Objects are created from classes, which are essentially templates for making objects. Class declarations consist of the keyword class , the name of the class, and a sequence of member declarations surrounded by braces. There are two kinds of class members : fields and methods. Fields are variables contained within objects. Methods are named blocks of code that operate on objects of a class.
By convention, class names begin with capital letters. Field and method names begin with lowercase letters. In the examples, field names being with a lowercase m to indicate that they're members of a class. This isn't required of the language; it's just a convention to make life a little easier. For example:
class WeatherInfo { float m_fltTemperature; float m_fltBarometricPressure; int m_intWindMilesPerHour; char m_chrWindDirection; boolean m_bolOvercast, m_bolSnowing, m_bolRaining; }
This defines a class named WeatherInfo that contains seven fields. As you can see, classes are similar to records and structures in other programming languages. You declare an object variable much like a primitive variable, using the class name as the data type of the variable:
WeatherInfo wiForecast;
This declares a variable named wiForecast that holds a reference to a WeatherInfo object. C++ programmers, be careful here! This code will not create a WeatherInfo object. To do that, you must use the new keyword, the name of the class, and a pair of parentheses, as shown here:
WeatherInfo wiForecast = new WeatherInfo();
The new keyword tells Java to create an object using the given class. The object is first created on the heap; then a reference to it is stored in wiForecast .
Now that you have an object, you can access each field by placing a period and a field name after the variable name. This is commonly referred to as dotted notation:
wiForecast.m_fltTemperature = 451.0F; wiForecast.m_intWindMilesPerHour = 750; wiForecast.m_chrWindDirection = 'N'; wiForecast.m_bolSnowing = false; wiForecast.m_bolOvercast = true;
This code sets five of the fields of your object to appropriate (if unrealistic ) values. But what of the two missing fields, mBarometricPressure and mRaining ? Well, whenever an object is created, each of its fields is initialized to one of the following default values, depending on the field type:
Byte, Short, Int, Long | |
Double, Float | 0.0 |
Char | The null character ( /u0000 ) |
Boolean | false |
Object, array | null |
Therefore, by default, m_fltBarometricPressure is set to 0.0 , and m_bolRaining is set to false . Array elements are also initialized to the same default values. Unfortunately, variables declared within a method (also called local variables) must be initialized before they're used.
Using Expressions and Operators
An expression is a sequence of variables, literals, method calls, and operators that evaluates to a single data value. Operators determine the manner in which an expression is evaluated and the data type of the result. Here's an example:
M_intWindMilesPerHour > 30;
This expression contains one variable ( m_intWindMilesPerHour ), one operator ( > ), and one literal ( 30 ) that together evaluate to a Boolean value. The operator > is the greater-than operator, which evaluates to true if the number before it is greater than the number after it. Greater-than is one of the relational operators, which are listed here:
> | Greater than |
< | Less than |
== | Equal to |
!= | Not equal to |
>= | Greater than or equal to |
<= | Less than or equal to |
Relational operators are frequently combined with the logical operators && (and), (or), and ! (not), as in the following compound expression:
m_fltTemperature < 65 m_fltTemperature > 90
This expression is read as " mTemperature is less than 65 , or mTemperature is greater than 90 ." Individual operations within a compound expression are performed according to the relative precedence of each operator. Every language has its own operator precedence table, and Java is no different. The order precedence is shown here:
Postfix | [] , . , (params), expr++ , expr-- |
Unary | ++expr , --expr , +expr , -expr , ! |
Creation or cast | new , (type)expr |
Multiplicative | * , / , % |
Additive | + , - |
Shift | << , >> , >>> |
Relational | < , > , >= , <= , instanceof |
Equality | == , != |
Bitwise AND | & |
Bitwise XOR | ^ |
Bitwise OR | |
Logical AND | && |
Logical OR | |
Conditional | expr?expr:expr |
Assignment | = , += , -= , *= , /= , %= , >>= , <<= , >>>= , &= , ^= , = |
Operators nearer to the top of the table are said to have higher precedence than those below. Operators on the same line have equal precedence. The following expression evaluates to 15 , not 30 , because multiplication has a higher precedence than addition:
3 + 2 * 6
You can use parentheses to alter the order of evaluation. This next expression evaluates to 30, not 15:
(3 + 2) * 6
The data type of the result is usually determined by the last operation performed. The following two expressions (which are equivalent) evaluate to a Boolean value ”in this case, false :
6 + 50 < 5 + 5 * 10 (6 + 50) < (5 + (5 * 10))
Expressions with numeric operands evaluate to the type in the expression with the greatest range. Therefore, an Int and Long result in a Long, and a Long and a Float result in a Float. You can't implicitly assign a floating-point value to any integer, even if the value is within range. To force Java to do so, you must use an explicit cast, which is a type name within parentheses placed before the value you want to convert. An example of this is shown here:
int aInteger = (int) 123.4;
The (int) cast converts 123.4 (a floating-point type) to 123 (an integer type). Java simply discards the fractional part in such casts.
The logical operators && and are short-circuit operators, which means that evaluation of an expression quits when the truth value can be determined. This is often useful:
boolean a_bolGoodToGo = a_bolGasTankFull aCar.fillTheTank();
If the Boolean variable a_bolGasTankFull is true , aCar.fillTheTank() won't be called.
You have already seen the primary assignment operator = , which assigns the value of the expression on the right to the variable on its left:
a_intSum = a_intSum + 24;
This expression adds 24 to the variable a_intSum . The rest of the assignment operators offer a concise way to write expressions of this form by combining the operator with the equals sign. This next expression also adds 24 to the variable a_intSum :
a_intSum += 24;
Two other operators can change a variable's value directly: the increment ( ++ ) operator, which adds 1 to a variable, and the decrement ( -- ) operator, which subtracts 1:
a_intCount++;
The previous code adds 1 to the value of count .
You can place the increment and decrement operators before or after the variable name to achieve different results. Place them before (or prefix ) the variable name if you want to use the changed value of the variable in the current expression. Put them after (or postfix ) the variable name if you want the increment or decrement to happen after the variable is used in the expression. In the following example, the variable a_bolEqualsTen is assigned true because a_intCount is incremented after it's used in the comparison:
int a_intCount = 10; boolean a_bolEqualsTen = (a_intCount++ == 10);
In this next snippet, a_bolEqualsTen will be false because aCount is incremented beforehand:
int a_b_intCount = 10; boolean a_bolEqualsTen = (++a_intCount == 10);
Note that the parentheses here aren't strictly necessary. The expression would evaluate the same without them. Parentheses are sometimes used in short Boolean expressions to help the reader spot the logic.
Another place you will often see optional parentheses is with the conditional or ternary operator, which looks like this:
float a_fltPrice = (a_intQuantity < 10) ? 19.95F : 19.95F * a_fltDiscount;
In this expression, a_intQuantity < 10 is first evaluated. If it's true , a_fltPrice is assigned the part between the question mark and the colon , or 19.95F . Otherwise, a_fltPrice is assigned the result of the expression after the colon, or 19.95F * a_fltDiscount . Again, the parentheses aren't required, but they help make things clearer.
The remaining operators are used less frequently. The shift and bitwise operators manipulate individual binary digits, or bits, of an integer. These are sometimes used when setting, clearing, and checking individual flag bits within an integer mask. The remainder operator is used to determine the remainder from an integer division, as in this line:
35 % 10;
This evaluates to 5 . The instanceof operator determines whether an object was made from a particular class. The following expression evaluates to true :
aInfo instanceof WeatherInfo;
Building Methods
A method is a named sequence of executable statements and is much like a procedure or function in other programming languages. Like fields, methods are contained within classes and are said to be members of a class.
Method Declarations
A method declaration consists of the return type, the name of the method, a pair of parentheses (which often contain a list of parameters), and a sequence of statements surrounded by braces. The part before the braces is often called the method's signature. Here's a method in the WeatherInfo class:
boolean isGoodBeachDay() { // not good if the temperature is too low or too high if (m_fltTemperature < 60 m_fltTemperature > 90) return false; // not good if the wind is too strong if (m_intWindMilesPerHour > 30) return false; // not good if it's overcast, raining, or snowing if (m_bolOvercast m_bolRaining m_bolSnowing) return false; // otherwise, let's head to the beach return true; }
This method is named isGoodBeachDay() and has a return type of Boolean. The return type determines the data type of the result, if any. To indicate that a method returns no result, use the keyword void . The return statement tells Java to stop executing the method and return the indicated value. This method returns false if any of the three bad-weather conditions is met; otherwise, it returns true .
The main() Method
A class can have a special method named main() , which is the first method to execute in a program. To illustrate , here's the WeatherInfo class, complete with main() :
class WeatherInfo { // fields float m_fltTemperature; float m_fltBarometricPressure; int m_intWindMilesPerHour; char m_chrWindDirection; boolean m_bolOvercast, m_bolSnowing, m_bolRaining; // method to determine a good beach day boolean isGoodBeachDay() { // not good if the temperature is too low or too high if (m_fltTemperature < 65 m_fltTemperature > 90) return false; // not good if the wind is too strong if (m_intWindMilesPerHour > 30) return false; // not good if it's overcast, raining, or snowing if (m_bolOvercast m_bolRaining m_bolSnowing) return false; // otherwise, let's head to the beach return true; } // main method public static void main(String[] pArgs) { // create the weather info object WeatherInfo wiInfo = new WeatherInfo(); // initialize some weather stats wiInfo.m_fltTemperature = 72F; wiInfo.m_intWindMilesPerHour = 5; // display a message about today's beach day status if (wiInfo.isGoodBeachDay()) System.out.println("Time to go to the beach!"); else System.out.println("Better rent some movies ... it's nasty out there."); } }
This is a complete Java program. The signature of each main() method must appear like this preceding one. I'll explain public and static later in this chapter. The pArgs array is used to retrieve arguments specified when the program is started, although it's not used in this example. System.out.println() writes what you put between the parentheses to the system console. The expression wiInfo.isGoodBeachDay() is called a method invocation or method call. It causes the isGoodBeachDay() method to execute using fields in the l_wiInfo object. In the example, this method returns true , so the program displays the following:
Time to go to the beach!
Local Variables and Parameters
The variable l_wiInfo in the main() method of the preceding example is a local variable. Local variables can be declared anywhere within a method, provided that they're declared before they're used. In the examples, I use the prefix l to indicate a local variable.
When a method ends, the local variables defined within it go out of scope, which means that the values and references they contain are lost. Unlike other languages, Java doesn't require that you free or delete your objects. The JVM garbage collector destroys objects on the heap that are no longer referenced. This makes life a lot easier.
Parameters are variables contained within the signature. They're used to pass data to the method when it's called. In the examples, I use the prefix p to indicate parameters.
All parameters in Java are pass- by-value , which means that when you change the value of a parameter within a method, the change won't affect the calling method. For example, say that you have a method with the following signature:
int getLowTempOnDay(int p_intDayOfMonth)
This method has a single integer parameter. You might call the method like this:
int l_intDayOfMonth = 10; int l_intTemp = l_wiInfo.getLowTempOnDay(l_intDayOfMonth);
This initializes the variable l_intDayOfMonth to 10 and then calls getLowTempOnDay() on the l_wiInfo object, passing the value of l_intDayOfMonth . The value in l_intDayOfMonth is then copied (or passed) to the variable p_intDayOfMonth , which is then available for use within getLowTempOnDay() . Any changes to p_intDayOfMonth within getLowTempOnDay() are local and will not affect the value of l_intDayOfMonth in the calling method.
Keep in mind that object variables contain references, not values. References are passed by value, but the objects to which they point can be accessed from both the calling method and the called method. Say that your method has this signature:
int getLowTempOnDay(Date p_dtSecondDate)
You could then use the method like this:
Date l_dtFirstDate = new Date(); // defaults to right now! int l_intTemp = l_wiInfo.getLowTempOnDay(l_dtFirstDate);
If you make a change to the object referenced by p_dtSecondDate in getLowTempOnDay() , it will affect l_dtFirstDate because both reference the same object.
Every primitive type has a corresponding wrapper class that can be used in places where an object is more useful than a primitive value. The class name in each case is the full descriptive name of the type, but with a capital letter:
Character l_chrInitial = new Character('Z');
This statement creates a Character object that contains the letter z . Wrapper classes can be used to pass primitive values by reference, as with the previous Date example.
You can have more than one method in a class with the same name, provided that each takes a different set of parameters. This technique, called overloading, enables you to have both versions of getLowTempOnDay() in your class. Java chooses which to use, depending on whether you passed an integer or a date.
Constructors
Classes can have a special kind of method known as a constructor that's called whenever an object of the class is created with new . Constructors are used to initialize an object when default values aren't enough. Constructor declarations use the same name as the class, and they leave out the return type. For example:
WeatherInfo() { m_fltTemperature = 72F; m_intWindMilesPerHour = 30; }
This initializes m_fltTemperature and m_intWindMilesPerHour to the indicated values when you create a WeatherInfo object like this:
WeatherInfo l_wiInfo = new WeatherInfo();
Constructors can have parameters, just like other methods. You can also overload constructors to provide more than one way to initialize an object:
public WeatherInfo(float p_fltTemperature, int p_intWindMilesPerHour) { m_fltTemperature = p_fltTemperature; m_fltWindMilesPerHour = p_intWindMilesPerHour; }
This constructor lets you specify the initial temperature and wind speed:
WeatherInfo l_wiInfo = new WeatherInfo(65.5, 10);
Modifiers
Notice the word public in the preceding declaration. This is an access control modifier. Such modifiers are placed in front of any member (field or method) to indicate whether and how the member can be used outside the class. Members declared public can be accessed from any class. Members declared private can be used only within their own class. Methods declared without an access modifier can be used only by classes in the same package. Members declared protected can be used only by subclasses and classes in the same package.
It's a great programming practice to hide fields by declaring them private. This enables you to make internal changes to a class without affecting other code that uses the class. You should refrain from using fields directly, as in this example:
l_wiInfo.m_fltTemperature = 65F;
The preferred way to access fields is to use accessor methods to encapsulate your object's data. By convention, use the prefix get for methods that retrieve a field's value, and use set for methods that change a value. Fields accessed in this way are called properties. The part after the get or set is the property name. Boolean properties may use is instead of get . For example:
class WeatherInfo { private float m_fltTemperature; private boolean m_bolOvercast; public float getTemperature() { return m_fltTemperature; } public void setTemperature(float p_fltVal) { m_fltTemperature = p_fltVal; } public boolean isOverCast() { return m_bolOvercast; } public void setOvercast(boolean p_bolVal) { m_bolOvercast = p_bolVal; } }
The modifier static can be used to indicate that a field or method belongs to the class itself and not individual objects of the class. Static fields are similar to global variables in other languages. In the examples, I use the prefix s to indicate static fields.
class WeatherInfo { public static int s_intRecordLowTemp = -30; ... public static boolean isRecordLow(int p_intTemp) { return (p_intTemp < s_intRecordLowTemp) } }
Here, the field s_intRecordLowTemp is shared by all objects of type WeatherInfo . The static method isRecordLow() uses s_intRecordLowTemp directly. Static fields and methods can be used outside the class by using the class name instead of a variable name:
if (WeatherInfo.isRecordLow(l_intTemp)) WeatherInfo.sRecordLowTemp = l_intTemp;
They can also be used with object variables or within methods, just like normal fields:
l_wiTampaInfo.s_intRecordLowTemp = 25; l_wiBuffaloInfo.s_intRecordLowTemp = -50;
Both these statements change the same shared field. Therefore, the first assignment has no effect because the value 25 is immediately replaced by the value 50 . If s_intRecordLowTemp were a normal field, both objects would have their own internal copy.
Making Statements
A statement is a single action in a program. Statements always end with a semicolon. The most common kinds of statement are declarations, expressions, and compound statements. Declaration statements declare and optionally initialize variables:
char l_chrMiddleInitial = 'Q';
Expression statements are essentially expressions with a semicolon at the end. The expression types that make sense as statements are assignment, increment, decrement, and method invocation types, such as the following:
l_intSum += l_intDelta; // assignment l_intSum++; // increment l_intDelta--; // decrement l_wiInfo.setTemperature(l_intSum); // invocation
Compound statements, also called blocks, consist of a sequence of statements contained within braces. Compound statements can be used wherever a normal statement is allowed. It's important to note that variables declared within a block are local to that block and cannot be used outside it. Blocks are often used with loops and conditional statements:
if (l_intTempNow < l_intLowestTemp) { l_intLowestTemp = l_intTempNow; l_wiInfo.setLowestTemp(l_intTempNow); }
The remaining statement types are named for the keywords they use. Most of these are identical in form and function to their C and C++ counterparts.
return
The return statement is used to end execution of a method and optionally return a value. The returned value must be consistent with the return type in the method's signature:
int getTheAnswerToLifeTheUniverseAndEverything() { return 42; }
Methods with a return type of void cannot return a value, although you can still use return to exit a method prematurely ”simply omit the value, as shown:
return;
return statements contained within a try block that has a finally clause always execute the finally clause before returning. This enables you to simplify your method by putting all the cleanup code in the finally clause.
while
The while statement is one of three looping constructs in Java. It consists of the keyword while , a Boolean expression contained within parentheses, and a statement to execute repeatedly as long as the expression evaluates to true , if ever. For example
while(l_wiInfo.isRaining()) l_wiInfo.getNewestWeatherStats();
If l_wiInfo.isRaining() evaluates to false the first time, the statement aInfo.getNewestWeatherStats() is never executed. Otherwise, it is called repeatedly, presumably updating the rain status until isRaining() returns false .
Compound statements are often used with while statements:
int l_intCount = 1; while(l_intCount < 10) { System.out.println("Count is " + l_intCount); l_intCount++; }
for
The for loop is the most concise. The preceding example can be written as follows:
for(int l_intCount = 1; l_intCount < 10; l_intCount++) System.out.println("Count is " + l_intCount);
The meat of the loop is in the three expressions in parentheses. The first expression is the initialization expression. It is executed before the loop begins. As you can see, variables can be declared here. The second expression is the loop conditional. Like the while loop, this is checked before each iteration of the loop. If the conditional is false to start with, the loop will never execute. The third expression is the iteration expression. This is executed after each iteration of the loop.
You can leave out any of the three expressions, but you still must put in each semicolon. The most extreme case is the infinite loop:
for(;;) doSomething();
There must be a way to break out of this, such as a break, a return, or a thrown exception, or it will keep looping until the user kills the program or the program crashes.
do...while
The do...while loop is the same as the while loop, except that the conditional is evaluated after each iteration. This means that do...while loops always execute their statement at least once. This loop is nearly equivalent to the earlier isRaining() example:
do { l_wiInfo.getNewestWeatherStats(); } while(l_wiInfo.isRaining());
The only difference is that getNewestWeatherStats() will be called whether or not it's raining. The braces aren't strictly necessary here because there's only one statement in the block, but they help clarify the logic, It's also good to get in the habit of using the braces so that you don't inadvertently leave one off when you need it.
break and continue
The break statement is used to exit a block, usually contained within a loop:
for(;;) { l_wiInfo.getNewestWeatherStats(); if(! l_wiInfo.isRaining()) break; }
This code keeps getting weather stats until it's not raining; it then exits this otherwise infinite loop. The break statement is also used in switch statements.
The continue statement behaves like break , but instead of breaking entirely out of a loop, it skips the rest of the current iteration and starts a new one. For example, you might want a loop that calculates the prime numbers to 100:
for(int l_intCount = 3; l_intCount < 100; l_intCount++) { if (l_intCount % 2 == 0) continue; // prime number test }
The expression l_intCount % 2 == 0 tests whether l_intCount is even. Because even numbers greater than 2 are never prime, there's no point in testing them, so continue skips to the next iteration. (A better way to do this is use l_intCount += 2 as an iteration expression.)
Both break and continue can specify a label to skip to. Labels are names given to statements or blocks that are sometimes helpful when using nested loops. For example:
outer: for(int l_intOuterCount = 0; l_intOuterCount < 10; l_intOuterCount++) { for(int l_intInnerCount = 10; l_intInnerCount > 0; l_intInnerCount--) { if (l_intInnerCount == 2) continue outer; // some processing if (l_intOuterCount == l_intInnerCount) break outer; } }
The label outer is used to indicate the outer loop. When l_intOuterCount and l_intInnerCount are equal, both loops end because of the break outer statement. Using break by itself would end only the inner loop. Likewise, the continue outer statement starts another iteration of the outer loop, not the inner loop.
if...else
The if statement is very straightforward. It's used to test a Boolean expression before executing another statement:
if(l_bolthisIsTrue) doThis();
The doThis() method executes only if the expression between the parentheses evaluates to true . You can also add an else clause:
if(l_bolthisIsTrue) doThis(); else doThat();
The doThat() method executes if the conditional evaluates to false . You can nest if...else statements as much as you like, but keep in mind that each else matches the preceding if . Blocks are used to execute groups of statements conditionally:
if(getRoomTemp() < m_fltGaugeTemp) { turnOnHeat(); while(getRoomTemp() < m_fltGaugeTemp) blinkHeatLight(); turnOffHeat(); }
switch
The switch statement uses an integer expression to choose among a sequence of statement groups. Each group begins with a case label, which consists of the keyword case , an integer expression, and a colon. Java executes the first statement group with a case label that matches the initial integer expression. For example:
switch(l_intLotionSPF) { case 0: System.out.println("Better have a good base."); case 4: System.out.println("Don't get burned!"); break; case 30: System.out.println("Why bother!"); break; default: System.out.println("Never heard of SPF " + aLotionSPF); }
If l_intLotionSPF is equal to , 4 , or 30 , the first statement following the corresponding case label is executed. If no matching case label is found, the default case is used. The default case is optional. If no default exists, the entire switch statement is skipped .
Two things are worth noting about switch statements. First, it's unnecessary to place statement groups within braces. Second, if you don't have a break statement at the end of each statement group, it will keep executing until it reaches a break or the end of the switch statement. So, if l_intLotionSPF equals , the program displays this:
Better have a good base. Don't get burned!
try...catch...finally and throw
Java has built-in support for exception handling. Exceptions are objects that represent exceptional events, usually errors. Exceptions are thrown with the throw statement:
throw new IOException("The hard drive's on fire!");
Code where an exception might occur is enclosed within try blocks. If an exception occurs within a block, it's passed as a parameter to the catch clause that follows the block:
void doSomething() { try { // dividing by zero is a no-no System.out.println(441 / 0); } catch(ArithmeticException e) { System.out.println("My goodness!Something went wrong: " + e.getMessage()); } }
This example causes an ArithmeticException to be thrown, which is then caught in the catch clause and passed as the parameter e . The method getMessage() is then used to retrieve a text message explaining the exception. This example displays the following:
My goodness! Something went wrong: / by zero
Many kinds of exceptions exist, each represented by its own class. You can have more than one catch clause after a try block to handle different exception types. You can also tack on a finally clause, which encloses code that will always be executed whether an exception occurs. This is a great place to put cleanup code.
void doSomething() { try { // open files to process } catch(IOException e) { System.out.println("Hey, something went wrongreading the file!"); } catch(ArithmeticException e) { System.out.println("Okay, so math's not my subject!"); } finally { // close files and say goodnight } }
Java's a stickler about exception handling, which is good because it makes you write better code. Your code won't compile unless you handle all checked exceptions, such as IOException . If you'd like to pass the buck in a given method, you can add a throws clause in the method's signature:
public void passTheBuck() throws IOException { // open some files }
It then becomes the responsibility of the calling method to handle the exception. You can pass the buck all the way back to main() if you want, but it stops there. Exceptions that are thrown several methods deep cause each calling method to abort until a catch clause is found. This is called stack unwinding , and it's a wonderful thing. Error checks need only appear where errors happen and where they're handled, not at every step in between.
Understanding Inheritance
Inheritance is a fundamental feature of Java and is critical to the design of any nontrivial software system. Because Notes makes little use of it, though, I'll just cover the bases here.
A class can inherit fields and methods from another class. You do this by adding the keyword extends and the name of the class from which you want to inherit:
class ExtremeWeatherInfo extends WeatherInfo { int m_intTornadoClass; int m_intSnowInches; float m_fltEarthquakeRichterValue; public boolean isHurricane() { return (m_intWindMilesPerHour > 75); } public boolean isBlizzard() { return (m_intSnowInches > 10); } }
The ExtremeWeatherInfo class is said to extend the WeatherInfo class. The original class is called the superclass, and the extending class is called the subclass. Although this might seem backward, it makes sense when looking at a class hierarchy diagram, which almost always has superclasses near the top of the page and subclasses below.
Objects created from a subclass contain the fields of the object's superclasses along with its own. Methods of a subclass can use superclass fields, as in the isHurricane() method, which uses the m_intWindMilesPerHour field of WeatherInfo . This arrangement enables you to put common attributes and behavior in superclasses and share functionality between subclasses. This helps reduce cut-and-paste duplication, a practice usually leading to buggy code that's very hard to maintain and extend.
Object Casting
Subclass objects can be assigned to variables with a superclass type. This is called implicit casting. Such variables can use members of the superclass, but not the subclass:
ExtremeWeatherInfo l_ewiExtremeInfo = new ExtremeWeatherInfo(); WeatherInfo l_wiInfo = l_ewiExtremeInfo; if (l_wiInfo.isRaining()) System.out.println("Is it in Spain? On a plain?"); if (l_ewiExtremeInfo.isHurricane()) System.out.println("Batten down the hatches!"); if (l_wiInfo.isHurricane()) System.out.println("This won't compile. It's ILLEGAL!");
You can also pass a subclass object to a superclass parameter. For example, you can invoke the following method with an ExtremeWeatherInfo parameter:
public void recordStats(WeatherInfo p_wiInfo) { // ... }
However, the reverse isn't true. Assigning variables of a superclass type to variables of a subclass type is called downcasting and can be dangerous. If you really want to force the issue, you can use an explicit cast. To do this, put the subclass in parentheses, like this:
l_ewiExtremeInfo = (ExtremeInfo) l_wiInfo;
Be prepared for a ClassCastException if the class doesn't match the object.
The Object Class
All classes inherit from the Object class. Classes that don't have an extends clause implicitly inherit from Object , as do arrays. The Object class defines many useful methods, such as equals() , which is used to determine whether an object is equal to another object, and clone() , which creates a copy of an object. The equals() method in Object returns true if the variables being tested refer to the same object:
if (l_wiInfo.equals(l_ewiExtremeInfo)) System.out.println("They're the same thing!");
You will often want to override this default equals() method so that you can use it to see whether two different objects have the same field values. For example, the following method could be added to the WeatherInfo class:
public boolean equals(Object p_objTemp) { // downcast the passed-in Object to the appropriate class WeatherInfo l_wiInfo = (WeatherInfo) p_objTemp; if (l_wiInfo.m_intTemperature != m_intTemperature) return false; if (l_wiInfo.m_fltBarometricPressure != m_fltBarometricPressure) return false; if (l_wiInfo.m_intWindMilesPerHour != m_intWindMilesPerHour) return false; if (l_wiInfo.m_chrWindDirection != m_chrWindDirection) return false; if (l_wiInfo.m_bolOvercast != m_bolOvercast) return false; if (l_wiInfo.m_bolSnowing != m_bolSnowing) return false; if (l_wiInfo.m_bolRaining != m_bolRaining) return false; return true; }
This method compares each field in both objects. It can be used like this:
if (l_wiSeattleInfo.equals(l_wiNaplesInfo)) System.println("Don't see this every day!");
Objects of type ExtremeWeatherInfo can use this method, but it won't test subclass fields such as m_intSnowInches . Because of this, you will also want to override equals() in ExtremeWeatherInfo :
public boolean equals(Object pObj) { // check equality of superclass fields if (!super.equals(pObj)) return false; // downcast the passed-in Object to the appropriate class ExtremeWeatherInfo aExtremeInfo = (ExtremeWeatherInfo) pObj; if(aExtremeInfo.mTornadoClass != mTornadoClass) return false; if(aExtremeInfo.mSnowInches != mSnowInches) return false; if(aExtremeInfo.mEarthquakeRichterValue != mEarthQuakeRichterValue) return false; return true; }
The keyword super is used as a special kind of variable. Use it to call the superclass version of a method. In the example, super.equals() calls the WeatherInfo version of equals() . Another special variable name is this , which contains a reference to the object on which the method was called. All fields in an object have an implicit this reference in front of them, as in this.mSnowInches . You will sometimes find this useful when passing a reference to the current object, as shown here:
recordStats(this);
You cannot use super or this in static methods because static methods operate on the class fields and not on fields within objects. Static methods have no this !
Interfaces
Interfaces are used to define a common set of attributes and behaviors that many classes share. Classes can implement many interfaces, but they can extend only one class. This means that Java does not support multiple inheritance. Instead, interfaces are used to determine functionality beyond that provided by the superclass.
Interface declarations look like class declarations, but with two key differences. First, interfaces are declared with the keyword interface instead of the word class . Second, their methods contain only signatures, not bodies:
public interface Thermostat { int MAX_TEMP = 100; int MIN_TEMP = 60; int getRoomTemp(); void turnOnHeat(); void turnOffHeat(); void blinkHeatLight(); void checkTemp(); }
This interface declares two fields and five methods. Fields defined in an interface are implicitly static and final, which makes interfaces a good place to put constants. To implement this interface in a class, use the keyword implements after the class name or extends clause. You can implement more than one interface by separating the names with commas:
class PortableHeater extends Heater implements Thermostat, HomingDevice { // ... }
Methods in an interface must be defined in every class that implements the interface. This might seem like a pain, but it's not so bad in practice. One common approach is to create a helper class that implements the interface and then include a helper object in your class. You can then provide quick "patch" methods such as this one:
class Heater implements Thermostat { ThermostatHelper m_thHelper = new ThermostatHelper(); int getRoomTemp() { return m_thHelper.getRoomTemp(); } void turnOnHeat() { m_thHelper.turnOnHeat(); } void turnOffHeat() { m_thHelper.turnOffHeat(); } void blinkHeatLight() { m_thHelper.blinkHeatLight(); } void checkTemp() { m_thHelper.checkTemp(); } }
Interfaces are a kind of data type, just like classes. Objects made from a class that implements an interface can be assigned to variables with the interface type. You can also call methods with these variables, provided that the method belongs to the interface:
Heater aHeater = new Heater();
Thermostat aThermostat = aHeater;
aThermostat.checkTemp();
Категории