Before applets, the Web, Windows, and the Macintosh, most programs were text-based console applications. These programs received user keyboard entries and displayed text output to the user. Although most of the programs we use today are GUI-based, console programs still play a role in some systems and applications. For example, I still use console programs with my Linux computer, and I still run the Java compiler from a DOS shell.
In this chapter you'll learn to write console programs in Java. You'll learn to read user keyboard input, process it, and display text output to the user's console. You'll also learn about some useful classes and interfaces in the java.lang, java.lang.reflect, and java.lang.ref packages. When you finish this chapter, you'll be able to write your own console programs.
In previous chapters you learned to use Java to write applets and window applications. You learned how to open windows, display GUI components, and handle events associated with those components. You don't need to do any of these things in console programs. There are no windows or GUI components, and in simple programs, the only event that you usually need to handle is the entering of keyboard data by the user.
Console programs are not limited to user interaction. They just minimize its complexities. Advanced console programs access databases via JDBC, use TCP and UDP sockets for network communication, and interface with distributed systems using RMI, CORBA, and DCOM. The primary difference between console programs and window applications and applets is that console programs lack a graphical user interface.
Console programs have the same entry point as window applications--a main() method with a String[] argument. Like window applications, the following program template is shared by all console programs:
class programClass { public static void main(String[] args) { . . . } }
Console programs start by reading the args array to see which parameters were passed to the program at the command line. After reading these parameters and performing any necessary initialization, the program then responds to input entered from the keyboard or received from a file, socket, remote method invocation, or other input source.
The most basic I/O performed by a console program is reading data entered at the user's keyboard and writing data to the user's console. We'll cover keyboard input and console output in this chapter. Later chapters of this book will show how to perform I/O via files, TCP/IP socket programming, JDBC, remote method invocation, CORBA, and DCOM.
The System class of the java.lang package provides all that you need for your programs to communicate with the user via the keyboard and console. The System.in variable provides access to the standard input stream, which defaults to the keyboard. The System.out variable provides access to the standard output stream, which is the console by default. The System.err variable provides access to the standard error stream, which is also usually the user's console. These three variables refer to stream objects of the java.io package. Each of these streams can be redirected to other inputs and outputs, such as file I/O and socket I/O.
NOTE: Chapter 17, "Input/Output Streams," covers stream-based input and output in great detail. This chapter provides you with just enough information on stream I/O to allow you to write console programs.
The System.in variable refers to an object of the InputStream class. This class provides basic methods for reading data, but is usually used to construct more powerful input classes, such as the BufferedReader class. The BufferedReader class provides the readLine() method for reading an entire line at a time from a stream. It returns the value null if the end of a stream has been encountered.
The following lines of code illustrate the use of these classes and methods:
BufferedReader keyboardInput; keyboardInput = new BufferedReader(new InputStreamReader(System.in)); String newLine; while(((newLine = keyboardInput.readLine())!=null)) { // Process each input line }
The first line declares the keyboardInput variable to be of the BufferedReader class. The second line uses the InputStream object referenced by System.in to construct a InputStreamReader object. The InputStreamReader object is then used to construct an object of the BufferedReader class. This object is assigned to the keyboardInput variable.
The while statement reads a line of input at a time by invoking the readLine() method of the BufferedReader object assigned to the keyboardInput variable. It then assigns this input (as a String object) to the newLine variable. The while statement checks to see if the input is null to determine whether the end of the input stream was encountered. If not, it processes the new line within the body of the while loop. In practice, readLine() will not return a null value when reading from the keyboard. Instead, it blocks (waits) until the user enters a line of input. However, it is good practice to test for end of input just in case the user redirects a file as input in place of the keyboard.
The System.out and System.err variables refer to objects of the PrintStream class that are directed to the user's console. The PrintStream class provides the print() and println() methods for printing data to an output stream.
In this section you'll learn to write a console program that illustrates keyboard input and console output. The BlackJackApp program is a simplified, character-based version of the popular blackjack card game. This example, while entertaining, also illustrates the basics of console application programming. The source code for this program is shown in Listing 10.1.
// BlackJackApp.java // Import all the Java API classes needed by this program. import java.lang.System; import java.lang.Integer; import java.lang.NumberFormatException; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; import java.util.Random; class BlackJackApp { public static void main (String args[]) throws IOException { // Create a BlackJackGame object ... BlackJackGame game = new BlackJackGame(); // and play it! game.play(); } } class BlackJackGame { // Variable declarations int bet; int money; Deck deck; Hand playersHand; Hand dealersHand;
BufferedReader keyboardInput;
// Method declarations public BlackJackGame() { // Constructor bet = 0; money = 1000; deck = new Deck(); keyboardInput = new BufferedReader(new InputStreamReader(System.in)); } void play() throws IOException { System.out.println("Welcome to Blackjack!"); System.out.println("You have $"+Integer.toString(money)+"."); do { placeBet(); if(bet>0) { initialDeal(); if(playersHand.blackjack()) playerWins(); else{ while(playersHand.under(22) && playerTakesAHit()) { playersHand.addCard(deck.deal()); playersHand.show(false,false); } while(dealersHand.mustHit()) dealersHand.addCard(deck.deal()); dealersHand.show(true,false); showResults(); } } } while (bet>0); } void placeBet() throws IOException, NumberFormatException { do{ System.out.print("Enter bet: "); System.out.flush(); bet = Integer.parseInt(keyboardInput.readLine()); } while(bet<0 || bet>money); } void initialDeal() { System.out.println("New hand..."); playersHand = new Hand(); dealersHand = new Hand(); for(int i = 0;i<2;++i) { playersHand.addCard(deck.deal()); dealersHand.addCard(deck.deal()); } dealersHand.show(true,true); playersHand.show(false,false); } void playerWins() { money += bet; System.out.println("Player wins $"+Integer.toString(bet)+"."); System.out.println("Player has $"+Integer.toString(money)+"."); } void dealerWins() { money -= bet; System.out.println("Player loses $"+Integer.toString(bet)+"."); System.out.println("Player has $"+Integer.toString(money)+"."); } void tie() { System.out.println("Tie."); System.out.println("Player has $"+Integer.toString(money)+"."); } boolean playerTakesAHit() throws IOException { char ch = ` `; do{ System.out.print("Hit or Stay: "); System.out.flush(); String playersDecision = keyboardInput.readLine(); try{ ch = playersDecision.charAt(0); }catch (StringIndexOutOfBoundsException exception){ } if(ch == `H' || ch == `h') return true; if(ch == `S' || ch == `s') return false; } while(true); } void showResults() { if(playersHand.busted() && dealersHand.busted()) tie(); else if(playersHand.busted()) dealerWins(); else if(dealersHand.busted()) playerWins(); else if(playersHand.bestScore() > dealersHand.bestScore()) playerWins(); else if(playersHand.bestScore() < dealersHand.bestScore()) dealerWins(); else tie(); } } // End of BlackJackGame class class Deck { // Variable declarations int cards[]; // Array of 52 cards int topCard; // 0-51 (index of card in deck) Random random; // Method declarations public Deck() { // Constructor cards = new int[52]; for(int i = 0;i<52;++i) cards[i] = i; topCard = 0; random = new Random(); shuffle(); } public void shuffle() { // Repeat 52 times for(int i = 0;i<52;++i) { // Randomly exchange two cards in the deck. int j = randomCard(); int k = randomCard(); int temp = cards[j]; cards[j] = cards[k]; cards[k] = temp; } } int randomCard() { int r = random.nextInt(); if(r<0) r = 0-r; return r%52; } Card deal() { if(topCard>51) { shuffle(); topCard = 0; } Card card = new Card(cards[topCard]); ++topCard; return card; } } // End of Deck class class Hand { // Variable declarations int numCards; Card cards[]; static int MaxCards = 12; //Method declarations public Hand() { // Constructor numCards = 0; cards = new Card[MaxCards]; } void addCard(Card c) { cards[numCards] = c; ++numCards; } void show(boolean isDealer,boolean hideFirstCard) { if(isDealer) System.out.println("Dealer:"); else System.out.println("Player:"); for(int i = 0;i<numCards;++i) { if(i == 0 && hideFirstCard) System.out.println(" Hidden"); else System.out.println(" "+cards[i].value+" of "+cards[i].suit); } } boolean blackjack() { if(numCards == 2) { if(cards[0].iValue == 1 && cards[1].iValue == 10) return true; if(cards[1].iValue == 1 && cards[0].iValue == 10) return true; } return false; } boolean under(int n) { int points = 0; for(int i = 0;i<numCards;++i) points += cards[i].iValue; if(points<n) return true; else return false; } int bestScore() { int points = 0; boolean haveAce = false; for(int i = 0;i<numCards;++i) { points += cards[i].iValue; if(cards[i].iValue == 1) haveAce = true; } if(haveAce) { if(points+10 < 22) points += 10; } return points; } boolean mustHit() { if(bestScore()<17) return true; else return false; } boolean busted() { if(!under(22)) return true; else return false; } } // End of Hand class class Card { // Variable declarations int iValue; // Numeric value corresponding to card. String value; // "A" "2" through "9" "T" "J" "Q" "K" String suit; // "S" "H" "C" "D" // Method declarations public Card(int n) { // Constructor int iSuit = n/13; iValue = n%13+1; switch(iSuit) { case 0: suit = "Spades"; break; case 1: suit = "Hearts"; break; case 2: suit = "Clubs"; break; default: suit = "Diamonds"; } if(iValue == 1) value = "Ace"; else if(iValue == 10) value = "Ten"; else if(iValue == 11) value = "Jack"; else if(iValue == 12) value = "Queen"; else if(iValue == 13) value = "King"; else value = Integer.toString(iValue); if(iValue>10) iValue = 10; } int getValue() { return iValue; } } // End of Card class
When you run BlackJackApp, it produces the following output:
Welcome to Blackjack! You have $1000. Enter bet:
The BlackJackApp program will provide you with $1,000 with which to play blackjack. You can place a bet between 0 and the amount of money you have. The program, acting as dealer, will deal two cards to you and two to itself. For example, after I entered a bet of $10, I received the following program output:
Welcome to Blackjack! You have $1000. Enter bet: 10 New hand... Dealer: Hidden 2 of Hearts Player: Queen of Clubs 3 of Spades Hit or Stay:
I was dealt a queen of clubs and a three of spades. This gave me a total of 13 points. Points are calculated as follows:
Card point | Value |
Ace | 1 or 11 (whichever is better) |
2 through 10 | Face value of card (that is, 2 through 10) |
Jack, Queen, King | 10 |
The objective of the game is to get as close to 21 as you can without going over. Whoever gets the closest wins. If you go over 21 you lose, unless the dealer does also, in which case you tie.
When you are dealt your initial two cards, you are shown one of the dealer's cards. This helps you determine whether you should take another card, referred to as hitting, or stay with the cards that you have. You can enter h or s to inform the dealer of your decision. If you enter h, you will be dealt another card. If you enter s, the dealer will begin to play its hand.
[begNOTE: If the point total of your first two cards is 21, you have blackjack and immediately win.
The dealer must take a hit until the total points in its hand is 17 or over, at which point it must stay. When both you and the dealer have finished playing your hand, the total number of points acquired by each is used to determine the winner. Play is repeated until you enter a $0 bet.
The following program output shows a game played between myself and the BlackJackApp program:
Welcome to Blackjack! You have $1000. Enter bet: 10 New hand... Dealer: Hidden 2 of Hearts Player: Queen of Clubs 3 of Spades Hit or Stay: h Player: Queen of Clubs 3 of Spades 7 of Spades Hit or Stay: s
Dealer:
Queen of Spades 2 of Hearts 5 of Spades Player wins $10. Player has $1010. Enter bet: 20 New hand... Dealer: Hidden 7 of Clubs Player: King of Clubs 9 of Spades Hit or Stay: s Dealer: 2 of Clubs 7 of Clubs 9 of Clubs Player wins $20. Player has $1030. Enter bet: 0
On the initial deal, I bet 10 bucks. I was given a queen of clubs and a three of spades, for a total of 13 points. The dealer was given a two of hearts and another (hidden) card. I elected to take a hit and was dealt a seven of spades, bringing the total in my hand up to 20 points--beginner's luck! The dealer turned over the hidden card to reveal a queen of spades. He then drew a five of spades for a total of 17 points. Because the dealer reached 17, he was forced to stay, and I had won $10. Feeling a little lightheaded, I proceeded to double my bet to $20. I was dealt a king of clubs and a nine of spades for a total of 19 points. I decided to stay with that hand. The dealer's hand was revealed to be a two of clubs and a seven of clubs. The dealer drew a nine of clubs for a total of 18 points. I had won again! At that point I elected to take the money and continue writing this book. I entered a 0 bet to end the game.
The point this is not to turn you into a blackjack gambler, but to serve as a more interesting example of console programming.
The BlackJackApp.java file is long, but don't let that daunt you. I'm going to break it down, class by class and method by method, to explain its operation.
The program begins by declaring the BlackJackApp class, the class that implements the blackjack application. The main() method consists of two Java statements. The first declares the game variable as having class type BlackJackGame and assigns it a new object of class BlackJackGame. The second statement invokes the play() method of the object referenced by game. Because BlackJackApp does not require any command-line arguments, the args array is not processed.
The BlackJackGame class is rather long. It declares six variables and nine methods. The variables are data structures that represent the state of a blackjack game. The bet variable identifies the amount bet by the player. The money variable identifies how much money the player has left. The deck variable references an object of class Deck that is used to represent a deck of cards. Two Hand variables are declared, representing the player's hand and the dealer's hand. The keyboardInput variable refers to a BufferedReader object that is used to read data entered at the user's keyboard.
The BlackJackGame() constructor initializes four of the six variables of the BlackJackGame class. The player's bet is set to 0, and the player is given $1000. The playersHand and dealersHand variables are not initialized until the cards are dealt. A new Deck object is created and assigned to the deck variable. Finally, the keyboardInput variable is assigned a new object of class BufferedReader. This object is created using the BufferedReader() and InputStreamReader() constructors with the System.in variable as an argument.
The second method defined for BlackJackGame is the play() method. This method is invoked in the main() method of BlackJackApp to cause the BlackJackGame object, referenced by game, to be played.
The play() method begins by displaying the Welcome to Blackjack! text and the amount of money available to the player. The second println() method takes three arguments. First it displays You have $, then it displays the contents of the money variable, and then it displays a period (.). It converts the integer value of money to a String value before printing it. The block of statements within the do statement prompts the player to bet and then play a hand of blackjack.
If bet is greater than 0, the initialDeal() method is invoked. This method is used to deal a new hand to the player and to the dealer. It causes the playersHand and dealersHand variables to each be initialized with an object of class Hand. The blackjack() method is used to check whether the player was dealt a blackjack (21 points). If so, the player wins the bet and the playerWins() method is invoked.
If the player was not fortunate enough to have a blackjack, a while statement checks to see if the player has 21 points or less in his hand and whether he wants to take another card. The playerTakesAHit() method is invoked to prompt the player to hit or stay. The statements enclosed within the first while statement invoke methods for the Hand object referenced by the playersHand variable. The first method causes a card to be added to the player's hand by dealing it from the deck. The second method determines if and how the player's hand should be displayed.
The second while statement is used to play the dealer's hand. It invokes the mustHit() method with the object referenced by the dealersHand variable to determine whether the dealer has fewer than 17 points in his hand and, therefore, must take a hit. If the dealer must take a hit, the addCard() method is invoked to deal a card to the dealer.
After the dealer's hand is played, the show() method is invoked to display it to the console. The showResults() method is then invoked to show the results of the hand.
The placeBet() method is invoked by the play() method to prompt the player to enter a bet. It uses a do statement to repeatedly prompt the user to enter a bet between 0 and the amount of money that he has left. The statement block enclosed by the do statement displays the prompt, reads the line entered by the user, converts it to an integer, and then assigns it to the bet variable.
The initialDeal() method is invoked by the play() method to deal a new hand to the player and the dealer. It displays the New hand... text to the console window to inform the player that a new hand is being dealt. It then creates two new objects of class Hand, initializes them with the Hand() constructor, and assigns them to the playersHand and dealersHand variables.
After creating the two new hands, the initialDeal() method executes a for statement to sequentially deal two cards to the player and two to the dealer via the addCard() method. After the player and dealer have been dealt their hands, the show() method is invoked to display the new hands. (You'll find out what the boolean values are used for when you study the show() method.)
The next three methods, playerWins(), dealerWins(), and tie(), are used to update the money variable based on the bet variable and the outcome of the hand. These methods also display the results to the player by converting the values of bet and money to String objects.
The playerTakesAHit() method prompts the user to hit or stay. The flush() method is used to flush all output to the console in the absence of a new line character. The readLine() method is used to read the line entered by the user.
The showResults() method is the last method declared for the BlackJackGame class. This method uses a series of nested if statements. The first if statement checks to see if the player's hand and the dealer's hand are both busted (over 21 points). If so, the tie() method is invoked to display the results to the player.
The second if statement checks to see if the player's hand is busted. Because the else part of the first if statement was executed, it is impossible for both the player and the dealer to be busted. So if the player is busted, the dealer wins.
The third if statement is executed in the else parts of the first and second if statements. It uses the same logic as the second if statement to determine whether the dealer busts and the player wins.
The fourth if statement is only executed if neither the player nor the dealer busts. It checks the points in both of their hands to see if the player is higher than the dealer and, therefore, is the victor.
The fifth if statement is only executed if neither busts and the player is not higher than the dealer. If the dealer is higher than the player, the dealer wins. If the dealer is not higher than the player, the final else part is executed. At this point, neither has busted but neither is higher than the other, so both must have the same number of points and a tie is declared.
The Deck class declares three variables and four methods. The cards variable is used to simulate a deck of cards. The topCard variable is an integer that identifies the next card to be dealt from the deck. The random variable is used to generate random numbers.
The constructor for the Deck class allocates an array of 52 integers and assigns it to cards. A for statement is used to assign 0 to cards[0], 1 to cards[1], 2 to cards[2], and so on, until 51 is assigned to cards[51]. This creates a deck of cards in which all the cards are ordered by suit and by value. The integers 0 through 51 are logically mapped to playing cards, as follows:
0 through 12 are mapped to the ace of spades through the king of spades.
13 through 25 are mapped to the ace of hearts through the king of hearts.
26 through 38 are mapped to the ace of clubs through the king of clubs.
39 through 51 are mapped to the ace of diamonds through the king of diamonds.
The topCard of the deck is set to 0. It is used as an index into the cards array. The random variable is assigned a new object of class Random. Finally, the shuffle() method is invoked to shuffle the new deck of cards.
The shuffle() method shuffles the deck of cards by randomly switching two cards in the deck 52 times. It does this by invoking the randomCard() method to generate a random integer between 0 and 51.
The randomCard() method returns an integer between 0 and 51, inclusive. It begins by declaring a variable r and assigning it a random integer value generated by applying the nextInt() method to the random variable. The nextInt() method is defined in the java.util.Random class. If the value assigned to r is less than 0, it is changed to a positive integer. The randomCard() method then returns an integer between 0 and 51 by returning the random integer modulus 52.
The deal() method is used to deal a card off the top of the deck. It does this by using the topCard variable as an index into the cards array. It starts at 0 and is incremented until it is greater than 51, indicating that all the cards in the deck have been dealt. In this case, the deck is reshuffled and topCard is set to 0 once again. This creates the effect of another deck being used because the player and dealer don't have to throw back any cards they're holding before the deck is shuffled.
The Card class is used to translate the integer card values to String values that can be displayed on the console. A card is dealt by constructing a new instance of Card, using the value of cards indexed by topCard as an argument. The topCard is then incremented to move to the next card in the deck. Note that deal() returns the object of class Card that was created using the Card() constructor.
The Hand class is used to implement a hand of cards as played by both the player and the dealer. It declares three variables and eight methods.
The numCards variable identifies the number of cards contained in the hand. The cards array has the same name as the cards array declared in the Deck class, but is logically and physically distinct. Because it is declared in a separate class, it is contained in objects that are instances of the Hand class and not of the Deck class. The MaxCards variable is declared to be static. It is used to identify the number of components to be allocated within cards.
The constructor for the Hand class sets numCards to 0 to indicate an empty hand, and then creates a MaxCards size array of Card objects and assigns it to cards.
Cards are added to a hand using the addCard() method. This method takes an object of class Card as an argument and adds it to the first available position within the cards array. It then increments numCards so that it will index the next available position within cards.
The show() method displays either the dealer's or the player's hand. It takes two boolean arguments that specify whether the hand belongs to the dealer, and if so, whether the first card should be hidden when the hand is displayed. The isDealer parameter is used in the initial if statement to determine whether a dealer or a player heading should be displayed. A for statement is then used to iterate numCards times in order to display each card of the hand. The statement block enclosed by the for statement uses the hideFirstCard parameter to determine whether the first card should be hidden or displayed.
The blackjack() method returns a boolean value indicating whether the hand is blackjack. If the number of cards is exactly two, it uses the iValue variable of the Card objects contained in the cards array to determine whether the current hand is blackjack. The iValue variable is discussed with the Card class. It identifies the number of points associated with a card. A card with iValue = 1 is an ace. Aces can be either 1 or 11 points.
The under() method returns a boolean value indicating whether the number of points in a hand is less than the argument passed via the n parameter. It declares a points variable of type int and uses a for statement to sum the points for all cards in the hand. It then checks to see if the number of points in the hand is less than n and returns an appropriate value of true or false.
The bestScore() method returns an integer value identifying the best possible point score for the hand. It adjusts the value associated with aces to either 1 or 11, depending upon whether it causes the hand to go over 21 points.
The mustHit() method is used to play out the dealer's hand. If the bestScore of the dealer's hand is lower than 17, the dealer must take a hit. If it is 17 or higher, the dealer must stay.
The busted() method determines whether the number of points in a hand is under 22.
The Card Class
The Card class is used to translate the integer value of cards, maintained by objects of the Deck class, into objects of type String. It declares three variables and two methods.
The iValue variable is used to keep track of the number of points associated with a card. It is an abbreviation for integer value and is used to differentiate it from the value variable, which references a text string that is used to describe the face value of a playing card. The suit variable is used to identify the suit of a playing card.
The Card() constructor takes an integer argument (0 through 51) that is a card value from the Deck class. Card() first determines the suit of the card identified by the n parameter. It does this by dividing n by 13 and assigning the result to an integer variable named iSuit. It determines the point value of the card by calculating n modulus 13 and adding 1. It adjusts this value later in the method. Card() then uses a switch statement to assign the correct text string to the suit variable.
The getValue() method is used to return the value of iValue, the point value of the card.
In the BlackJackApp program, you learned to use the System class of java.lang to perform keyboard input and console output. The java.lang package is one of the most important packages of the Core Java API. It provides a number of classes and interfaces that are fundamental to Java programming. This section covers the classes and interfaces of the java.lang, java.lang.reflect, and java.lang.ref packages. It contains several console programs that illustrate the use of these classes and interfaces.
Object and Class are two of the most important classes in the Java API. The Object class is at the top of the Java class hierarchy. All classes are subclasses of Object and therefore inherit its methods. The Class class is used to provide class descriptors for all objects created during Java program execution. The Package class is new to JDK 1.2. It is used to provide version information about a package.
The Object class does not have any variables and has only one constructor. However, it provides 11 methods that are inherited by all Java classes and support general operations used by all objects. For example, the equals() and hashCode()methods are used to construct hash tables of Java objects. Hash tables are like arrays, but are indexed by key values and dynamically grow in size. They make use of hash functions to quickly access the data that they contain. The hashCode() method creates a hashcode for an object. Hashcodes are used to quickly determine whether two objects are different.
The clone() method creates an identical copy of an object. The object must implement the Cloneable interface. This interface is defined within the java.lang package. It contains no methods and is used only to differentiate clonable classes from nonclonable classes.
The getClass()method identifies the class of an object by returning an object of Class. You'll learn how to use this method in the next programming example. (See the "A Touch of Class" section.)
The toString() method creates a String representation of the value of an object. This method is handy for quickly displaying the contents of an object. When an object is displayed, using print() or println(), the toString() method of its class is automatically called to convert the object into a string before printing. Classes that override the toString() method can easily provide a custom display for their objects.
The finalize() method of an object is executed when an object is garbage-collected. The method performs no action, by default, and needs to be overridden by any class that requires specialized finalization processing.
The Object class provides three wait() and two notify() methods that support thread control. These methods are implemented by the Object class so that they can be made available to threads that are not created from subclasses of class Thread. The wait() methods cause a thread to wait until it is notified or until a specified amount of time has elapsed. The notify() methods are used to notify waiting threads that their wait is over.
The Class class provides over 30 methods that support the runtime processing of an object's class and interface information. This class does not have a constructor. Objects of this class, referred to as class descriptors, are automatically created and associated with the objects to which they refer. Despite their name, class descriptors are used for interfaces as well as classes.
The getName()and toString()methods return the String containing the name of a class or interface. The toString() method differs in that it prepends the string class or interface, depending on whether the class descriptor is a class or an interface. The static forName() method loads the class specified by a String object and returns a class descriptor for that class.
The getSuperclass() method returns the class descriptor of a class's superclass. The isInterface()method identifies whether a class descriptor applies to a class or an interface. The getInterfaces() method returns an array of Class objects that specify the interfaces of a class, if any.
The newInstance()method creates an object that is a new instance of the specified class. It can be used in lieu of a class's constructor, although it is generally safer and clearer to use a constructor rather than newInstance().
The getClassLoader()method returns the class loader of a class, if one exists. Classes are not usually loaded by a class loader. However, if a class is loaded from outside the CLASSPATH, such as over a network, a class loader is used to convert the class byte stream into a class descriptor. The ClassLoader class is covered later in this chapter in the "ClassLoader" section.
The Class class contains a number of other methods that begin with get and is. These methods are as follows:
Java software development is based upon the use and reuse of packages. Both Java 1.0 and Java 1.1 used packages. However, the Package class is new to JDK 1.2. It provides methods for obtaining package version information stored in the manifest of .jar files. The Package class provides fourteen methods that can be used to retrieve information about packages. The static getPackage() and getAllPackages() methods provide Package objects that are known to the current class loader. The getName(), getSpecificationTitle(), getImplementationTitle(), getSpecificationVersion(), getImplementationVersion(), getSpecificationVendor(), and getImplementationVendor() methods return name, title, version, and vendor information about the specification and implementation of packages. The getSealBase() method returns the base URL of a signed package. The isSealed() method is used to determine if a package is sealed. The isCompatibleWith() method is used to determine whether a package is comparable with a particular version. The hashCode() and toString() methods override those inherited from the Object class.
In order to give you a feel for how the Class methods can be used, let's create and run a small program called ClassApp. The program's source code is shown in Listing 10.2.
import java.util.*; public class ClassApp { public static void main(String args[]) { Vector v = new Vector(); Class cl = v.getClass(); do { describeClass(cl); cl = cl.getSuperclass(); }while(!cl.getName().equals("java.lang.Object")); } public static void describeClass(Class classDesc){ System.out.println("Class: "+classDesc.getName()); System.out.println("Superclass: "+classDesc.getSuperclass().getName()); Class interfaces[] = classDesc.getInterfaces(); for(int i=0;i<interfaces.length;++i) System.out.println("has interface: "+interfaces[i].getName()); System.out.println(); } }
The program shows how the Class methods can be used to generate runtime class and interface information about an arbitrary object. It creates an instance of the Vector class of the java.util package and then uses the getClass(), getSuperclass(), and getName() methods of the Class class.
A do loop invokes the describeClass() method for the class identified by cl and then assigns cl to the class's superclass. The loop repeats until cl becomes the class descriptor of the Object class.
The describeClass() method uses the getName() method to get the name of the class and its superclass. The describeClass() method displays this information to the console. It uses the getInterfaces() method to get all interfaces implemented by a class and the getName() method to get and display the name of each interface.
Compile and run the ClassApp program. Its output is as follows:
Class: java.util.Vector Superclass: java.util.AbstractList has interface: java.util.List has interface: java.lang.Cloneable has interface: java.io.Serializable Class: java.util.AbstractList Superclass: java.util.AbstractCollection has interface: java.util.List Class: java.util.AbstractCollection Superclass: java.lang.Object has interface: java.util.Collection
It steps up the class hierarchy from Vector to CGObject to display information about each class. See if you can modify the program to work with objects of other classes. You can do this by assigning the class of these objects to the cl variable in the main() method.
The ClassLoader, SecurityManager, and Runtime classes provide a fine level of control over the operation of the Java runtime system. However, most of the time you will not want or need to exercise this control because Java is set up to perform optimally for a variety of applications. The ClassLoader class allows you to define custom loaders for classes that you load outside of your CLASSPATH--for example, over a network. The SecurityManager class allows you to define a variety of security policies that govern the accesses that classes may make to threads, executable programs, your network, and your file system. The Runtime class provides you with the capability to control and monitor the Java runtime system. It also allows you to execute external programs.
Classes that are loaded from outside the CLASSPATH require a class loader to convert the class byte stream into a class descriptor. ClassLoader is an abstract class that is used to define class loaders. It uses the defineClass() method to convert an array of bytes into a class descriptor. The definePackage() method is used to define a package. The loadClass() method is used to load a class from its source, usually a network. The resolveClass() method resolves all the classes referenced by a particular class by loading and defining those classes. The findSystemClass()method is used to load classes that are located within the CLASSPATH and, therefore, do not require a class loader. The findLoadedClass() method is used to access a class that has been loaded. The findLocalClass() method is used to find a class that is on the local system.
The checkPackageAccess() method is used to determine whether the invoking object's thread is permitted to access a particular package. The static currentClassLoader() method returns a reference to the ClassLoader that is currently in use. getParent() returns a class loader's parent.
The getResource(), getResourceAsStream(), getSystemResource(), and getSystemResourceAsStream() methods are used to access application- or system-specific resources. Resources are additional files or other objects that are associated with an application or the runtime system. The getPackage() and getPackages() methods are used to access packages.
The setSigners() method is used to set the signers of a loaded class.
The SecurityManager class is an abstract class that works with class loaders to implement a security policy. It contains several methods that can be overridden to implement customized security policies. These methods are of the form checkX(), where X is the access being checked. You can extend SecurityManager and override these methods to implement custom security policies. As of JDK 1.2, it is preferable to configure security policy using the security policy configuration methods described in Chapter 3, "The Extended Java Security Model."
The Runtime class provides access to the Java runtime system. It consists of a number of methods that implement system-level services.
The getRuntime() method is a static method that is used to obtain access to an object of class Runtime. The exec() methods are used to execute external programs from the Java runtime system. The exec() methods provide a number of alternatives for passing parameters to the executed program. These alternatives are similar to the standard C methods for passing command-line and environment information. The exec() methods are subject to security checking to ensure that they are executed by trusted code. The RuntimePermission class is used to implement permissions related to runtime security checking.
The exit() method is used to exit the Java runtime system with an error code. It is similar to the exit function found in standard C libraries.
The totalMemory(), freeMemory(), and gc() methods are used to obtain information about the runtime system and control the memory used by it. The totalMemory() method identifies the total memory available to the runtime system. The freeMemory() method identifies the amount of free (unused) memory. The RuntimeMemoryAdvice interface is new to JDK 1.2. It provides constants for determining the safety level associated with available memory. RuntimeMemoryAdvice is implemented by the Runtime class. The waitForMemoryAdvice() and getMemoryAdvice() methods are used to determine the current memory safety level.
The gc() method is used to run the garbage collector to free up memory allocated to objects that are no longer being used. In general, you should not use the gc() method, but rather let Java perform its own automated garbage collection.
The getLocalizedInputStream() and getLocalizedOutputStream() methods are used to convert local (usually ASCII) input and output streams to Unicode-based streams.
The load()and loadLibrary() methods are used to load dynamic link libraries. This is usually performed in conjunction with native methods, which are described in Chapter 53, "Native Methods."
The runFinalization() method causes the finalize() method of each object awaiting finalization to be invoked. The runFinalizersOnExit() method can toggle on or off whether finalization occurs when the runtime system exits. The traceInstructions() and traceMethodCalls() methods are used to enable or disable instruction and method tracing. You will most likely never need to use any of these methods in your programs. They are used in programs such as the debugger to trace through the execution of Java methods and instructions.
Using Runtime
Most of the methods provided by Runtime are not typically used in application programs. However, some methods are pretty useful. The program in Listing 10.3 shows how the Runtime methods can be used to display memory status information.
import java.lang.System; import java.lang.Runtime; import java.io.IOException; public class RuntimeMemApp { public static void main(String args[]) throws IOException { Runtime r = Runtime.getRuntime(); System.out.println(r.totalMemory()); System.out.println(r.freeMemory()); } }
This program uses the static getRuntime() method to get an instance of Runtime that represents the current Java runtime system. The totalMemory() method is used to display the total number of bytes of runtime system memory. The freeMemory() method is used to display the number of bytes of memory that are unallocated and currently available.
When you run the program, you should get results that are similar to the following:
1048568 845136
Listing 10.4 demonstrates how to use the Runtime exec() method to execute external programs. This example assumes that you are using Windows 95 and may not work with other Java implementations. However, it can be easily tailored to launch application programs on other operating systems.
import java.lang.System; import java.lang.Runtime; import java.io.IOException; public class RuntimeExecApp { public static void main(String args[]) throws IOException { Runtime r = Runtime.getRuntime(); r.exec("C:\\Windows\\Explorer.exe"); } }
This program uses getRuntime() to get the current instance of the runtime system and then uses exec() to execute the Windows Explorer. The double backslashes (\\) are Java escape codes for a single backslash (\). When you run this program, it should launch a copy of the Windows Explorer. Under Windows 95, the exec() function works with true Win32 programs. It cannot be used to execute built-in DOS commands.
You are no stranger to the System class because you have used it in several previous programming examples. It is one of the most important and useful classes provided by java.lang. It provides a standard interface to common system resources and functions. It implements the standard input, output, and error streams, and supplies a set of methods that provide control over the Java runtime system. Some of these methods duplicate those provided by the Runtime class.
The in, out, and err variables are, by default, assigned to the standard input, output, and error streams. The setIn(), setOut(), and setErr() methods can be used to reassign these variables to other streams.
The System class provides several properties-related methods. Properties are extensions of the Dictionary and Hashtable classes and are defined in the java.util package. A set of system properties is available through the System class that describes the general characteristics of the operating system and runtime system you are using. The getProperties() method gets all of the system properties and stores them in an object of class Properties. The getProperty() method gets a single property, as specified by a key. The setProperties() method sets the system properties to the values of a Properties object. The setProperty() method sets the value of a particular property. The identityHashCode() method returns the hash code associated with an object. The sample program presented in Listing 10.4 introduces you to these system properties.
The getSecurityManager() and setSecurityManager() methods provide access to the security manager that is currently in effect. The setSecurityManager() method can be used to implement a custom security policy. However, as of JDK 1.2, the best way to implement a custom policy is via the policy permissions covered in Chapter 3, "The Extended Java Security Model."
Several of the methods defined for the Runtime class are made available through the System class. These methods include exit(), gc(), load(), loadLibrary(), runFinalizersOnExit(), and runFinalization().
The arraycopy() method is used to copy data from one array to another. This function provides the opportunity for system-specific memory-copying operations to optimize memory-to-memory copies.
The currentTimeMillis() method returns the current time in milliseconds since January 1, 1970. If you want more capable date and time methods, check out the Date class in java.util.
The getenv() method is used to obtain the value of an environment variable. However, this method is identified as obsolete in the Java API documentation and can no longer be used.
The short program in Listing 10.5 illustrates a few of the methods provided by the System class. If your heyday was in the 1960s, it will allow you to keep track of the number of milliseconds that have elapsed since the good old days. It also gets and displays the System properties. Take a look through these properties to get a feel for the type of information that is provided. Finally, the exit() method is used to terminate the program, returning a status code of 13.
import java.lang.System; import java.util.Properties; public class SystemApp { public static void main(String args[]) { long time = System.currentTimeMillis(); System.out.print("Milliseconds elapsed since January 1, 1970: "); System.out.println(time); Properties p=System.getProperties(); p.list(System.out); System.exit(13); } }
The program generated the following output on my computer:
Milliseconds elapsed since January 1, 1970: 887133030120 -- listing properties -- java.specification.name=Java Platform API Specification awt.toolkit=sun.awt.windows.WToolkit java.version=1.2beta2 java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment java.tmpdir=c:\windows\TEMP\ user.timezone=PST java.specification.version=1.2beta2 user.home=C:\JDK1.2BETA2\BIN\.. java-vm.name=non-JIT os.arch=x86 java.awt.fonts=C:\WINDOWS\Fonts java.vendor.url=http://www.sun.com/ user.region=US file.encoding.pkg=sun.io java.home=C:\JDK1.2BETA2\BIN\.. java-vm.specification.vendor=Sun Microsystems Inc. java-vm.specification.version=1.0 java.class.path=.;C:\JavaWebServer1.0.3\public_html\c... line.separator= os.name=Windows 95 java.vendor=Sun Microsystems Inc. java.library.path=C:\JDK1.2BETA2\BIN;.;C:\WINDOWS;c:\wi... java-vm.version=1.2beta2 file.encoding=8859_1 java.specification.vendor=Sun Microsystems Inc. user.name=Jamie user.language=en java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport... java.class.version=45.3 os.version=4.0 path.separator=; java-vm.specification.name=Java Virtual Machine Specification file.separator=\ user.dir=C:\jdk1.2beta2\ju\ch10 java-vm.vendor=Sun Microsystems Inc.
Variables that are declared using the primitive Java types are not objects and cannot be created and accessed using methods. Primitive types also cannot be subclassed. To get around the limitations of primitive types, the java.lang package defines class wrappers for these types. These class wrappers furnish methods that provide basic capabilities such as class conversion, value testing, hash codes, and equality checks. The constructors for the wrapped classes allow objects to be created and converted from primitive values and strings. Be sure to browse the API pages for each of these classes to familiarize yourself with the methods they provide.
The Boolean class is a wrapper for the boolean primitive type. It provides the getBoolean(), toString(), valueOf(), and booleanValue() methods to support type and class conversion. The toString(), equals(), and hashCode() methods override those of class Object.
The Character class is a wrapper for the char primitive type. It provides several methods that support case, type, and class testing and conversion. Check out the API pages on these methods. We'll use some of them in the upcoming example.
These classes wrap the byte, short, int, and long primitive types. They provide the MIN_VALUE and MAX_VALUE constants, as well as a number of type and class testing and conversion methods. The parseInt() and parseLong() methods are used to parse String objects and convert them to Byte, Short, Integer, and Long objects.
The Double and Float classes wrap the double and float primitive types. They provide the MIN_VALUE, MAX_VALUE, POSITIVE_INFINITY, and NEGATIVE_INFINITY constants, as well as the NaN (not-a-number) constant. NaN is used as a value that is not equal to any value, including itself. These classes provide a number of type and class testing and conversion methods, including methods that support conversion to and from integer bit representations.
The Number class is an abstract numeric class that is subclassed by Byte, Short, Integer, Long, Float, and Double. It provides six methods that support conversion of objects from one class to another.
The program in Listing 10.6 shows some of the methods that can be used with the primitive types when they are wrapped as objects. Look up these methods in the API pages for each class and try to figure out how they work before moving on to their explanations.
import java.lang.System; import java.lang.Boolean; import java.lang.Character; import java.lang.Integer; import java.lang.Long; import java.lang.Float; import java.lang.Double; public class WrappedClassApp { public static void main(String args[]) { Boolean b1 = new Boolean("TRUE"); Boolean b2 = new Boolean("FALSE"); System.out.println(b1.toString()+" or "+b2.toString()); for(int j=0;j<16;++j) System.out.print(Character.forDigit(j,16)); System.out.println(); Integer i = new Integer(Integer.parseInt("ef",16)); Long l = new Long(Long.parseLong("abcd",16)); long m=l.longValue()*i.longValue(); System.out.println(Long.toString(m,8)); System.out.println(Float.MIN_VALUE); System.out.println(Double.MAX_VALUE); } }
The program examines some of the more useful methods provided by the wrapped classes. It creates two objects of class Boolean from string arguments passed to their constructors. It assigns these objects to b1 and b2 and then converts them back to String objects when it displays them. They are displayed in lowercase, as boolean values are traditionally represented.
The program then executes a for loop that prints out the character corresponding to each of the hexadecimal digits. The static forDigit() method of the Character class is used to generate the character values of digits in a number system of a different radix.
The static parseInt() and parseLong() methods are used to parse strings according to different radices. In the example, they are used to convert strings representing hexadecimal numbers into Integer and Long values. These values are then multiplied together and converted to a string that represents the resulting value in base 8. This is accomplished using an overloaded version of the toString() method.
The sample program concludes by displaying the minimum float value and the maximum double value using the predefined class constants of the Float and Double classes.
The program's output is as follows:
true or false 0123456789abcdef 50062143 1.4E-45 1.7976931348623157E308
The Math class provides an extensive set of mathematical methods in the form of a static class library. It also defines the mathematical constants E and PI. The supported methods include arithmetic, trigonometric, exponential, logarithmic, random number, and conversion routines. You should browse the API page of this class to get a feel for the methods it provides. The example in Listing 10.7 only touches on a few of these methods.
import java.lang.System; import java.lang.Math; public class MathApp { public static void main(String args[]) { System.out.println(Math.E); System.out.println(Math.PI); System.out.println(Math.abs(-1234)); System.out.println(Math.cos(Math.PI/4)); System.out.println(Math.sin(Math.PI/2)); System.out.println(Math.tan(Math.PI/4)); System.out.println(Math.log(1)); System.out.println(Math.exp(Math.PI)); for(int i=0;i<3;++i) System.out.print(Math.random()+" "); System.out.println(); } }
This program prints the constants e and p, |-1234|, cos(p/4), sin(p/2), tan(p/4), ln(1), ep, and then three random double numbers between 0.0 and 1.1. Its output is as follows:
2.718281828459045 3.141592653589793 1234 0.7071067811865476 1.0 0.9999999999999999 0.0 23.14069263277926 0.5214844573332809 0.7036104523989761 0.15555052349418896
The random numbers you generate will almost certainly differ from those shown here.
The Comparable interface is a new interface that was added with JDK 1.2. This interface defines the compareTo() method. Objects of classes that implement the Comparable interface can be compared to each other and sorted.
The String and StringBuffer Classes
The String and StringBuffer classes are used to support operations on strings of characters. The String class supports constant (unchanging) strings, whereas the StringBuffer class supports growable, modifiable strings. String objects are more compact than StringBuffer objects, but StringBuffer objects are more flexible.
String literals are strings that are specified using double quotes. "This is a string" and "xyz" are examples of string literals. String literals are different than the literal values used with primitive types. When the javac compiler encounters a String literal, it converts it to a String constructor. For example, this:
String str = "text";
is equivalent to this:
String str = new String("text");
The fact that the compiler automatically supplies String constructors allows you to use String literals everywhere that you could use objects of the String class.
If String objects are constant, how can they be concatenated with the + operator and be assigned to existing String objects? In the following example, the code will result in the string "ab" being assigned to the s object:
String s = ""; s = s + "a" + "b";
How can this be possible if Strings are constant? The answer lies in the fact that the Java compiler uses StringBuffer objects to accomplish the string manipulations. This code would be rendered as something similar to the following by the Java compiler:
String s = ""; s = new StringBuffer("").append("a").append("b").toString();
A new object of class StringBuffer is created with the "" argument. The StringBuffer append() method is used to append the strings "a" and "b" to the new object, and then the object is converted to an object of class String via the toString() method. The toString() method creates a new object of class String before it is assigned to the s variable. In this way, the s variable always refers to a constant (although new) String object.
The String class provides several constructors for the creation and initialization of String objects. These constructors allow strings to be created from other strings, string literals, arrays of characters, arrays of bytes, and StringBuffer objects. Browse through the API page for the String class to become familiar with these constructors.
The String class provides a very powerful set of methods for working with String objects. These methods allow you to access individual characters and substrings; test and compare strings; copy, concatenate, and replace parts of strings; convert and create strings; and perform other useful string operations.
The most important String methods are the length() method, which returns an integer value identifying the length of a string; the charAt() method, which allows the individual characters of a string to be accessed; the substring() method, which allows substrings of a string to be accessed; and the valueOf() method, which allows primitive data types to be converted into strings.
In addition to these methods, the Object class provides a toString() method for converting other objects to String objects. This method is often overridden by subclasses to provide a more appropriate object-to-string conversion.
Character and Substring Methods
Several String methods allow you to access individual characters and substrings of a string. These include charAt(), getBytes(), getChars(), indexOf(), lastIndexOf(), and substring(). Whenever you need to perform string manipulations, be sure to check the API documentation to make sure that you don't overlook an easy-to-use, predefined String method.
String Comparison and Test Methods
Several String methods allow you to compare strings, substrings, byte arrays, and other objects with a given string. Some of these methods are compareTo(), endsWith(), equals(), equalsIgnoreCase(), regionMatches(), and startsWith().
Copy, Concatenation, and Replace Methods
The following methods are useful for copying, concatenating, and manipulating strings: concat(), copyValueOf(), replace(), and trim().
String Conversion and Generation
A number of string methods support String conversion. These are intern(), toCharArray(), toLowerCase(), toString(), toUpperCase(), and valueOf(). You'll explore the use of some of these methods in the following example.
Stringing Along
The program in Listing 10.8 provides a glimpse at the operation of some of the methods identified in the previous subsections. Because strings are frequently used in application programs, learning to use the available methods is essential to being able to use the String class most effectively.
import java.lang.System; import java.lang.String; public class StringApp { public static void main(String args[]) { String s = " Java Unleashed 1.2 "; System.out.println(s); System.out.println(s.toUpperCase()); System.out.println(s.toLowerCase()); System.out.println("["+s+"]"); s=s.trim(); System.out.println("["+s+"]"); s=s.replace(`J','X'); s=s.replace(`U','Y'); s=s.replace(`2','Z'); System.out.println(s); int i1 = s.indexOf(`X'); int i2 = s.indexOf(`Y'); int i3 = s.indexOf(`Z'); char ch[] = s.toCharArray(); ch[i1]='J'; ch[i2]='U'; ch[i3]='2'; s = new String(ch); System.out.println(s); } }
This program performs several manipulations of a string s, which is initially set to "Java Unleashed 1.2 ". It prints the original string and then prints upper- and lowercase versions of it, illustrating the use of the toUpperCase() and toLowerCase() methods. It prints the string enclosed between two braces to show that it contains leading and trailing spaces. It then trims away these spaces using the trim() method and reprints the string to show that these spaces were removed.
The program uses the replace() method to replace `J', `U', and `2' with `X', `Y', and `Z', and prints out the string to show the changes. The replace() method is case sensitive. It uses the indexOf() method to get the indices of `X', `Y', and `Z' within s. It uses the toCharArray() to convert the string to a char array. It then uses the indices to put `J', `U', and `2' back in their proper locations within the character array. The String() constructor is used to construct a new string from the character array. The new string is assigned to s and is printed.
The program's output is as follows:
Java Unleashed 1.2 JAVA UNLEASHED 1.2 java unleashed 1.2 [ Java Unleashed 1.2 ] [Java Unleashed 1.2] Xava Ynleashed 1.Z Java Unleashed 1.2
The StringBuffer class is the force behind the scenes for most complex string manipulations. The compiler automatically declares and manipulates objects of this class to implement common string operations.
The StringBuffer class provides three constructors: an empty constructor, a constructor with a specified initial buffer length, and a constructor that creates a StringBuffer object from a String object. In general, you will find yourself constructing StringBuffer objects from String objects, and the last constructor will be the one you use most often.
The StringBuffer class provides several versions of the append() method to convert and append other objects and primitive data types to StringBuffer objects. It provides a similar set of insert() methods for inserting objects and primitive data types into StringBuffer objects. It also provides methods to access the character-buffering capacity of StringBuffer and methods for accessing the characters contained in a string. It is well worth a visit to the StringBuffer API pages to take a look at the methods that it has to offer.
Strung Out
The program in Listing 10.9 shows how StringBuffer objects can be manipulated using the append(), insert(), and setCharAt() methods.
import java.lang.System; import java.lang.String; import java.lang.StringBuffer; public class StringBufferApp { public static void main(String args[]) { StringBuffer sb = new StringBuffer(" is "); sb.append("Hot"); sb.append(`!'); sb.insert(0,"Java"); sb.append(`\n'); sb.append("This is "); sb.append(true); sb.setCharAt(21,'T'); sb.append(`\n'); sb.append("Java is #"); sb.append(1); String s = sb.toString(); System.out.println(s); } }
The program creates a StringBuffer object using the string " is ". It appends the string "Hot" using the append() method and the character `!' using an overloaded version of the same method. The insert() method is used to insert the string "Java" at the beginning of the string buffer.
Three appends are used to tack on a newline character (\n), the string "This is ", and the boolean value true. The append() method is overloaded to support the appending of the primitive data types as well as arbitrary Java objects.
The setCharAt() method is used to replace the letter `t' at index 21 with the letter `T'. The charAt() and setCharAt() methods allow StringBuffer objects to be treated as arrays of characters.
Finally, another newline character is appended to sb, followed by the string "Java is #" and the int value 1. The StringBuffer object is then converted to a string and displayed to the console window.
The output of the program is as follows:
Java is Hot! This is True Java is #1
Threads and Processes
This section describes the classes of java.lang that support multithreading. It also covers the Process class, which is used to manipulate processes that are executed using the System.exec() methods.
The Runnable interface provides a common approach to identifying the code to be executed as part of an active thread. It consists of a single method, run(), which is executed when a thread is activated. The Runnable interface is implemented by the Thread class and by other classes that support threaded execution.
The Thread class is used to construct and access individual threads of execution that are executed as part of a multithreaded program. It defines the priority constants that are used to control task scheduling: MIN_PRIORITY, MAX_PRIORITY, and NORM_PRIORITY. It provides seven constructors for creating instances of class Thread. The four constructors with the Runnable parameters are used to construct threads for classes that do not subclass the Thread class. The other constructors are used for the construction of Thread objects from Thread subclasses.
Thread supports many methods for accessing Thread objects. These methods provide the capabilities to work with a thread's group; obtain detailed information about a thread's activities; set and test a thread's properties; and cause a thread to wait, be interrupted, or be destroyed.
The ThreadGroup class is used to encapsulate a group of threads as a single object so that they can be accessed as a single unit. A number of access methods are provided for manipulating ThreadGroup objects. These methods keep track of the threads and thread groups contained in a thread group and perform global operations on all threads in the group. The global operations are group versions of the operations that are provided by the Thread class.
The ThreadLocal class is used to implement variables that are local to a thread. The get(), set(), and initialize() methods are used to set and retrieve the values of these variables.
The Process class is used to encapsulate processes that are executed with the System.exec() methods. An instance of class Process is returned by the Runtime class exec() method when it executes a process that is external to the Java runtime system. This Process object can be destroyed using the destroy() method and waited on using the waitFor() method. The exitValue() method returns the system exit value of the process. The getInputStream(), getOutputStream(), and getErrorStream() methods are used to access the standard input, output, and error streams of the process.
The simple program in Listing 10.10 actually performs some pretty complex processing. It is provided as an example of some of the powerful things that can be accomplished using the Process class.
import java.lang.System; import java.lang.Runtime; import java.lang.Process; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; public class ProcessApp { public static void main(String args[]) throws IOException { Runtime r = Runtime.getRuntime(); Process p = r.exec("java SystemApp"); BufferedReader kbdInput = new BufferedReader(new InputStreamReader(p.getInputStream())); String line; while((line = kbdInput.readLine())!=null) System.out.println(line); } }
The program uses the static getRuntime() method to get the current instance of the Java runtime system. It then uses the exec() method to execute another separate copy of the Java interpreter with the SystemApp program that was developed earlier in this chapter. It creates a BufferedReader object, kbdInput, that is connected to the output stream of the SystemApp program. It then uses kbdInput to read the output of the SystemApp program and display it on the console window.
The exec() methods combined with the Process class provide a powerful set of tools by which Java programs can be used to launch and control the execution of other programs.
The Compiler class consists of static methods that are used to compile Java classes in the rare event that you want to compile classes directly from a program or applet. These methods allow you to build your own customized Java development environment.
The java.lang package establishes the Java exception hierarchy and declares numerous exceptions and errors. Errors are used to indicate the occurrence of abnormal and fatal events that should not be handled within application programs.
The Throwable class is at the top of the Java error-and-exception hierarchy. It is extended by the Error and Exception classes and provides methods that are common to both classes. These methods consist of stack tracing methods, the getMessage() method, and the toString() method, which is an override of the method inherited from the Object class. The getMessage() method is used to retrieve any messages that are supplied in the creation of Throwable objects.
The fillInStackTrace() and printStackTrace() methods supply and print information that is used to trace the propagation of exceptions and errors throughout a program's execution.
The Error class is used to provide a common superclass to define abnormal and fatal events that should not occur. It provides two constructors and no other methods. Four major classes of errors extend the Error class: AWTError, LinkageError, ThreadDeath, and VirtualMachineError.
The AWTError class identifies fatal errors that occur in the Abstract Window Toolkit packages. It is a single identifier for all AWT errors and is not subclassed.
The LinkageError class is used to define errors that occur as the result of incompatibilities between dependent classes. These incompatibilities result when class Y depends on class X, which is changed before class Y can be recompiled. The LinkageError class is extensively subclassed to identify specific manifestations of this type of error.
The ThreadDeath error class is used to indicate that a thread has been stopped. Instances of this class can be caught and then rethrown to ensure that a thread is gracefully terminated, although this is not recommended. The ThreadDeath class is not subclassed.
The VirtualMachineError class is used to identify fatal errors occurring in the operation of the Java Virtual Machine. It has four subclasses: InternalError, OutOfMemoryError, StackOverflowError, and UnknownError.
The Exception Class
The Exception class provides a common superclass for the exceptions that can be defined for Java programs and applets.
The Void class is used to reference the Class object representing the void type. It is provided for completeness. It has no constructors or methods.
The classes and interfaces of the java.lang.reflect package enable classes, interfaces, and objects to be examined, and their public fields, constructors, and methods to be discovered and used at runtime. These capabilities are used by JavaBeans, object inspection tools, Java runtime tools such as the debugger, and other Java applications and applets.
The java.lang.reflect package consists of the Member interface and seven classes: AccessibleObject, Array, Constructor, Field, Method, Modifier, and ReflectPermission.
The Member interface is used to provide information about a Field, Constructor, or Method. It defines two constant variables and three methods. The DECLARED constant identifies the class members (fields, constructors, and methods) that are declared for a class. The PUBLIC constant identifies all members of a class or interface, including those that are inherited. The getName() method returns the name of the referenced Member. The getModifiers() method returns the modifiers of the referenced Member encoded as an integer. The Modifier class is used to decode this integer. The getDeclaringClass() method returns the class in which the Member is declared.
The AccessibleObject class is introduced with JDK 1.2. It is the superclass of the Constructor, Field, and Method classes. It was added to the class hierarchy to provide the capability to specify whether an object suppresses reflection access control checks. The isAccessible() method identifies whether the object suppresses access control checks. The setAccessible() method is used to set the accessibility of an object or array of objects.
The Array class is used to obtain information about, create, and manipulate arrays. It consists of 21 static methods. The getLength() method is used to access the length of an array.
The get() method is used to access an indexed element of an array. The getBoolean(), getByte(), getChar(), getDouble(), getFloat(), getInt(), getLong(), and getShort() methods are used to access an indexed element of an array as a particular primitive type.
The set() method is used to set an indexed element of an array. The setBoolean(), setByte(), setChar(), setDouble(), setFloat(), setInt(), setLong(), and setShort() methods are used to set an indexed element of an array to a value of a particular primitive type.
The newInstance() method is used to create new arrays of a specified size.
The Constructor class is used to obtain information about and access the constructors of a class. It consists of nine methods.
The getName() method returns the name of the constructor. The getDeclaringClass() method identifies the class to which the constructor applies.
The newInstance() method is used to create a new instance of the class to which the constructor applies. The getParameterTypes() method provides access to the parameters used by the constructor. The getModifiers() method encodes the constructor's modifiers as an integer that can be decoded by the Modifier class. The getExceptionTypes() method identifies the exceptions that are thrown by the constructor.
The equals(), hashCode(), and toString() methods override those of the Object class.
The Field class is used to obtain information about and access the field variables of a class. It consists of 25 methods.
The getName() method returns the name of the variable. The getDeclaringClass() method identifies the class in which the variable is declared. The getType() method provides access to the data type of the variable. The getModifiers() method encodes the variable's modifiers as an integer that can be decoded by the Modifier class.
The get() method is used to access the value of the variable. The getBoolean(), getByte(), getChar(), getDouble(), getFloat(), getInt(), getLong(), and getShort() methods are used to access the value as a particular primitive type.
The set() method is used to set the value of the variable. The setBoolean(), setByte(), setChar(), setDouble(), setFloat(), setInt(), setLong(), and setShort() methods are used to set the value to a particular primitive type.
The equals(), hashCode(), and toString() methods override those of the Object class.
The Method class is used to obtain information about and access the methods of a class. It consists of 10 methods.
The getName() method returns the name of the method. The getDeclaringClass() method identifies the class in which the method is declared.
The invoke() method is used to invoke the method for a particular object and list of parameters. The getParameterTypes() method provides access to the parameters used by the method. The getModifiers() method encodes the method's modifiers as an integer that can be decoded by the Modifier class. The getExceptionTypes() method identifies the exceptions that are thrown by the method. The getReturnType() method identifies the type of object returned by the method.
The equals(), hashCode(), and toString() methods override those of the Object class.
The Modifier class is used to decode integers that represent the modifiers of classes, interfaces, field variables, constructors, and methods. It consists of 11 constants, a single parameterless constructor, and 12 static access methods.
The 11 constants are used to represent all possible modifiers. They are ABSTRACT, FINAL, INTERFACE, NATIVE, PRIVATE, PROTECTED, PUBLIC, STATIC, SYNCHRONIZED, TRANSIENT, and VOLATILE.
The toString() method returns a string containing the modifiers encoded in an integer. The isAbstract(), isFinal(), isInterface(), isNative(), isPrivate(), isProtected(), isPublic(), isStatic(), isSynchronized(), isTransient(), and isVolatile() methods return a boolean value indicating whether the respective modifier is encoded in an integer.
The ReflectPermission class is a permission class introduced with JDK 1.2. It is used to specify whether the default language access checks should be suppressed for reflected objects. Chapter 3 covers the use of permissions in setting up a security policy.
The java.lang.reflect provides a number of useful methods for discovering information about the members of a class or interface. In most cases, you will use it with the Class class of java.lang. The example in Listing 10.11 shows how Class and the classes of java.lang.reflect can be used together to create a program that discovers and displays information about classes and interfaces.
import java.lang.reflect.*; public class ReflectApp { public static void main(String args[]) { String parm = args[0]; Class className = void.class; try { className= Class.forName(parm); }catch (ClassNotFoundException ex){ System.out.println("Not a class or interface."); System.exit(0); } describeClassOrInterface(className,parm); } static void describeClassOrInterface(Class className,String name){ if(className.isInterface()){ System.out.println("Interface: "+name); displayModifiers(className.getModifiers()); displayFields(className.getDeclaredFields()); displayMethods(className.getDeclaredMethods()); }else{ System.out.println("Class: "+name); displayModifiers(className.getModifiers()); displayInterfaces(className.getInterfaces()); displayFields(className.getDeclaredFields()); displayConstructors(className.getDeclaredConstructors()); displayMethods(className.getDeclaredMethods()); } } static void displayModifiers(int m){ System.out.println("Modifiers: "+Modifier.toString(m)); } static void displayInterfaces(Class[] interfaces){ if(interfaces.length>0){ System.out.println("Interfaces: "); for(int i=0;i<interfaces.length;++i) System.out.println(interfaces[i].getName()); } } static void displayFields(Field[] fields){ if(fields.length>0){ System.out.println("Fields: "); for(int i=0;i<fields.length;++i) System.out.println(fields[i].toString()); } } static void displayConstructors(Constructor[] constructors){ if(constructors.length>0){ System.out.println("Constructors: "); for(int i=0;i<constructors.length;++i) System.out.println(constructors[i].toString()); } } static void displayMethods(Method[] methods){ if(methods.length>0){ System.out.println("Methods: "); for(int i=0;i<methods.length;++i) System.out.println(methods[i].toString()); } } }
Compile the program and run it as follows. It takes the fully qualified name of a class or interface as a command-line argument. The following output shows the information that it generates when the java.lang.reflect.AccessibleObject class is used as an argument. It identifies the class modifiers as public and synchronized and identifies ACCESS_PERMISSION and override as field variables. It also shows that AccessibleObject has a single parameterless constructor and three methods.
java ReflectApp java.lang.reflect.AccessibleObject Class: java.lang.reflect.AccessibleObject Modifiers: public synchronized Fields: private static final java.security.Permission Âjava.lang.reflect.AccessibleObject .ACCESS_PERMISSION private boolean java.lang.reflect.AccessibleObject.override Constructors: protected java.lang.reflect.AccessibleObject() Methods: public static void java.lang.reflect.AccessibleObject.setAccessible Â(java.lang.reflect.AccessibleObject[],boolean) throws Âjava.lang.SecurityException public void java.lang.reflect.AccessibleObject.setAccessible(boolean) Âthrows java.lang.SecurityException public boolean java.lang.reflect.AccessibleObject.isAccessible()
Try running ReflectApp with other classes and interfaces. It's a great way to learn about the definition of classes and interfaces.
ReflectApp takes the first command-line argument and uses the forName() method of Class to create a Class object representing the class or interface identified by the argument. This Class object is assigned to the className variable. The describeClassOrInterface() method is invoked to display information about the class or interface.
The describeClassOrInterface() method uses the isInterface() method of Class to determine whether the Class object refers to an interface or class. It uses getInterfaces() to retrieve all of the interfaces of a class and invokes displayInterfaces() to display those interfaces.
The describeClassOrInterface() method uses getModifiers(), getDeclaredFields(), getDeclaredConstructors(), and getDeclaredMethods() to retrieve the Modifier, Field, Constructor, and Method objects associated with a class or interface. It invokes displayModifiers(), displayFields(), displayConstructors(), and displayMethods() to display information about these objects. These display methods use the toString() method to convert the objects into a useful display string.
JDK 1.2 introduces reference objects, which are objects that store references to other objects. The java.lang.ref package provides five classes that implement reference objects. These classes also provide the capability to notify a program when a referenced object is subject to garbage collection. This capability allows reference objects to be used to implement object caching mechanisms.
The Reference class is the top-level class of the reference class hierarchy. It provides the capability to store a reference to another object. The object being referenced is referred to as the referent. The Reference class provides the following methods for getting and setting references to the referents:
The classes of the reference object hierarchy are used to work with referents of varying degrees of reachability. Reachability refers to the ease with which an object can be referenced. The following degrees of reachability are defined:
Reference objects can be registered with a reference queue. When the garbage collector determines that a referent has a change in reachability, the reference object is added to the queue. A program can be notified of the queuing of a reference. The program can then remove the reference from the queue in order to access the referent. When a reference object is removed from a queue, it becomes unregistered. A reference object can only be registered with a single reference queue. The ReferenceQueue class implements reference queues. It provides the remove() method for removing an object from the queue. The register() and unregister() methods of the Reference class are used to register and unregister objects with a ReferenceQueue object.
The subclasses of Reference are used to implement the reachability levels. These classes are as follows:
Although the reference classes are intended to be used to build caching systems, reference objects can be used in other ways. The ReferenceApp program (Listing 10.12) provides an example of the use of reference objects. It creates two String objects and assigns them to s1 and s2. It then creates two WeakReference objects that refer to the String objects, and assigns the reference objects to g1 and g2. It shows that g1 and g2 reference the original String objects by displaying the values returned by the get() method. The WeakReference object referenced by g2 is assigned to g1. The value of the referent of g1 is then displayed to show that it is now the second String object.
The output of ReferenceApp is as follows:
g1 = The value of a String object 1 g2 = The value of a String object 2 g1 = The value of a String object 2
import java.lang.ref.*; public class ReferenceApp { public static void main(String args[]) { String s1 = "The value of a String object 1"; String s2 = "The value of a String object 2"; GuardedReference g1 = new WeakReference(s1); GuardedReference g2 = new WeakReference(s2); System.out.println("g1 = "+(String) g1.get()); System.out.println("g2 = "+(String) g2.get()); g1=g2; System.out.println("g1 = "+(String) g1.get()); } }
In this chapter you learned to write console programs in Java. You learned how to read user keyboard input, process the input, and display text output to the user's console. You also learned about some useful classes and interfaces in the java.lang, java.lang.reflect, and java.lang.ref packages. In the next chapter you'll learn how to use the classes and interfaces of the java.util and java.math packages.
© Copyright, Macmillan Computer Publishing. All rights reserved.