Published: January 1, 2004
By Richard G. Baldwin
Purpose
The purpose of this miniseries is to help you study for the Advanced Placement Examinations designed by the College Board.
Once you understand everything in this miniseries, plus the material in the lessons that I published earlier on Java Data Structures, you should understand the Java programming features that the College Board considers essential for the first two semesters of object-oriented programming education at the university level.
Approach
These lessons provide questions, answers, and explanations designed to help you to understand the subset of Java features covered by the Java Advanced Placement Examinations (as of October, 2001).
Please see the first lesson in the miniseries entitled Java Advanced Placement Study Guide: Introduction to the Lessons, Primitive Types, for additional background information. The previous lesson was entitled Java Advanced Placement Study Guide: Comparing Objects, Packages, Import Directives, and Some Common Exceptions.
Supplementary material
In addition to the material in these lessons, I recommend that you also study the other lessons in my extensive collection of online Java tutorials, which are designed from a more conventional textbook approach. You will find those lessons published at Gamelan.com. However, as of the date of this writing, Gamelan doesn't maintain a consolidated index of my Java tutorial lessons, and sometimes they are difficult to locate there. You will find a consolidated index at Baldwin's Java Programming Tutorials.
What is Included?
Click here for a preview of the
Java
programming features covered by this lesson.
public class Ap151{ |
2. What output is produced by the following program?
public class Ap152{ |
3. What output is produced by the following program?
import java.util.Random; |
4. What output is produced by the following program?
import |
5. The following source code is contained in a single file named Ap155.java
What output is produced by the program?
public class Ap155{ |
6. A Java application consists of the two source files shown below, having names of AP156.java and AP156a.java
What output is produced by this program?
public class Ap156{ |
public class Ap156a{ |
7. Explain the purpose of the boldface terms
@param
and @return in the following program. Also explain any of
the other terms that make sense to you.
public class Ap157{ |
8. What output is produced by the following program?
public class Ap158{ |
9. What output is produced by the following program?
public class Ap159{ |
10. What output is produced by the following program?
public class Ap160{ |
Richard has participated in numerous consulting projects involving Java, XML, or a combination of the two. He frequently provides onsite Java and/or XML training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Java Programming Tutorials, which has gained a worldwide following among experienced and aspiring Java programmers. He has also published articles on Java Programming in Java Pro magazine.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
Success at last
The boldface code in the following fragment resolves the compilation
problem from Question 8 and the runtime problem from Question 9.
void doIt(){ |
Simply initializing the local reference variable named ref satisfies the compiler, making it possible to compile the program.
Initializing the local reference variable named ref with a reference to a valid array object eliminates the NullPointerException that was experienced in Question 9.
Printing the contents of the array object
The print statement passes the reference variable to the print() method. The print() method finds that the reference variable refers to a valid object (instead of containing null as was the case in Question 9) and behaves accordingly.
The print statement causes the initialized
contents
of the array object to be displayed. Then those contents are
replaced
with a new set of characters. The println statement causes the
new
characters to be displayed.
This is an update to the program from Question
8. The boldface code in the following fragment solves the
compilation
problem identified in Question 8.
void doIt(){ |
In particular, initializing the value of the reference variable named ref satisfies the compiler and makes it possible to compile the program.
A NullPointerException
However, there is still a problem, which causes a runtime error.
The following statement attempts to use the
reference
variable named ref to print something on the screen. This
results, among other things, in an attempt to invoke the toString()
method on the reference. However, the reference doesn't refer to
an object. Rather, it contains the value null.
System.out.print(ref); |
The result is a runtime error with the following infamous NullPointerException message appearing on the screen:
java.lang.NullPointerException
at
java.io.Writer.write(Writer.java:107)
at
java.io.PrintStream.write(PrintStream.java:245)
at
java.io.PrintStream.print(PrintStream.java:396)
at Worker.doIt(Ap159.java:22)
at Ap159.main(Ap159.java:15)
Earlier programming languages, notably C and C++ allowed you to inadvertently write programs that process the garbage left in memory by previous programs running there. This happens when the C or C++ programmer fails to properly initialize variables, allowing them to contain left-over garbage from memory.
Member variables are automatically initialized to default values
That is not possible in Java. All member variables in a Java object are automatically initialized to a default value if you don't write the code to initialize them to some other value.
Local variables are not automatically initialized
Local variables are not automatically initialized. However, your program will not compile if you write code that attempts to fetch and use a value in a local variable that hasn't been initialized or had a value assigned to it.
Print an uninitialized local variable
The boldface statement in the following code fragment attempts to
fetch
and print a value using the uninitialized local variable named ref.
void doIt(){ |
As a result, the program refuses to compile, displaying the following error message under JDK 1.3.
Ap158.java:23: variable ref might not have been initialized
System.out.print(ref);
When you download the JDK from Sun, you receive a program named javadoc.exe in addition to several other programs.
The purpose of the javadoc program is to help you to document the Java programs that you write. You create the documentation by running the javadoc program and specifying your source file as a command-line parameter. For example, you can generate documentation for this program by entering the following at the command line.
javadoc Ap157.java
Produces HTML files as output
This will produce a large number of related HTML files containing documentation for the class named Ap157.java. The primary HTML file is named Ap157.html.
(As a labor saving device, you can also specify a group of input files to the javadoc program, using wildcard characters as appropriate, to cause the program to produce documentation files for each of the input files in a single run.)Special documentation comments and directives
If you include comments in your source code that begin with /** and end with */ they will be picked up by the javadoc program and become part of the documentation.
In addition to plain comments, you can also enter a variety of
special
directives to the javadoc program as shown in the following
program.
public class Ap157{ |
The @param and @return directives
The @param and @return directives in the source code shown above are used by the javadoc program for documenting information about parameters passed to and information returned from the method named charAt(). The method definition follows the special javadoc comment.
The javadoc program output
Click here to view one of the html files produced by running the above program through javadoc. Try to correlate the information in the javadoc comments and directives above with the contents of the documentation file.
(Note that this is only one of many related files produced in this documentation process. To view all of the files, you will need to run the javadoc program yourself. Note also that I changed the name of this file from AP157.html to JavaAP028a.htm to retain name consistency in the publishing process. The output file name produced by the javadoc program was AP157.html.)
This program meets the requirement identified in Question 5. In particular, this program defines two public classes. The source code for each public class is stored in a separate file. Thus, the program compiles and executes successfully, producing the text OK on the screen.
Java requires that the source code for every public class be contained in a separate file. In this case, the source code for two public classes was contained in a single file. The following compiler error was produced by JDK 1.3:
Ap155.java:18: class Ap155a is public, should be declared in
a file named Ap155a.java
public class Ap155a{
This program defines, creates, and uses a very simple container object for the purpose of illustrating the NoSuchElementException.
The code in the following fragment shows the beginning of a class
named
MyContainer
from which the container object is instantiated.
class MyContainer{ |
A wrapper for an array object
This class is essentially a wrapper for a simple array object of type int. An object of the class provides a method named put(), which can be used to store an int value into the array. The put() method receives two parameters. The first parameter specifies the index of the element where the value of the second parameter is to be stored.
Throw NoSuchElementException on index out of bounds
The put() method tests to confirm that the specified index is within the positive bounds of the array. If not, it uses the throw keyword to throw an exception of the type NoSuchElementException. Otherwise, it stores the incoming data value in the specified index position in the array.
(Note that a negative index will cause an ArrayIndexOutOfBoundsException instead of a NoSuchElementException to be thrown.)The get method
An object of the MyContainer
class
also provides a get() method that can be used to retrieve the
value
stored in a specified index.
public int get(int idx){ |
The get() method also tests to confirm that the specified index is within the positive bounds of the array. If not, it throws an exception of the type NoSuchElementException. Otherwise, it returns the value stored in the specified index of the array.
(As noted earlier, a negative index will cause an ArrayIndexOutOfBoundsException instead of a NoSuchElementException to be thrown.)The NoSuchElementException
Thus, this container class illustrates the general intended purpose of the NoSuchElementException.
Instantiate and populate a container
The remainder of the program simply exercises
the container. The code in the following fragment instantiates a
new container, and uses the put() method to populate each of
its
three available elements with the values 5, 10, and 15.
void doIt(){ |
Get and display the data in the container
Then the code in the next fragment uses the get() method to get and display the values in each of the three elements, causing the following text to appear on the screen:
5 10 15
System.out.print(ref.get(0)+" "); |
One step too far
Finally, the code in the next fragment goes
one
step too far and attempts to get a value from index 3, which is outside
the bounds of the container.
System.out.print(ref.get(3)+" "); |
This causes the get() method of the container object to throw a NoSuchElementException. The program was not designed to handle this exception, so this causes the program to abort with the following text showing on the screen:
5 10 15 java.util.NoSuchElementException
at MyContainer.get(Ap154.java:49)
at Worker.doIt(Ap154.java:30)
at Ap154.main(Ap154.java:15)
(Note that the values of 5, 10, and 15 were displayed on the screen before the program aborted and displayed the error message.)
This program illustrates the use of the throw keyword to throw an exception.
(Note that the throw keyword is different from the throws keyword.)Throw an exception if random boolean value is true
A random boolean value is obtained. If the value is true, the program throws an IllegalStateException and aborts with the following message on the screen:
java.lang.IllegalStateException
at Worker.doIt(Ap153.java:29)
at Ap153.main(Ap153.java:20)
If the random boolean value is false, the program runs to completion, displaying the text OK on the screen.
Instantiate a Random object
The following code fragment instantiates a new object of the Random
class and stores the object's reference in a reference variable named ref.
void doIt(){ |
I'm not going to go into a lot of detail about the Random class. Suffice it to say that an object of this class provides methods, which will return a pseudo random sequence of values upon successive invocations. You might think of this object as a random value generator.
Seeding the random generator
The constructor for the class accepts a long integer as the seed for the sequence.
(Two Random objects instantiated using the same seed will produce the same sequence of values.)In this case, I obtained the time in milliseconds, relative to January 1, 1970, as a long integer, and provided that value as the seed. Thus, if you run the program two times in succession, with a time delay of at least one millisecond in between, the random sequences will be different.
Get a random boolean value
The code in the next fragment invokes the nextBoolean()
method on the Random object to obtain a random boolean value.
if(ref.nextBoolean()){ |
Throw an exception
If the boolean value obtained in the above fragment is true, the code instantiates a new object of the IllegalStateException class, and uses the throw keyword to throw an exception of this type.
Program aborts
The program was not designed to gracefully handle such an exception. Therefore the program aborts, displaying the error message shown earlier.
Don't throw an exception
The code in the next fragment shows that if
the
boolean
value tested above is false, the program will display the text OK
and run successfully to completion.
}else{ |
You may need to run the program several times to see both possibilities.
It is allowable, but not necessary, to cast the type of an object's reference toward the root of the inheritance hierarchy.
It is also allowable to cast the type of an object's reference along the inheritance hierarchy toward the actual class from which the object was instantiated.
Cast is not allowable ...
However, (excluding interface type casts), it is not allowable to cast the type of an object's reference in ways that are not related in a subclass-superclass inheritance sense. For example, you cannot cast the type of an object's reference to the type of a sibling of that object.
Two sibling classes
The code in the following fragment defines two simple classes named
MyClassA
and MyClassB. By default, each of these classes extends
the
class named Object. Therefore, neither is a superclass of
the other. Rather, they are siblings.
class MyClassA{ |
Instantiate one object from each sibling class
The code in the next fragment instantiates one object from each of the above classes, and stores references to those objects in reference variables of type Object.
Then the code causes the overridden toString() method of one
of the objects to be invoked by passing that object's reference to the
print() method.
void doIt(){ |
The code in the above fragment causes the text OK to appear on the screen.
Try to cast to a sibling class type
At this point, the reference variable named ref1 holds a reference to an object of type MyClassA. The reference is being held as type Object.
The statement in the next fragment attempts to cast that reference
to
type
MyClassB, which class is a sibling of the class named MyClassA.
MyClassB ref3 = (MyClassB)ref1; |
A ClassCastException
The above statement causes a ClassCastException to be thrown, which in turn causes the program to abort. The screen output is shown below:
OK java.lang.ClassCastException:MyClassA
at Worker.doIt(Ap152.java:24)
at Ap152.main(Ap152.java:14)
(Note that the text OK appeared on the screen before the program aborted and displayed diagnostic information on the screen.)
This program illustrates type conversion up the inheritance hierarchy, both with and without a cast.
Store object's reference as type Object
The following fragment instantiates a new object of the class named
MyClassA,
and stores that object's reference in a reference variable of type Object.
This demonstrates that you can store an object's reference in a
reference
variable whose type is a superclass of the class from which the object
was instantiated, with no cast required.
class Worker{ |
Cast object's reference to type Object
The code in the next fragment instantiates an object of the class
named
MyClassB,
and stores the object's reference in a reference variable of type Object,
after first casting the reference to type Object. This,
and
the previous fragment demonstrate that while it is allowable to cast a
reference to the superclass type before storing it in a superclass
reference
variable, such a cast is not required.
Object refB = |
Type conversion and assignment compatibility
This is part of a larger overall topic commonly referred to as type conversion. It also touches the fringes of something that is commonly referred to as assignment-compatibility.
Automatic type conversions
Some types of type conversions happen automatically. For example, you can assign a value of type byte to a variable of type int and the type conversion will take place automatically.
Cast is required for narrowing conversions
However, if you attempt to assign a value of type int to a variable of type byte, the assignment will not take place automatically. Rather, the compiler requires you to provide a cast to confirm that you accept responsibility for the conversion, which in the case of int to byte could result in the corruption of data.
Automatic conversions up the inheritance hierarchy
When working with objects, type conversion takes place automatically for conversions in the direction of the root of the inheritance hierarchy. Therefore, conversion from any class type to type Object happen automatically. However, conversions in the direction away from the root require a cast.
(Conversion from any class type to any superclass of that class also happens automatically.)Polymorphic behavior
The code in the next fragment uses polymorphic behavior to display
the
contents of the two String objects.
System.out.print(refA); |
No cast required
This works without the use of a cast because the print() method invokes the toString() method on any object's reference that it receives as an incoming parameter. The toString() method is defined in the Object class, and overridden in the String class. Polymorphic behavior dictates that in such a situation, the version of the method belonging to the object will be invoked regardless of the type of the reference variable holding the reference to the object.
When would a cast be required?
Had the program attempted to invoke a method on the reference that
is
not defined in the Object, class, it would have been necessary
to
cast the reference down the inheritance hierarchy in order to
successfully
invoke the method.
Richard has participated in numerous consulting projects involving Java, XML, or a combination of the two. He frequently provides onsite Java and/or XML training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Java Programming Tutorials, which has gained a worldwide following among experienced and aspiring Java programmers. He has also published articles on Java Programming in Java Pro magazine.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
-end-