|
Related Reading
Java In a Nutshell |
In this excerpt from Chapter 4 of Java in a Nutshell, 4th Edition, David Flanagan shows you a number of the Java 2SE platform packages, using examples of the most useful classes in these packages.
Chapters 2 and 3 documented the Java programming language. This chapter switches gears and covers the Java platform -- a vast collection of predefined classes available to every Java program, regardless of the underlying host system on which it is running. The classes of the Java platform are collected into related groups, known as packages. This chapter begins with an overview of the packages of the Java platform that are documented in this book. It then moves on to demonstrate, in the form of short examples, the most useful classes in these packages. Most of the examples are code snippets only, not full programs you can compile and run. For fully fleshed-out, real-world examples, see Java Examples in a Nutshell (O'Reilly). That book expands greatly on this chapter and is intended as a companion to this one.
Table 1-1 summarizes the key packages of the Java platform that are covered in this book.
| Package | Description |
|---|---|
java.beans |
The JavaBeans component model for reusable, embeddable software components. |
java.beans.beancontext |
Additional classes that define bean context objects that hold and provide services to the JavaBeans objects they contain. |
java.io |
Classes and interfaces for input and output. Although some of the classes in this package are for working directly with files, most are for working with streams of bytes or characters. |
java.lang |
The core classes of the language, such as
|
java.lang.ref |
Classes that define weak references to objects. A weak reference is one that does not prevent the referent object from being garbage-collected. |
java.lang.reflect |
Classes and interfaces that allow Java programs to reflect on themselves by examining the constructors, methods, and fields of classes. |
java.math |
A small package that contains classes for arbitrary-precision integer and floating-point arithmetic. |
java.net |
Classes and interfaces for networking with other systems. |
java.nio |
Buffer classes for the New I/O API. |
java.nio.channels |
Channel and selector interfaces and classes for high-performance, nonblocking I/O. |
java.nio.charset |
Character set encoders and decoders for converting Unicode strings to and from bytes. |
java.security |
Classes and interfaces for access control and authentication. Supports cryptographic message digests and digital signatures. |
java.security.acl |
A package that supports access control lists. Deprecated and unused as of Java 1.2. |
java.security.cert |
Classes and interfaces for working with public-key certificates. |
java.security.interfaces |
Interfaces used with DSA and RSA public-key encryption. |
java.security.spec |
Classes and interfaces for transparent representations of keys and parameters used in public-key cryptography. |
java.text |
Classes and interfaces for working with text in internationalized applications. |
java.util |
Various utility classes, including the powerful collections framework for working with collections of objects. |
java.util.jar |
Classes for reading and writing JAR files. |
java.util.logging |
A flexible logging facility. |
java.util.prefs |
An API to read and write user and system preferences. |
java.util.regex |
Text pattern matching using regular expressions. |
java.util.zip |
Classes for reading and writing ZIP files. |
javax.crypto |
Classes and interfaces for encryption and decryption of data. |
javax.crypto.interfaces |
Interfaces that represent the Diffie-Hellman public/private keys used in the Diffie-Hellman key agreement protocol. |
javax.crypto.spec |
Classes that define transparent representations of keys and parameters used in cryptography. |
javax.net |
Defines factory classes for creating sockets and server sockets. Enables the creation of socket types other than the default. |
javax.net.ssl |
Classes for encrypted network communication using the Secure Sockets Layer (SSL). |
javax.security.auth |
The top-level package for the JAAS API for authentication and authorization. |
javax.security.auth.callback |
Classes that facilitate communication between a low-level login module and a user through a user interface. |
javax.security.auth.kerberos |
Utility classes to support network authentication using the Kerberos protocol. |
javax.security.auth.login |
The |
javax.security.auth.spi |
Defines the |
javax.security.auth.x500 |
Utility classes that represent X.500 certificate information. |
javax.xml.parsers |
A high-level API for parsing XML documents using pluggable DOM and SAX parsers. |
javax.xml.transform |
A high-level API for transforming XML documents using a pluggable XSLT transformation engine and for converting XML documents between streams, DOM trees, and SAX events. |
javax.xml.transform.dom |
Concrete XML transformation classes for DOM. |
javax.xml.transform.sax |
Concrete XML transformation classes for SAX. |
javax.xml.transform.stream |
Concrete XML transformation classes for XML streams. |
org.ietf.jgss |
The Java binding of the Generic Security Services API, which defines a single API for underlying security mechanisms such as Kerberos. |
org.w3c.dom |
Interfaces defined by the World Wide Web Consortium to represent an XML document as a DOM tree. |
org.xml.sax |
Classes and interfaces for parsing XML documents using the event-based SAX (Simple API for XML) API. |
org.xml.sax.ext |
Extension classes for the SAX API. |
org.xml.sax.helpers |
Utility classes for the SAX API. |
Table 1-1 does not list all the
packages in the Java platform, only those documented in this book.
(And it omits a few "spi" packages that are documented in this
book but are of interest only to low-level "service
providers.") Java also defines numerous
packages for graphics and graphical user interface programming and
for distributed, or enterprise, computing. The graphics and GUI
packages are java.awt and
javax.swing and their many subpackages. These
packages, along with the java.applet package,
are documented in Java Foundation Classes in a
Nutshell (O'Reilly). The enterprise packages of Java
include java.rmi, java.sql,
javax.jndi, org.omg.CORBA,
org.omg.CosNaming, and all of their
subpackages. These packages, as well as several standard
extensions to the Java platform, are documented in
Java Enterprise in a Nutshell (O'Reilly).
Strings of text are a fundamental and commonly used data type. In
Java, however, strings are not a primitive type, like
char, int, and
float. Instead, strings are represented
by the java.lang.String class, which
defines many useful methods for manipulating
strings. String objects are
immutable: once a String
object has been created, there is no way to modify the string of
text it represents. Thus, each method that operates on a string
typically returns a new String object that holds the
modified string.
This code shows some of the basic operations you can perform on strings:
// Creating strings
String s = "Now"; // String objects have a special literal syntax
String t = s + " is the time."; // Concatenate strings with + operator
String t1 = s + " " + 23.4; // + converts other values to strings
t1 = String.valueOf('c'); // Get string corresponding to char value
t1 = String.valueOf(42); // Get string version of integer or any value
t1 = object.toString(); // Convert objects to strings with toString()
// String length
int len = t.length(); // Number of characters in the string: 16
// Substrings of a string
String sub = t.substring(4); // Returns char 4 to end: "is the time."
sub = t.substring(4, 6); // Returns chars 4 and 5: "is"
sub = t.substring(0, 3); // Returns chars 0 through 2: "Now"
sub = t.substring(x, y); // Returns chars between pos x and y-1
int numchars = sub.length(); // Length of substring is always (y-x)
// Extracting characters from a string
char c = t.charAt(2); // Get the 3rd character of t: w
char[] ca = t.toCharArray(); // Convert string to an array of characters
t.getChars(0, 3, ca, 1); // Put 1st 3 chars of t into ca[1]-ca[3]
// Case conversion
String caps = t.toUpperCase(); // Convert to uppercase
String lower = t.toLowerCase(); // Convert to lowercase
// Comparing strings
boolean b1 = t.equals("hello"); // Returns false: strings not equal
boolean b2 = t.equalsIgnoreCase(caps); // Case-insensitive compare: true
boolean b3 = t.startsWith("Now"); // Returns true
boolean b4 = t.endsWith("time."); // Returns true
int r1 = s.compareTo("Pow"); // Returns < 0: s comes before "Pow"
int r2 = s.compareTo("Now"); // Returns 0: strings are equal
int r3 = s.compareTo("Mow"); // Returns > 0: s comes after "Mow"
r1 = s.compareToIgnoreCase("pow"); // Returns < 0 (Java 1.2 and later)
// Searching for characters and substrings
int pos = t.indexOf('i'); // Position of first 'i': 4
pos = t.indexOf('i', pos+1); // Position of the next 'i': 12
pos = t.indexOf('i', pos+1); // No more 'i's in string, returns -1
pos = t.lastIndexOf('i'); // Position of last 'i' in string: 12
pos = t.lastIndexOf('i', pos-1); // Search backwards for 'i' from char 11
pos = t.indexOf("is"); // Search for substring: returns 4
pos = t.indexOf("is", pos+1); // Only appears once: returns -1
pos = t.lastIndexOf("the "); // Search backwards for a string
String noun = t.substring(pos+4); // Extract word following "the"
// Replace all instances of one character with another character
String exclaim = t.replace('.', '!'); // Works only with chars, not substrings
// Strip blank space off the beginning and end of a string
String noextraspaces = t.trim();
// Obtain unique instances of strings with intern()
String s1 = s.intern(); // Returns s1 equal to s
String s2 = "Now".intern(); // Returns s2 equal to "Now"
boolean equals = (s1 == s2); // Now can test for equality with ==
As you know, individual characters are represented in Java by the
primitive char type. The Java platform also defines
a Character class, which defines useful class
methods for checking the type of a character and for converting
the case of a character. For example:
char[] text; // An array of characters, initialized somewhere else
int p = 0; // Our current position in the array of characters
// Skip leading whitespace
while((p < text.length) && Character.isWhitespace(text[p])) p++;
// Capitalize the first word of text
while((p < text.length) && Character.isLetter(text[p])) {
text[p] = Character.toUpperCase(text[p]);
p++;
}
Since String objects are immutable, you cannot
manipulate the characters of an instantiated String. If you need to do this, use a
java.lang.StringBuffer instead:
// Create a string buffer from a string
StringBuffer b = new StringBuffer("Mow");
// Get and set individual characters of the StringBuffer
char c = b.charAt(0); // Returns 'M': just like String.charAt()
b.setCharAt(0, 'N'); // b holds "Now": can't do that with a String!
// Append to a StringBuffer
b.append(' '); // Append a character
b.append("is the time."); // Append a string
b.append(23); // Append an integer or any other value
// Insert Strings or other values into a StringBuffer
b.insert(6, "n't"); // b now holds: "Now isn't the time.23"
// Replace a range of characters with a string (Java 1.2 and later)
b.replace(4, 9, "is"); // Back to "Now is the time.23"
// Delete characters
b.delete(16, 18); // Delete a range: "Now is the time"
b.deleteCharAt(2); // Delete 2nd character: "No is the time"
b.setLength(5); // Truncate by setting the length: "No is"
// Other useful operations
b.reverse(); // Reverse characters: "si oN"
String s = b.toString(); // Convert back to an immutable string
s = b.substring(1,2); // Or take a substring: "i"
b.setLength(0); // Erase buffer; now it is ready for reuse
In Java 1.4, both the String and the
StringBuffer classes implement the new
java.lang.CharSequence interface, which is a
standard interface for querying the length of and extracting characters
and subsequences from a readable sequence of characters. This
interface is also implemented by the
java.nio.CharBuffer interface, which is part of
the New I/O API that was introduced in Java 1.4.
CharSequence provides a way to perform simple
operations on strings of characters regardless of the underlying
implementation of those strings. For example:
/**
* Return a prefix of the specified CharSequence that starts at the first
* character of the sequence and extends up to (and includes) the first
* occurrence of the character c in the sequence. Returns null if c is
* not found. s may be a String, StringBuffer, or java.nio.CharBuffer.
*/
public static CharSequence prefix(CharSequence s, char c) {
int numChars = s.length(); // How long is the sequence?
for(int i = 0; i < numChars; i++) { // Loop through characters in sequence
if (s.charAt(i) == c) // If we find c,
return s.subSequence(0,i+1); // then return the prefix subsequence
}
return null; // Otherwise, return null
}
In Java 1.4 and later, you can perform textual pattern matching
with regular expressions. Regular expression support is
provided by the Pattern and
Matcher classes of the
java.util.regex package, but the
String class defines a number of convenient
methods that allow you to use regular expressions even more
simply. Regular expressions use a fairly complex grammar to
describe patterns of characters. The Java implementation uses
the same regex syntax as the Perl programming language. See the
java.util.regex.Pattern class in for a summary of this syntax or consult a
good Perl programming book for further details. For a complete
tutorial on Perl-style regular expressions, see
Mastering Regular Expressions (O'Reilly).
The simplest String method that accepts a
regular expression argument is matches(); it
returns true if the string matches the
pattern defined by the specified regular expression:
// This string is a regular expression that describes the pattern of a typical
// sentence. In Perl-style regular expression syntax, it specifies
// a string that begins with a capital letter and ends with a period,
// a question mark, or an exclamation point.
String pattern = "^[A-Z].*[\\.?!]$";
String s = "Java is fun!";
s.matches(pattern); // The string matches the pattern, so this returns true.
The matches() method returns
true only if the entire string is a match for the
specified pattern. Perl programmers should note that this
differs from Perl's behavior, in which a match means only that some
portion of the string matches the pattern. To determine if a
string or any substring matches a pattern, simply alter the
regular expression to allow arbitrary characters before and
after the desired pattern. In the following code, the regular
expression characters .* match any number of
arbitrary characters:
s.matches(".*\\bJava\\b.*"); // True if s contains the word "Java" anywhere
// The b specifies a word boundary
If you are already familiar with Perl's regular expression syntax, you know that it relies on the liberal use of backslashes to escape certain characters. In Perl, regular expressions are language primitives, and their syntax is part of the language itself. In Java, however, regular expressions are described using strings and are typically embedded in programs using string literals. The syntax for Java string literals also uses the backslash as an escape character, so to include a single backslash in the regular expression, you must use two backslashes. Thus, in Java programming, you will often see double backslashes in regular expressions.
In addition to matching, regular expressions can be used for
search-and-replace operations. The
replaceFirst() and
replaceAll() methods search a string for the
first substring or all substrings that match a given pattern
and replace the string or strings with the specified replacement
text, returning a new string that contains the replacements.
For example, you could use this code to ensure that the word
"Java" is correctly capitalized in a string s:
s.replaceAll("(?i)\\bjava\\b",// Pattern: the word "java", case-insensitive
"Java"); // The replacement string, correctly capitalized
The replacement string passed to replaceAll()
and replaceFirst() need not be a simple
literal string; it may also include references to text that
matched parenthesized subexpressions within the pattern. These
references take the form of a dollar sign followed by the number
of the subexpression. (If you are not familiar with
parenthesized subexpressions within a regular expression, see
java.util.regex.Pattern in .) For example, to
search for words such as JavaBean, JavaScript, JavaOS, and JavaVM
(but not Java or Javanese), and to replace the Java prefix with
the letter J without altering the suffix, you could use code such as:
s.replaceAll("\\bJava([A-Z]\\w+)", // The pattern
"J$1"); // J followed by the suffix that matched the
// subexpression in parentheses: [A-Z]\\w+
The other new Java 1.4 String method that uses regular
expressions is split(), which returns an
array of the substrings of a string, separated by delimiters
that match the specified pattern. To obtain an array of words
in a string separated by any number of spaces, tabs, or
newlines, do this:
String sentence = "This is a\n\ttwo-line sentence";
String[] words = sentence.split("[ \t\n\r]+");
An optional second argument specifies the maximum number of
entries in the returned array.
The matches(),
replaceFirst(),
replaceAll(), and split()
methods are suitable for when you use a regular
expression only once. If you want to use a regular expression for
multiple matches, you should explicitly use the
Pattern and Matcher
classes of the java.util.regex package.
First, create a Pattern object to represent
your regular expression with the static
Pattern.compile() method. (Another reason to
use the Pattern class explicitly instead of
the String convenience methods is that
Pattern.compile() allows you to specify flags
such as Pattern.CASE_INSENSITIVE that
globally alter the way the pattern matching is done.) Note that
the compile() method can throw a
PatternSyntaxException if you pass it an
invalid regular expression string. (This exception is also
thrown by the various String convenience
methods.) The Pattern class defines
split() methods that are similar to the
String.split() methods. For all other
matching, however, you must create a
Matcher object with the
matcher() method and specify the text to be
matched against:
import java.util.regex.*;
Pattern javaword = Pattern.compile("\\bJava(\\w*)", Pattern.CASE_INSENSITIVE);
Matcher m = javaword.matcher(sentence);
boolean match = m.matches(); // True if text matches pattern exactly
Once you have a Matcher object, you can
compare the string to the pattern in various ways. One of the
more sophisticated ways is to find all substrings that match
the pattern:
String text = "Java is fun; JavaScript is funny.";
m.reset(text); // Start matching against a new string
// Loop to find all matches of the string and print details of each match
while(m.find()) {
System.out.println("Found '" + m.group(0) + "' at position " + m.start(0));
if (m.start(1) < m.end(1)) System.out.println("Suffix is " + m.group(1));
}
See the Matcher class in for further details.
The compareTo() and equals()
methods of the String class allow you to
compare strings. compareTo() bases its
comparison on the character order defined by the Unicode encoding,
while equals() defines string equality as
strict character-by-character equality. These are not always the
right methods to use, however. In some languages, the character
ordering imposed by the Unicode standard does not match the
dictionary ordering used when alphabetizing strings. In Spanish,
for example, the letters "ch" are considered a single letter that
comes after "c" and before "d." When comparing human-readable
strings in an internationalized application, you should use the
java.text.Collator class instead:
import java.text.*;
// Compare two strings; results depend on where the program is run
// Return values of Collator.compare() have same meanings as String.compareTo()
Collator c = Collator.getInstance(); // Get Collator for current locale
int result = c.compare("chica", "coche"); // Use it to compare two strings
There are a number of other Java classes that operate on strings
and characters. One notable class is
java.util.StringTokenizer, which you can use
to break a string of text into its component words:
String s = "Now is the time";
java.util.StringTokenizer st = new java.util.StringTokenizer(s);
while(st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
You can even use this class to tokenize words that are delimited
by characters other than spaces:
String s = "a:b:c:d";
java.util.StringTokenizer st = new java.util.StringTokenizer(s, ":");
Java provides the byte,
short, int,
long, float, and
double primitive types for representing
numbers. The java.lang package includes the
corresponding Byte,
Short, Integer,
Long, Float, and
Double classes, each of which is a subclass of
Number. These classes can be useful
as object wrappers around their primitive types, and they also define
some useful constants:
// Integral range constants: Integer, Long, and Character also define these
Byte.MIN_VALUE // The smallest (most negative) byte value
Byte.MAX_VALUE // The largest byte value
Short.MIN_VALUE // The most negative short value
Short.MAX_VALUE // The largest short value
// Floating-point range constants: Double also defines these
Float.MIN_VALUE // Smallest (closest to zero) positive float value
Float.MAX_VALUE // Largest positive float value
// Other useful constants
Math.PI // 3.14159265358979323846
Math.E // 2.7182818284590452354
A Java program that operates on numbers must get its input values
from somewhere. Often, such a program
reads a textual representation of a number and must
convert it to a numeric representation. The
various Number subclasses define useful
conversion methods:
String s = "-42";
byte b = Byte.parseByte(s); // s as a byte
short sh = Short.parseShort(s); // s as a short
int i = Integer.parseInt(s); // s as an int
long l = Long.parseLong(s); // s as a long
float f = Float.parseFloat(s); // s as a float (Java 1.2 and later)
f = Float.valueOf(s).floatValue(); // s as a float (prior to Java 1.2)
double d = Double.parseDouble(s); // s as a double (Java 1.2 and later)
d = Double.valueOf(s).doubleValue(); // s as a double (prior to Java 1.2)
// The integer conversion routines handle numbers in other bases
byte b = Byte.parseByte("1011", 2); // 1011 in binary is 11 in decimal
short sh = Short.parseShort("ff", 16); // ff in base 16 is 255 in decimal
// The valueOf() method can handle arbitrary bases between 2 and 36
int i = Integer.valueOf("egg", 17).intValue(); // Base 17!
// The decode() method handles octal, decimal, or hexadecimal, depending
// on the numeric prefix of the string
short sh = Short.decode("0377").byteValue(); // Leading 0 means base 8
int i = Integer.decode("0xff").shortValue(); // Leading 0x means base 16
long l = Long.decode("255").intValue(); // Other numbers mean base 10
// Integer class can convert numbers to strings
String decimal = Integer.toString(42);
String binary = Integer.toBinaryString(42);
String octal = Integer.toOctalString(42);
String hex = Integer.toHexString(42);
String base36 = Integer.toString(42, 36);
Numeric values are often printed differently in different
countries. For example, many European languages use a comma to
separate the integral part of a floating-point value from the
fractional part (instead of a decimal point). Formatting differences can diverge
even further when displaying numbers that represent monetary
values. When converting numbers to strings for display,
therefore, it is best to use the
java.text.NumberFormat class to perform the
conversion in a locale-specific way:
import java.text.*;
// Use NumberFormat to format and parse numbers for the current locale
NumberFormat nf = NumberFormat.getNumberInstance(); // Get a NumberFormat
System.out.println(nf.format(9876543.21)); // Format number for current locale
try {
Number n = nf.parse("1.234.567,89"); // Parse strings according to locale
} catch (ParseException e) { /* Handle exception */ }
// Monetary values are sometimes formatted differently than other numbers
NumberFormat moneyFmt = NumberFormat.getCurrencyInstance();
System.out.println(moneyFmt.format(1234.56)); // Prints $1,234.56 in U.S.
The Math class defines a number of methods that
provide trigonometric, logarithmic, exponential, and rounding
operations, among others. This class is primarily useful with floating-point values. For
the trigonometric functions, angles are expressed in radians. The
logarithm and exponentiation functions are base
e, not base 10. Here are some examples:
double d = Math.toRadians(27); // Convert 27 degrees to radians
d = Math.cos(d); // Take the cosine
d = Math.sqrt(d); // Take the square root
d = Math.log(d); // Take the natural logarithm
d = Math.exp(d); // Do the inverse: e to the power d
d = Math.pow(10, d); // Raise 10 to this power
d = Math.atan(d); // Compute the arc tangent
d = Math.toDegrees(d); // Convert back to degrees
double up = Math.ceil(d); // Round to ceiling
double down = Math.floor(d); // Round to floor
long nearest = Math.round(d); // Round to nearest
The Math class also defines a rudimentary method for
generating pseudo-random numbers, but the
java.util.Random class is more flexible. If
you need very random pseudo-random numbers,
you can use the java.security.SecureRandom class:
// A simple random number
double r = Math.random(); // Returns d such that: 0.0 <= d < 1.0
// Create a new Random object, seeding with the current time
java.util.Random generator = new java.util.Random(System.currentTimeMillis());
double d = generator.nextDouble(); // 0.0 <= d < 1.0
float f = generator.nextFloat(); // 0.0 <= f < 1.0
long l = generator.nextLong(); // Chosen from the entire range of long
int i = generator.nextInt(); // Chosen from the entire range of int
i = generator.nextInt(limit); // 0 <= i < limit (Java 1.2 and later)
boolean b = generator.nextBoolean(); // true or false (Java 1.2 and later)
d = generator.nextGaussian(); // Mean value: 0.0; std. deviation: 1.0
byte[] randomBytes = new byte[128];
generator.nextBytes(randomBytes); // Fill in array with random bytes
// For cryptographic strength random numbers, use the SecureRandom subclass
java.security.SecureRandom generator2 = new java.security.SecureRandom();
// Have the generator generate its own 16-byte seed; takes a *long* time
generator2.setSeed(generator2.generateSeed(16)); // Extra random 16-byte seed
// Then use SecureRandom like any other Random object
generator2.nextBytes(randomBytes); // Generate more random bytes
The java.math package contains the
BigInteger and BigDecimal
classes. These classes allow you to work with arbitrary-size and
arbitrary-precision integers and floating-point values. For example:
import java.math.*;
// Compute the factorial of 1000
BigInteger total = BigInteger.valueOf(1);
for(int i = 2; i <= 1000; i++)
total = total.multiply(BigInteger.valueOf(i));
System.out.println(total.toString());
In Java 1.4, BigInteger has a method to
randomly generate large prime numbers, which is useful in many
cryptographic applications:
BigInteger prime =
BigInteger.probablePrime(1024, // 1024 bits long
generator2); // Source of randomness; from
// preceding example
Java uses several different classes for working with dates and
times. The java.util.Date class represents an
instant in
time (precise down to the millisecond). This class is nothing
more than a wrapper around a long value that
holds the number of milliseconds since midnight GMT, January 1, 1970. Here are two ways to determine the
current time:
long t0 = System.currentTimeMillis(); // Current time in milliseconds
java.util.Date now = new java.util.Date(); // Basically the same thing
long t1 = now.getTime(); // Convert a Date to a long value
The Date class has a number of
interesting-sounding methods, but almost all of them have been
deprecated in favor of methods of the
java.util.Calendar and
java.text.DateFormat classes.
To print a date or a
time, use the DateFormat class, which
automatically handles locale-specific conventions for date and time
formatting. DateFormat even works correctly in
locales that use a calendar other than the common era
(Gregorian) calendar in use throughout much of the world:
import java.util.Date;
import java.text.*;
// Display today's date using a default format for the current locale
DateFormat defaultDate = DateFormat.getDateInstance();
System.out.println(defaultDate.format(new Date()));
// Display the current time using a short time format for the current locale
DateFormat shortTime = DateFormat.getTimeInstance(DateFormat.SHORT);
System.out.println(shortTime.format(new Date()));
// Display date and time using a long format for both
DateFormat longTimestamp =
DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL);
System.out.println(longTimestamp.format(new Date()));
// Use SimpleDateFormat to define your own formatting template
// See java.text.SimpleDateFormat for the template syntax
DateFormat myformat = new SimpleDateFormat("yyyy.MM.dd");
System.out.println(myformat.format(new Date()));
try { // DateFormat can parse dates too
Date leapday = myformat.parse("2000.02.29");
}
catch (ParseException e) { /* Handle parsing exception */ }
The Date class and its millisecond
representation allow only a very simple form of date arithmetic:
long now = System.currentTimeMillis(); // The current time
long anHourFromNow = now + (60 * 60 * 1000); // Add 3,600,000 milliseconds
To perform more sophisticated date and time arithmetic and
manipulate dates in ways humans (rather than computers)
typically care about, use the
java.util.Calendar class:
import java.util.*;
// Get a Calendar for current locale and time zone
Calendar cal = Calendar.getInstance();
// Figure out what day of the year today is
cal.setTime(new Date()); // Set to the current time
int dayOfYear = cal.get(Calendar.DAY_OF_YEAR); // What day of the year is it?
// What day of the week does the leap day in the year 2000 occur on?
cal.set(2000, Calendar.FEBRUARY, 29); // Set year, month, day fields
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); // Query a different field
// What day of the month is the 3rd Thursday of May, 2001?
cal.set(Calendar.YEAR, 2001); // Set the year
cal.set(Calendar.MONTH, Calendar.MAY); // Set the month
cal.set(Calendar.DAY_OF_WEEK, Calendar.THURSDAY); // Set the day of week
cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, 3); // Set the week
int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); // Query the day in month
// Get a Date object that represents 30 days from now
Date today = new Date(); // Current date
cal.setTime(today); // Set it in the Calendar object
cal.add(Calendar.DATE, 30); // Add 30 days
Date expiration = cal.getTime(); // Retrieve the resulting date
The java.lang.System class defines an
arraycopy() method that is useful for
copying specified
elements in one array to a specified position in a second array. The second array must be the same type as the first, and it
can even be the same array:
char[] text = "Now is the time".toCharArray();
char[] copy = new char[100];
// Copy 10 characters from element 4 of text into copy, starting at copy[0]
System.arraycopy(text, 4, copy, 0, 10);
// Move some of the text to later elements, making room for insertions
System.arraycopy(copy, 3, copy, 6, 7);
In Java 1.2 and later, the java.util.Arrays class
defines useful array-manipulation methods, including
methods for sorting and searching arrays:
import java.util.Arrays;
int[] intarray = new int[] { 10, 5, 7, -3 }; // An array of integers
Arrays.sort(intarray); // Sort it in place
int pos = Arrays.binarySearch(intarray, 7); // Value 7 is found at index 2
pos = Arrays.binarySearch(intarray, 12); // Not found: negative return value
// Arrays of objects can be sorted and searched too
String[] strarray = new String[] { "now", "is", "the", "time" };
Arrays.sort(strarray); // { "is", "now", "the", "time" }
// Arrays.equals() compares all elements of two arrays
String[] clone = (String[]) strarray.clone();
boolean b1 = Arrays.equals(strarray, clone); // Yes, they're equal
// Arrays.fill() initializes array elements
byte[] data = new byte[100]; // An empty array; elements set to 0
Arrays.fill(data, (byte) -1); // Set them all to -1
Arrays.fill(data, 5, 10, (byte) -2); // Set elements 5, 6, 7, 8, 9 to -2
Arrays can be treated and manipulated as objects in Java. Given
an arbitrary object o, you can use code such as
the following to find out if the object
is an array and, if so, what type of array it is:
Class type = o.getClass();
if (type.isArray()) {
Class elementType = type.getComponentType();
}
The Java collection framework is a set of
important utility classes and interfaces in the
java.util package for working with collections
of objects. The collection framework defines two fundamental
types of collections. A Collection is a group
of objects, while a Map is a set of mappings, or
associations, between objects. A Set is a type
of Collection in which there are no duplicates,
and a List is a Collection
in which the elements are ordered. SortedSet
and SortedMap are specialized sets and maps
that maintain their elements in a sorted order.
Collection, Set,
List, Map,
SortedSet, and SortedMap are
all interfaces, but the java.util package also
defines various concrete implementations, such as lists based on
arrays and linked lists, and maps and sets based on hashtables or
binary trees. (See the java.util package in for a complete list.) Other important interfaces
are Iterator and
ListIterator, which allow you to loop through
the objects in a collection. The collection framework is new as of
Java 1.2, but prior to that release you can use
Vector and Hashtable, which
are approximately the same as ArrayList and
HashMap.
In Java 1.4, the Collections API has grown with the addition of
the RandomAccess marker interface, which is
implemented by List implementations that
support efficient random access (i.e., it is implemented by
ArrayList and Vector but not
by LinkedList.) Java 1.4 also introduces
LinkedHashMap and
LinkedHashSet, which are hashtable-based maps
and sets that preserve the insertion order of elements. Finally,
IdentityHashMap is a hashtable-based
Map implementation that uses the
== operator to compare key objects rather than
using the equals() method to compare them.
The following code demonstrates how you might create and perform basic manipulations on sets, lists, and maps:
import java.util.*;
Set s = new HashSet(); // Implementation based on a hashtable
s.add("test"); // Add a String object to the set
boolean b = s.contains("test2"); // Check whether a set contains an object
s.remove("test"); // Remove a member from a set
Set ss = new TreeSet(); // TreeSet implements SortedSet
ss.add("b"); // Add some elements
ss.add("a");
// Now iterate through the elements (in sorted order) and print them
for(Iterator i = ss.iterator(); i.hasNext();)
System.out.println(i.next());
List l = new LinkedList(); // LinkedList implements a doubly linked list
l = new ArrayList(); // ArrayList is more efficient, usually
Vector v = new Vector(); // Vector is an alternative in Java 1.1/1.0
l.addAll(ss); // Append some elements to it
l.addAll(1, ss); // Insert the elements again at index 1
Object o = l.get(1); // Get the second element
l.set(3, "new element"); // Set the fourth element
l.add("test"); // Append a new element to the end
l.add(0, "test2"); // Insert a new element at the start
l.remove(1); // Remove the second element
l.remove("a"); // Remove the element "a"
l.removeAll(ss); // Remove elements from this set
if (!l.isEmpty()) // If list is not empty,
System.out.println(l.size()); // print out the number of elements in it
boolean b1 = l.contains("a"); // Does it contain this value?
boolean b2 = l.containsAll(ss); // Does it contain all these values?
List sublist = l.subList(1,3); // A sublist of the 2nd and 3rd elements
Object[] elements = l.toArray(); // Convert it to an array
l.clear(); // Delete all elements
Map m = new HashMap(); // Hashtable an alternative in Java 1.1/1.0
m.put("key", new Integer(42)); // Associate a value object with a key object
Object value = m.get("key"); // Look up the value associated with a key
m.remove("key"); // Remove the association from the Map
Set keys = m.keySet(); // Get the set of keys held by the Map
Arrays of objects and collections serve similar purposes. It is possible to convert from one to the other:
Object[] members = set.toArray(); // Get set elements as an array
Object[] items = list.toArray(); // Get list elements as an array
Object[] keys = map.keySet().toArray(); // Get map key objects as an array
Object[] values = map.values().toArray(); // Get map value objects as an array
List l = Arrays.asList(a); // View array as an ungrowable list
List l = new ArrayList(Arrays.asList(a)); // Make a growable copy of it
Just as the java.util.Arrays class defined methods to
operate on arrays, the java.util.Collections class
defines methods to operate on collections. Most notable are
methods to sort and search the elements of collections:
Collections.sort(list);
int pos = Collections.binarySearch(list, "key"); // list must be sorted first
Here are some other interesting Collections
methods:
Collections.copy(list1, list2); // Copy list2 into list1, overwriting list1
Collections.fill(list, o); // Fill list with Object o
Collections.max(c); // Find the largest element in Collection c
Collections.min(c); // Find the smallest element in Collection c
Collections.reverse(list); // Reverse list
Collections.shuffle(list); // Mix up list
Set s = Collections.singleton(o); // Return an immutable set with one element o
List ul = Collections.unmodifiableList(list); // Immutable wrapper for list
Map sm = Collections.synchronizedMap(map); // Synchronized wrapper for map
The java.lang.Class class represents data
types in Java and, along with the classes in the
java.lang.reflect package, gives Java programs
the capability of introspection (or self-reflection); a
Java class can look at
itself, or any other class, and determine its superclass, what
methods it defines, and so on.
There are several ways you can
obtain a Class object in Java:
// Obtain the Class of an arbitrary object o
Class c = o.getClass();
// Obtain a Class object for primitive types with various predefined constants
c = Void.TYPE; // The special "no-return-value" type
c = Byte.TYPE; // Class object that represents a byte
c = Integer.TYPE; // Class object that represents an int
c = Double.TYPE; // etc; see also Short, Character, Long, Float
// Express a class literal as a type name followed by ".class"
c = int.class; // Same as Integer.TYPE
c = String.class; // Same as "dummystring".getClass()
c = byte[].class; // Type of byte arrays
c = Class[][].class; // Type of array of arrays of Class objects
Once you have a Class object, you can perform
some interesting reflective operations with it:
import java.lang.reflect.*;
Object o; // Some unknown object to investigate
Class c = o.getClass(); // Get its type
// If it is an array, figure out its base type
while (c.isArray()) c = c.getComponentType();
// If c is not a primitive type, print its class hierarchy
if (!c.isPrimitive()) {
for(Class s = c; s != null; s = s.getSuperclass())
System.out.println(s.getName() + " extends");
}
// Try to create a new instance of c; this requires a no-arg constructor
Object newobj = null;
try { newobj = c.newInstance(); }
catch (Exception e) {
// Handle InstantiationException, IllegalAccessException
}
// See if the class has a method named setText that takes a single String
// If so, call it with a string argument
try {
Method m = c.getMethod("setText", new Class[] { String.class });
m.invoke(newobj, new Object[] { "My Label" });
} catch(Exception e) { /* Handle exceptions here */ }
Class also provides a simple
mechanism for dynamic class loading in Java. For more complete
control over dynamic class loading, however, you should use a
java.lang.ClassLoader object, typically a
java.net.URLClassLoader. This technique
is useful, for example, when
you want to load a class that is named in a configuration
file instead of being hardcoded into your program:
// Dynamically load a class specified by name in a config file
String classname = // Look up the name of the class
config.getProperty("filterclass", // The property name
"com.davidflanagan.filters.Default"); // A default
try {
Class c = Class.forName(classname); // Dynamically load the class
Object o = c.newInstance(); // Dynamically instantiate it
} catch (Exception e) { /* Handle exceptions */ }
The preceding code works only if the class to be loaded is in the
class path. If this is not the case, you can create a custom
ClassLoader object to load a class from a
path (or URL) you specify yourself:
import java.net.*;
String classdir = config.getProperty("filterDirectory"); // Look up class path
trying {
ClassLoader loader = new URLClassLoader(new URL[] { new URL(classdir) });
Class c = loader.loadClass(classname);
}
catch (Exception e) { /* Handle exceptions */ }
Java 1.3 added the Proxy class and
InvocationHandler interface to the
java.lang.reflect package.
Proxy is a powerful but infrequently used class
that allows you to dynamically create a new class or instance that
implements a specified interface or set of interfaces. It also dispatches invocations of the interface methods to an
InvocationHandler object.
|
Java makes it easy to
define and work with multiple threads of execution within a
program. java.lang.Thread is the
fundamental thread class in the Java API. There are two ways to define a
thread. One is to subclass Thread, override
the run() method, and then instantiate your
Thread subclass. The other is to define a
class that implements the Runnable method
(i.e., define a run() method) and then pass
an instance of this Runnable object to the
Thread() constructor. In either case, the
result is a Thread object, where the
run() method is the body of the thread. When you call the start() method of the
Thread object, the interpreter creates a
new thread to execute the run() method. This new thread continues to run until the
run() method exits, at which point it
ceases to exist. Meanwhile, the original
thread continues running itself, starting with the
statement following the start() method. The
following code demonstrates:
final List list; // Some long unsorted list of objects; initialized elsewhere
/** A Thread class for sorting a List in the background */
class BackgroundSorter extends Thread {
List l;
public BackgroundSorter(List l) { this.l = l; } // Constructor
public void run() { Collections.sort(l); } // Thread body
}
// Create a BackgroundSorter thread
Thread sorter = new BackgroundSorter(list);
// Start it running; the new thread runs the run() method above, while
// the original thread continues with whatever statement comes next.
sorter.start();
// Here's another way to define a similar thread
Thread t = new Thread(new Runnable() { // Create a new thread
public void run() { Collections.sort(list); } // to sort the list of objects.
});
t.start(); // Start it running
Threads can run at different priority levels. A thread at a given priority level does not typically run unless there are no higher-priority threads waiting to run. Here is some code you can use when working with thread priorities:
// Set a thread t to lower-than-normal priority
t.setPriority(Thread.NORM_PRIORITY-1);
// Set a thread to lower priority than the current thread
t.setPriority(Thread.currentThread().getPriority() - 1);
// Threads that don't pause for I/O should explicitly yield the CPU
// to give other threads with the same priority a chance to run.
Thread t = new Thread(new Runnable() {
public void run() {
for(int i = 0; i < data.length; i++) { // Loop through a bunch of data
process(data[i]); // Process it
if ((i % 10) == 0) // But after every 10 iterations,
Thread.yield(); // pause to let other threads run.
}
}
});
Often, threads are used to perform some kind of repetitive
task at a fixed interval. This is particularly true when doing
graphical programming that involves animation or similar
effects. The key to doing this is making a thread sleep,
or stop running for a specified amount of time. This is done with
the static Thread.sleep() method:
public class Clock extends Thread {
java.text.DateFormat f = // How to format the time for this locale
java.text.DateFormat.getTimeInstance(java.text.DateFormat.MEDIUM);
volatile boolean keepRunning = true;
public Clock() { // The constructor
setDaemon(true); // Daemon thread: interpreter can exit while it runs
start(); // This thread starts itself
}
public void run() { // The body of the thread
while(keepRunning) { // This thread runs until asked to stop
String time = f.format(new java.util.Date()); // Current time
System.out.println(time); // Print the time
try { Thread.sleep(1000); } // Wait 1,000 milliseconds
catch (InterruptedException e) {} // Ignore this exception
}
}
// Ask the thread to stop running
public void pleaseStop() { keepRunning = false; }
}
Notice the pleaseStop() method in this
example. You can forcefully terminate a thread by calling its
stop() method, but this method has been
deprecated because a thread that is forcefully stopped can leave
objects it is manipulating in an inconsistent state. If you need
a thread that can be stopped, you should define a method such as
pleaseStop() that stops the thread
in a controlled way.
In Java 1.3, the java.util.Timer and
java.util.TimerTask classes make it even easier
to run repetitive tasks. Here is some code that behaves much like
the previous Clock class:
import java.util.*;
// How to format the time for this locale
final java.text.DateFormat timeFmt =
java.text.DateFormat.getTimeInstance(java.text.DateFormat.MEDIUM);
// Define the time-display task
TimerTask displayTime = new TimerTask() {
public void run() { System.out.println(timeFmt.format(new Date())); }
};
// Create a timer object to run the task (and possibly others)
Timer timer = new Timer();
// Now schedule that task to be run every 1,000 milliseconds, starting now
Timer.schedule(displayTime, 0, 1000);
// To stop the time-display task
displayTime.cancel();
Sometimes one thread needs to stop and wait for
another thread to complete. You can accomplish this with the
join() method:
List list; // A long list of objects to be sorted; initialized elsewhere
// Define a thread to sort the list: lower its priority, so it runs only
// when the current thread is waiting for I/O, and then start it running.
Thread sorter = new BackgroundSorter(list); // Defined earlier
sorter.setPriority(Thread.currentThread.getPriority()-1); // Lower priority
sorter.start(); // Start sorting
// Meanwhile, in this original thread, read data from a file
byte[] data = readData(); // Method defined elsewhere
// Before we can proceed, we need the list to be fully sorted, so
// we must wait for the sorter thread to exit, if it hasn't already.
try { sorter.join(); } catch(InterruptedException e) {}
When using multiple threads, you must be very careful
if you allow more than one thread to access the same data
structure. Consider what would happen if one thread was trying
to loop through the elements of a List while
another thread was sorting those elements. Preventing this
problem is called thread synchronization and is one of the
central problems of multithreaded computing. The basic technique
for preventing two threads from accessing the same object at the
same time is to require a thread to obtain a lock on the
object before the thread can modify it. While any one thread holds the
lock, another thread that requests the lock has to wait until the
first thread is done and releases the lock. Every Java object has the fundamental ability to provide such
a locking capability.
The easiest way to keep objects thread-safe is to declare all
sensitive methods synchronized. A thread must
obtain a lock on an object before it can execute any of its
synchronized methods, which means that no other
thread can execute any other synchronized
method at the same time. (If a static method is declared
synchronized, the thread must obtain a
lock on the class, and this works in the same manner.)
To do finer-grained locking, you can specify
synchronized blocks of code that hold a lock
on a specified object for a short time:
// This method swaps two array elements in a synchronized block
public static void swap(Object[] array, int index1, int index2) {
synchronized(array) {
Object tmp = array[index1];
array[index1] = array[index2];
array[index2] = tmp;
}
}
// The Collection, Set, List, and Map implementations in java.util do
// not have synchronized methods (except for the legacy implementations
// Vector and Hashtable). When working with multiple threads, you can
// obtain synchronized wrapper objects.
List synclist = Collections.synchronizedList(list);
Map syncmap = Collections.synchronizedMap(map);
When you are synchronizing threads, you must be careful to avoid deadlock, which occurs when two threads end up waiting for each other to release a lock they need. Since neither can proceed, neither one can release the lock it holds, and they both stop running:
// When two threads try to lock two objects, deadlock can occur unless
// they always request the locks in the same order.
final Object resource1 = new Object(); // Here are two objects to lock
final Object resource2 = new Object();
Thread t1 = new Thread(new Runnable() { // Locks resource1 then resource2
public void run() {
synchronized(resource1) {
synchronized(resource2) { compute(); }
}
}
});
Thread t2 = new Thread(new Runnable() { // Locks resource2 then resource1
public void run() {
synchronized(resource2) {
synchronized(resource1) { compute(); }
}
}
});
t1.start(); // Locks resource1
t2.start(); // Locks resource2 and now neither thread can progress!
Sometimes a thread needs to stop running and wait until some kind
of event occurs, at which point it is told to continue
running. This is done with the wait() and
notify() methods. These aren't methods of the
Thread class, however; they are methods of
Object. Just as every Java object has a lock
associated with it, every object can maintain a list of waiting
threads. When a thread calls the wait() method
of an object, any locks the thread holds are temporarily released,
and the thread is added to the list of waiting threads for that
object and stops running. When another thread calls the
notify() method of the same object, the object
wakes up one of the waiting threads and allows it to continue
running:
/**
* A queue. One thread calls push() to put an object on the queue.
* Another calls pop() to get an object off the queue. If there is no
* data, pop() waits until there is some, using wait()/notify().
* wait() and notify() must be used within a synchronized method or
* block.
*/
import java.util.*;
public class Queue {
LinkedList q = new LinkedList(); // Where objects are stored
public synchronized void push(Object o) {
q.add(o); // Append the object to the end of the list
this.notify(); // Tell waiting threads that data is ready
}
public synchronized Object pop() {
while(q.size() == 0) {
try { this.wait(); }
catch (InterruptedException e) { /* Ignore this exception */ }
}
return q.remove(0);
}
}
In the examples illustrating the sleep(),
join(), and wait()
methods, you may have noticed that calls to each of these methods
are wrapped in a try statement that catches an
InterruptedException. This is necessary
because the interrupt() method allows one
thread to interrupt the execution of another. Interrupting a
thread is not intended to stop it from executing, but to
wake it up from a blocking state.
If the interrupt() method is called on a thread
that is not blocked, the thread continues running, but
its "interrupt status" is set to indicate that an interrupt has
been requested. A thread can test its own interrupt status by
calling the static Thread.interrupted() method,
which returns true if the thread has been
interrupted and, as a side effect, clears the interrupt status.
One thread can test the interrupt status of another thread with
the instance method isInterrupted(), which
queries the status but does not clear it.
If a thread calls sleep(),
join(), or wait() while its
interrupt status is set, it does not block but
immediately throws an InterruptedException (the
interrupt status is cleared as a side effect of throwing the
exception). Similarly, if the interrupt()
method is called on a thread that is already blocked in a call to
sleep(), join(), or
wait(), that thread stops blocking by
throwing an InterruptedException.
One of the most common times that threads block is while doing
input/output; a thread often has to pause and
wait for data to become available from the filesystem or from
the network. (The java.io,
java.net, and java.nio APIs
for performing I/O operations are discussed later in this
chapter.) Unfortunately, the interrupt()
method does not wake up a thread blocked in an I/O method of
the java.io package. This is one of the
shortcomings of java.io that is cured by the
New I/O API in java.nio. If a thread is
interrupted while blocked in an I/O operation on any channel that
implements
java.nio.channels.InterruptibleChannel,
the channel is closed, the thread's interrupt status is set, and
the thread wakes up by throwing a
java.nio.channels.ClosedByInterruptException.
The same thing happens if a thread tries to call a blocking I/O
method while its interrupt status is set. Similarly, if a thread
is interrupted while it is blocked in the
select() method of a
java.nio.channels.Selector (or if it calls
select() while its interrupt status is set),
select() will stop blocking (or will never
start) and will return immediately. No exception is thrown in this
case; the interrupted thread simply wakes up, and the
select() call returns.
The java.io.File class represents a file or a
directory and defines a number of important methods for
manipulating files and directories. Note, however, that
none of these methods allow you to read the contents of a file;
that is the job of java.io.FileInputStream, which is
just one of the many types of I/O streams used in Java
and discussed in the next section. Here are some things you
can do with File:
import java.io.*;
import java.util.*;
// Get the name of the user's home directory and represent it with a File
File homedir = new File(System.getProperty("user.home"));
// Create a File object to represent a file in that directory
File f = new File(homedir, ".configfile");
// Find out how big a file is and when it was last modified
long filelength = f.length();
Date lastModified = new java.util.Date(f.lastModified());
// If the file exists, is not a directory, and is readable,
// move it into a newly created directory.
if (f.exists() && f.isFile() && f.canRead()) { // Check config file
File configdir = new File(homedir, ".configdir"); // A new config directory
configdir.mkdir(); // Create that directory
f.renameTo(new File(configdir, ".config")); // Move the file into it
}
// List all files in the home directory
String[] allfiles = homedir.list();
// List all files that have a ".java" suffix
String[] sourcecode = homedir.list(new FilenameFilter() {
public boolean accept(File d, String name) { return name.endsWith(".java"); }
});
The File class provides some important additional
functionality as of Java 1.2:
// List all filesystem root directories; on Windows, this gives us
// File objects for all drive letters (Java 1.2 and later).
File[] rootdirs = File.listRoots();
// Atomically, create a lock file, then delete it (Java 1.2 and later)
File lock = new File(configdir, ".lock");
if (lock.createNewFile()) {
// We successfully created the file, so do something
...
// Then delete the lock file
lock.delete();
}
else {
// We didn't create the file; someone else has a lock
System.err.println("Can't create lock file; exiting.");
System.exit(1);
}
// Create a temporary file to use during processing (Java 1.2 and later)
File temp = File.createTempFile("app", ".tmp"); // Filename prefix and suffix
// Make sure file gets deleted when we're done with it (Java 1.2 and later)
temp.deleteOnExit();
The
java.io package also defines a
RandomAccessFile class that allows you to read
binary data from arbitrary locations in a file. This can be
a useful thing to do in certain situations,
but most applications read files
sequentially, using the stream classes described in the next
section. Here is a short example of using
Random-AccessFile:
// Open a file for read/write ("rw") access
File datafile = new File(configdir, "datafile");
RandomAccessFile f = new RandomAccessFile(datafile, "rw");
f.seek(100); // Move to byte 100 of the file
byte[] data = new byte[100]; // Create a buffer to hold data
f.read(data); // Read 100 bytes from the file
int i = f.readInt(); // Read a 4-byte integer from the file
f.seek(100); // Move back to byte 100
f.writeInt(i); // Write the integer first
f.write(data); // Then write the 100 bytes
f.close(); // Close file when done with it
The java.io package
defines a large number of classes for reading and writing
streaming, or sequential, data. The
InputStream and OutputStream
classes are for reading and writing streams of bytes, while the
Reader and Writer classes
are for reading and writing streams of characters. Streams can be
nested, meaning you might read characters from a
FilterReader object that reads and processes
characters from an underlying Reader stream. This underlying Reader stream might read bytes from
an InputStream and convert them to
characters.
There are a number of common operations you can perform with streams. One is to read lines of input the user types at the console:
import java.io.*;
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
System.out.print("What is your name: ");
String name = null;
try {
name = console.readLine();
}
catch (IOException e) { name = "<" + e + ">"; } // This should never happen
System.out.println("Hello " + name);
Reading lines of text from a file is a similar operation. The following code reads an entire text file and quits when it reaches the end:
String filename = System.getProperty("user.home") + File.separator + ".cshrc";
try {
BufferedReader in = new BufferedReader(new FileReader(filename));
String line;
while((line = in.readLine()) != null) { // Read line, check for end-of-file
System.out.println(line); // Print the line
}
in.close(); // Always close a stream when you are done with it
}
catch (IOException e) {
// Handle FileNotFoundException, etc. here
}
Throughout this book, you've seen the use of the
System.out.println() method to display text on
the console. System.out simply refers to an output
stream. You can print text to any output stream using similar
techniques. The following code
shows how to output text to a file:
try {
File f = new File(homedir, ".config");
PrintWriter out = new PrintWriter(new FileWriter(f));
out.println("## Automatically generated config file. DO NOT EDIT!");
out.close(); // We're done writing
}
catch (IOException e) { /* Handle exceptions */ }
Not all files contain text, however. The following lines of code treat a file as a stream of bytes and read the bytes into a large array:
try {
File f; // File to read; initialized elsewhere
int filesize = (int) f.length(); // Figure out the file size
byte[] data = new byte[filesize]; // Create an array that is big enough
// Create a stream to read the file
DataInputStream in = new DataInputStream(new FileInputStream(f));
in.readFully(data); // Read file contents into array
in.close();
}
catch (IOException e) { /* Handle exceptions */ }
Various other packages of the Java platform define specialized stream
classes that operate on streaming data in some useful way. The
following code shows how to use stream classes from
java.util.zip to compute a
checksum of data and then compress the data while writing it to a
file:
import java.io.*;
import java.util.zip.*;
try {
File f; // File to write to; initialized elsewhere
byte[] data; // Data to write; initialized elsewhere
Checksum check = new Adler32(); // An object to compute a simple checksum
// Create a stream that writes bytes to the file f
FileOutputStream fos = new FileOutputStream(f);
// Create a stream that compresses bytes and writes them to fos
GZIPOutputStream gzos = new GZIPOutputStream(fos);
// Create a stream that computes a checksum on the bytes it writes to gzos
CheckedOutputStream cos = new CheckedOutputStream(gzos, check);
cos.write(data); // Now write the data to the nested streams
cos.close(); // Close down the nested chain of streams
long sum = check.getValue(); // Obtain the computed checksum
}
catch (IOException e) { /* Handle exceptions */ }
The java.util.zip package also contains a
ZipFile class that gives you random access to
the entries of a ZIP archive and allows you to read those entries
through a stream:
import java.io.*;
import java.util.zip.*;
String filename; // File to read; initialized elsewhere
String entryname; // Entry to read from the ZIP file; initialized elsewhere
ZipFile zipfile = new ZipFile(filename); // Open the ZIP file
ZipEntry entry = zipfile.getEntry(entryname); // Get one entry
InputStream in = zipfile.getInputStream(entry); // A stream to read the entry
BufferedInputStream bis = new BufferedInputStream(in); // Improves efficiency
// Now read bytes from bis...
// Print out contents of the ZIP file
for(java.util.Enumeration e = zipfile.entries(); e.hasMoreElements();) {
ZipEntry zipentry = (ZipEntry) e.nextElement();
System.out.println(zipentry.getName());
}
If you need to compute a cryptographic-strength checksum (also
knows as a message digest), use one of the stream classes of the
java.security package. For example:
import java.io.*;
import java.security.*;
import java.util.*;
File f; // File to read and compute digest on; initialized elsewhere
List text = new ArrayList(); // We'll store the lines of text here
// Get an object that can compute an SHA message digest
MessageDigest digester = MessageDigest.getInstance("SHA");
// A stream to read bytes from the file f
FileInputStream fis = new FileInputStream(f);
// A stream that reads bytes from fis and computes an SHA message digest
DigestInputStream dis = new DigestInputStream(fis, digester);
// A stream that reads bytes from dis and converts them to characters
InputStreamReader isr = new InputStreamReader(dis);
// A stream that can read a line at a time
BufferedReader br = new BufferedReader(isr);
// Now read lines from the stream
for(String line; (line = br.readLine()) != null; text.add(line)) ;
// Close the streams
br.close();
// Get the message digest
byte[] digest = digester.digest();
So far, we've used a variety of stream classes
to manipulate streaming data, but the data itself ultimately comes
from a file or is written to the console. The
java.io package defines other
stream classes that can
read data from and write data to arrays of bytes or strings of
text:
import java.io.*;
// Set up a stream that uses a byte array as its destination
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
out.writeUTF("hello"); // Write some string data out as bytes
out.writeDouble(Math.PI); // Write a floating-point value out as bytes
byte[] data = baos.toByteArray(); // Get the array of bytes we've written
out.close(); // Close the streams
// Set up a stream to read characters from a string
Reader in = new StringReader("Now is the time!");
// Read characters from it until we reach the end
int c;
while((c = in.read()) != -1) System.out.print((char) c);
Other classes that operate this way include
ByteArrayInputStream, StringWriter,
CharArrayReader, and CharArrayWriter.
PipedInputStream and
PipedOutputStream and their character-based
counterparts, PipedReader and
PipedWriter, are
another interesting set of streams defined by
java.io. These streams are used in pairs
by two threads that want to communicate. One thread writes bytes to a
PipedOutputStream or characters to a
PipedWriter, and another thread reads bytes or
characters from the corresponding
PipedInputStream or
PipedReader:
// A pair of connected piped I/O streams forms a pipe. One thread writes
// bytes to the PipedOutputStream, and another thread reads them from the
// corresponding PipedInputStream. Or use PipedWriter/PipedReader for chars.
final PipedOutputStream writeEndOfPipe = new PipedOutputStream();
final PipedInputStream readEndOfPipe = new PipedInputStream(writeEndOfPipe);
// This thread reads bytes from the pipe and discards them
Thread devnull = new Thread(new Runnable() {
public void run() {
try { while(readEndOfPipe.read() != -1); }
catch (IOException e) {} // ignore it
}
});
devnull.start();
One of the most important features of the
java.io package is the ability to
serialize objects: to convert an object into a stream of bytes that can
later be deserialized back into a copy of the original object. The following code shows how to use serialization to save an
object to a file and later read it back:
Object o; // The object we are serializing; it must implement Serializable
File f; // The file we are saving it to
try {
// Serialize the object
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
oos.writeObject(o);
oos.close();
// Read the object back in
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
Object copy = ois.readObject();
ois.close();
}
catch (IOException e) { /* Handle input/output exceptions */ }
catch (ClassNotFoundException cnfe) { /* readObject() can throw this */ }
The previous example serializes to a file, but remember, you can write
serialized objects to any type of stream. Thus, you can write an
object to a byte array, then read it back from the byte array,
creating a deep copy of the object. You can write the
object's bytes to a compression stream or even write
the bytes to a stream connected across a network to another
program!
Java 1.4 introduces a new serialization mechanism intended for
use with JavaBeans components. java.io
serialization works by saving the state of the internal fields
of an object. java.beans persistence, on the
other hand, works by saving a bean's state as a sequence of calls
to the public methods defined by the class. Since it is based
on the public API rather than on the internal state, the JavaBeans
persistence mechanism allows interoperability between different
implementations of the same API, handles version skew more
robustly, and is suitable for longer-term storage of serialized
objects.
A bean and any descendant beans or other objects
serialized with java.beans.XMLEncoder and
can be deserialized with
java.beans.XMLDecoder. These classes write
to and read from specified streams, but they are not stream
classes themselves. Here is how you might encode a bean:
// Create a JavaBean, and set some properties on it
javax.swing.JFrame bean = new javax.swing.JFrame("PersistBean");
bean.setSize(300, 300);
// Now save its encoded form to the file bean.xml
BufferedOutputStream out = // Create an output stream
new BufferedOutputStream(new FileOutputStream("bean.xml"));
XMLEncoder encoder = new XMLEncoder(out); // Create encoder for stream
encoder.writeObject(bean); // Encode the bean
encoder.close(); // Close encoder and stream
Here is the corresponding code to decode the bean from its
serialized form:
BufferedInputStream in = // Create input stream
new BufferedInputStream(new FileInputStream("bean.xml"));
XMLDecoder decoder = new XMLDecoder(in); // Create decoder for stream
Object b = decoder.readObject(); // Decode a bean
decoder.close(); // Close decoder and stream
bean = (javax.swing.JFrame) b; // Cast bean to proper type
bean.setVisible(true); // Start using it
The java.net package defines a number of
classes that make writing networked applications surprisingly
easy. Various examples follow.
The easiest networking class to use is
URL, which represents a uniform resource
locator. Different Java implementations may support different
sets of URL protocols, but, at a minimum, you can rely on support
for the http://, ftp://, and
file:// protocols. In Java 1.4, secure HTTP is
also supported with the https:// protocol.
Here are some ways you can
use the URL class:
import java.net.*;
import java.io.*;
// Create some URL objects
URL url=null, url2=null, url3=null;
try {
url = new URL("http://www.oreilly.com"); // An absolute URL
url2 = new URL(url, "catalog/books/javanut4/"); // A relative URL
url3 = new URL("http:", "www.oreilly.com", "index.html");
} catch (MalformedURLException e) { /* Ignore this exception */ }
// Read the content of a URL from an input stream
InputStream in = url.openStream();
// For more control over the reading process, get a URLConnection object
URLConnection conn = url.openConnection();
// Now get some information about the URL
String type = conn.getContentType();
String encoding = conn.getContentEncoding();
java.util.Date lastModified = new java.util.Date(conn.getLastModified());
int len = conn.getContentLength();
// If necessary, read the contents of the URL using this stream
InputStream in = conn.getInputStream();
Sometimes you need more control over your networked application
than is possible with the URL class. In this
case, you can use a Socket to communicate
directly with a server. For example:
import java.net.*;
import java.io.*;
// Here's a simple client program that connects to a web server,
// requests a document, and reads the document from the server.
String hostname = "java.oreilly.com"; // The server to connect to
int port = 80; // Standard port for HTTP
String filename = "/index.html"; // The file to read from the server
Socket s = new Socket(hostname, port); // Connect to the server
// Get I/O streams we can use to talk to the server
InputStream sin = s.getInputStream();
BufferedReader fromServer = new BufferedReader(new InputStreamReader(sin));
OutputStream sout = s.getOutputStream();
PrintWriter toServer = new PrintWriter(new OutputStreamWriter(sout));
// Request the file from the server, using the HTTP protocol
toServer.print("GET " + filename + " HTTP/1.0\r\n\r\n");
toServer.flush();
// Now read the server's response, assume it is a text file, and print it out
for(String l = null; (l = fromServer.readLine()) != null; )
System.out.println(l);
// Close everything down when we're done
toServer.close();
fromServer.close();
s.close();
In Java 1.4, the Java Secure Socket Extension, or JSSE, has been
added to the core Java platform in the packages
javax.net and javax.net.ssl.[1]
This API enables encrypted network communication over sockets that
use the SSL (Secure Sockets Layer, also known as TLS) protocol.
SSL is widely used on the Internet: it is the basis for secure web
communication using the https:// protocol. In
Java 1.4 and later, you can use https:// with
the URL class as previously shown to securely
download documents from web servers that support SSL.
[1] An earlier version of JSSE using different package names is available as a separate download for use with Java 1.2 and Java 1.3. See http://java.sun.com/products/jsse/.
Like all Java security APIs, JSSE is highly configurable and
gives low-level control over all details of setting up and
communicating over an SSL socket. The
javax.net and javax.net.ssl
packages are fairly complex, but in practice, there are only a few
classes you need to use to securely communicate with a
server. The following program is a variant on the preceding code that
uses HTTPS instead of HTTP to securely transfer the contents of
the requested URL:
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
import java.security.cert.*;
/**
* Get a document from a web server using HTTPS. Usage:
* java HttpsDownload <hostname> <filename>
**/
public class HttpsDownload {
public static void main(String[] args) throws IOException {
// Get a SocketFactory object for creating SSL sockets
SSLSocketFactory factory =
(SSLSocketFactory) SSLSocketFactory.getDefault();
// Use the factory to create a secure socket connected to the
// HTTPS port of the specified web server.
SSLSocket sslsock=(SSLSocket)factory.createSocket(args[0], // Hostname
443); // HTTPS port
// Get the certificate presented by the web server
SSLSession session = sslsock.getSession();
X509Certificate cert;
try { cert = (X509Certificate)session.getPeerCertificates()[0]; }
catch(SSLPeerUnverifiedException e) { // If no or invalid certificate
System.err.println(session.getPeerHost() +
" did not present a valid certificate.");
return;
}
// Display details about the certificate
System.out.println(session.getPeerHost() +
" has presented a certificate belonging to:");
System.out.println("\t[" + cert.getSubjectDN().getName() + "]");
System.out.println("The certificate bears the valid signature of:");
System.out.println("\t[" + cert.getIssuerDN().getName() + "]");
// If the user does not trust the certificate, abort
System.out.print("Do you trust this certificate (y/n)? ");
System.out.flush();
BufferedReader console =
new BufferedReader(new InputStreamReader(System.in));
if (Character.toLowerCase(console.readLine().charAt(0)) != 'y') return;
// Now use the secure socket just as you would use a regular socket
// First, send a regular HTTP request over the SSL socket
PrintWriter out = new PrintWriter(sslsock.getOutputStream());
out.print("GET " + args[1] + " HTTP/1.0\r\n\r\n");
out.flush();
// Next, read the server's response and print it to the console
BufferedReader in =
new BufferedReader(new InputStreamReader(sslsock.getInputStream()));
String line;
while((line = in.readLine()) != null) System.out.println(line);
// Finally, close the socket
sslsock.close();
}
}
A client application uses a Socket to
communicate with a server. The server does the same thing: it uses
a Socket object to communicate with each of its
clients. However, the server has an additional task, in that it
must be able to recognize and accept client connection
requests. This is done with the ServerSocket
class. The following code shows how you might use a
ServerSocket. The code implements a simple HTTP
server that responds to all requests by sending back (or
mirroring) the exact contents of the HTTP request. A dummy server
like this is useful when debugging HTTP clients:
import java.io.*;
import java.net.*;
public class HttpMirror {
public static void main(String[] args) {
try {
int port = Integer.parseInt(args[0]); // The port to listen on
ServerSocket ss = new ServerSocket(port); // Create a socket to listen
for(;;) { // Loop forever
Socket client = ss.accept(); // Wait for a connection
ClientThread t = new ClientThread(client); // A thread to handle it
t.start(); // Start the thread running
} // Loop again
}
catch (Exception e) {
System.err.println(e.getMessage());
System.err.println("Usage: java HttpMirror <port>;");
}
}
static class ClientThread extends Thread {
Socket client;
ClientThread(Socket client) { this.client = client; }
public void run() {
try {
// Get streams to talk to the client
BufferedReader in =
new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out =
new PrintWriter(new OutputStreamWriter(client.getOutputStream()));
// Send an HTTP response header to the client
out.print("HTTP/1.0 200\r\nContent-Type: text/plain\r\n\r\n");
// Read the HTTP request from the client and send it right back
// Stop when we read the blank line from the client that marks
// the end of the request and its headers.
String line;
while((line = in.readLine()) != null) {
if (line.length() == 0) break;
out.println(line);
}
out.close();
in.close();
client.close();
}
catch (IOException e) { /* Ignore exceptions */ }
}
}
}
This server code could be modified using JSSE to support SSL connections. Making a server secure is more complex than making a client secure, however, because a server must have a certificate it can present to the client. Therefore, server-side JSSE is not demonstrated here.
Both URL and
Socket perform networking on top of a
stream-based network connection. Setting up and maintaining a
stream across a network takes work at the network level, however. Sometimes you need a low-level way to speed a packet of data
across a network, but you don't care about maintaining a stream. If, in addition, you don't need a guarantee that your data
will get there or that the packets of data will arrive in the
order you sent them, you may be interested in the
DatagramSocket and
DatagramPacket classes:
import java.net.*;
// Send a message to another computer via a datagram
try {
String hostname = "host.example.com"; // The computer to send the data to
InetAddress address = // Convert the DNS hostname
InetAddress.getByName(hostname); // to a lower-level IP address.
int port = 1234; // The port to connect to
String message = "The eagle has landed."; // The message to send
byte[] data = message.getBytes(); // Convert string to bytes
DatagramSocket s = new DatagramSocket(); // Socket to send message with
DatagramPacket p = // Create the packet to send
new DatagramPacket(data, data.length, address, port);
s.send(p); // Now send it!
s.close(); // Always close sockets when done
}
catch (UnknownHostException e) {} // Thrown by InetAddress.getByName()
catch (SocketException e) {} // Thrown by new DatagramSocket()
catch (java.io.IOException e) {} // Thrown by DatagramSocket.send()
// Here's how the other computer can receive the datagram
try {
byte[] buffer = new byte[4096]; // Buffer to hold data
DatagramSocket s = new DatagramSocket(1234); // Socket that receives it
// through
DatagramPacket p =
new DatagramPacket(buffer, buffer.length); // The packet that receives it
s.receive(p); // Wait for a packet to arrive
String msg = // Convert the bytes from the
new String(buffer, 0, p.getLength()); // packet back to a string.
s.close(); // Always close the socket
}
catch (SocketException e) {} // Thrown by new DatagramSocket()
catch (java.io.IOException e) {} // Thrown by DatagramSocket.receive()
java.util.Properties is a subclass of
java.util.Hashtable, a legacy collections class
that predates the Collections API of Java 1.2. A
Properties object maintains a mapping between
string keys and string values and defines methods that allow the
mappings to be written to and read from a simply formatted text
file. This makes the Properties class ideal for
configuration and user preference files. The
Properties class is also used for the system
properties returned by System.get- Property():
import java.util.*;
import java.io.*;
// Note: many of these system properties calls throw a security exception if
// called from untrusted code such as applets.
String homedir = System.getProperty("user.home"); // Get a system property
Properties sysprops = System.getProperties(); // Get all system properties
// Print the names of all defined system properties
for(Enumeration e = sysprops.propertyNames(); e.hasMoreElements();)
System.out.println(e.nextElement());
sysprops.list(System.out); // Here's an even easier way to list the properties
// Read properties from a configuration file
Properties options = new Properties(); // Empty properties list
File configfile = new File(homedir, ".config"); // The configuration file
try {
options.load(new FileInputStream(configfile)); // Load props from the file
} catch (IOException e) { /* Handle exception here */ }
// Query a property ("color"), specifying a default ("gray") if undefined
String color = options.getProperty("color", "gray");
// Set a property named "color" to the value "green"
options.setProperty("color", "green");
// Store the contents of the Properties object back into a file
try {
options.store(new FileOutputStream(configfile), // Output stream
"MyApp Config File"); // File header comment text
} catch (IOException e) { /* Handle exception */ }
Java 1.4 introduces a new Preferences API, which is specifically
tailored for working with user and systemwide preferences and is
more useful than Properties for this purpose. The Preferences API
is defined by the java.util.prefs package. The
key class in that package is Preferences.
You can obtain a Preferences object that contains
user-specific preferences with the static method
Preferences.userNodeForPackage() and obtain a
Preferences object that contains systemwide
preferences with
Preferences.systemNodeForPackage(). Both
methods take a java.lang.Class object as their
sole argument and return a Preferences object
shared by all classes in that package. (This means that
the preference names you use must be unique within the package.)
Once you have a Preferences object, use the
get() method to query the string value of a
named preference, or use other type-specific methods such as
getInt(), getBoolean(), and
getByteArray(). Note that to query
preference values, a default value must be passed for all methods. This
default value is returned if no preference with the specified name
has been registered or if the file or database that holds the
preference data cannot be accessed. A typical use
of Preferences is the following:
package com.davidflanagan.editor;
import java.util.prefs.Preferences;
public class TextEditor {
// Fields to be initialized from preference values
public int width; // Screen width in columns
public String dictionary; // Dictionary name for spell checking
public void initPrefs() {
// Get Preferences objects for user and system preferences for this package
Preferences userprefs = Preferences.userNodeForPackage(TextEditor.class);
Preferences sysprefs = Preferences.systemNodeForPackage(TextEditor.class);
// Look up preference values. Note that you always pass a default value.
width = userprefs.getInt("width", 80);
// Look up a user preference using a system preference as the default
dictionary = userprefs.get("dictionary"
sysprefs.get("dictionary",
"default_dictionary"));
}
}
In addition to the get() methods for querying
preference values, there are corresponding
put() methods for setting the values of named
preferences:
// User has indicated a new preference, so store it
userprefs.putBoolean("autosave", false);
If your application wants to be notified of user or
system preference changes while the application is in progress, it
may register a PreferenceChangeListener with
addPreferenceChangeListener(). A
Preferences object can export the names and
values of its preferences as an XML file and can read
preferences from such an XML file. (See
importPreferences(), exportNode(), and
exportSubtree() in java.util.pref.Preferences in .)
Preferences objects exist in a hierarchy that
typically corresponds to the hierarchy of package names. Methods
for navigating this hierarchy exist but are not typically used by
ordinary applications.
Another new feature of Java 1.4 is the Logging API, defined in the
java.util.logging package. Typically,
the application developer uses a Logger object
with a name that corresponds to the class or package name of the
application to generate log messages at any of seven severity
levels (see the Level class in ). These messages
may report errors and warnings or provide informational
messages about interesting events in the application's life cycle.
They can include debugging information or even trace the execution
of important methods within the program.
The system administrator
or end user of the application is responsible for setting up a
logging configuration file that specifies where log messages are
directed (the console, a file, a network socket, or a combination
of these), how they are formatted (as plain text or XML documents),
and at what severity threshold they are logged (log messages with
a severity below the specified threshold are discarded with very
little overhead and should not significantly impact the
performance of the application). The logging level
severity threshold can be configured independently for each named
Logger. This end-user configurability means
that you can write programs to output diagnostic messages that
are normally discarded but can be logged during program
development or when a problem arises in a deployed application.
Logging is particularly useful for applications such as servers that
run unattended and do not have a graphical user interface.
For most applications, using the Logging API is quite simple.
Obtain a named Logger object whenever necessary
by calling the static Logger.getLogger()
method, passing the class or package name of the application as
the logger name. Then, use one of the many
Logger instance methods to generate log
messages. The easiest methods to use have names that correspond
to severity levels, such as severe(),
warning(), and info():
import java.util.logging.*;
// Get a Logger object named after the current package
Logger logger = Logger.getLogger("com.davidflanagan.servers.pop");
logger.info("Starting server."); // Log an informational message
ServerSocket ss; // Do some stuff
try { ss = new ServerSocket(110); }
catch(Exception e) { // Log exceptions
logger.log(Level.SEVERE, "Can't bind port 110", e); // Complex log message
logger.warning("Exiting"); // Simple warning
return;
}
logger.fine("got server socket"); // Low-severity (fine-detail) debug message
|
Java 1.4 introduces an entirely new API for high-performance, nonblocking I/O and networking. This API consists
primarily of three new packages. java.nio
defines Buffer classes that are used to store
sequences of bytes or other primitive values.
java.nio.channels defines
channels through which data can be transferred
between a buffer and a data source or sink, such as a file or
a network socket. This package also contains important classes
used for nonblocking I/O. Finally, the
java.nio.charset package contains classes for
efficiently converting buffers of bytes into buffers of
characters. The subsections that follow contain examples of
using all three of these packages, as well as examples of specific
I/O tasks with the New I/O API.
The java.nio package includes an abstract
Buffer class, which defines generic operations
on buffers. This package also defines type-specific subclasses such as
ByteBuffer, CharBuffer, and
IntBuffer.
(See Buffer and ByteBuffer
in for details on these classes and their
various methods.) The following code illustrates typical sequences
of buffer operations on a ByteBuffer.
The other type-specific buffer classes have similar methods.
import java.nio.*;
// Buffers don't have public constructors. They are allocated instead.
ByteBuffer b = ByteBuffer.allocate(4096); // Create a buffer for 4,096 bytes
// Or do this to try to get an efficient buffer from the low-level OS
ByteBuffer buf2 = ByteBuffer.allocateDirect(65536);
// Here's another way to get a buffer: by "wrapping" an array
byte[] data; // Assume this array is created and initialized elsewhere
ByteBuffer buf3 = ByteBuffer.wrap(data); // Create buffer that uses the array
// It is also possible to create a "view buffer" to view bytes as other types
buf3.order(ByteOrder.BIG_ENDIAN); // Specify the byte order for the buffer
IntBuffer ib = buf3.asIntBuffer(); // View those bytes as integers
// Now store some data in the buffer
b.put(data); // Copy bytes from array to buffer at current position
b.put((byte)42); // Store another byte at the new current position
b.put(0, (byte)9); // Overwrite first byte in buffer. Don't change position.
b.order(ByteOrder.BIG_ENDIAN); // Set the byte order of the buffer
b.putChar('x'); // Store the two bytes of a Unicode character in buffer
b.putInt(0xcafebabe); // Store four bytes of an int into the buffer
// Here are methods for querying basic numbers about a buffer
int capacity = b.capacity(); // How many bytes can the buffer hold? (4,096)
int position = b.position(); // Where will the next byte be written or read?
// A buffer's limit specifies how many bytes of the buffer can be used.
// When writing into a buffer, this should be the capacity. When reading data
// from a buffer, it should be the number of bytes that were previously
// written.
int limit = b.limit(); // How many should be used?
int remaining = b.remaining(); // How many left? Return limit-position.
boolean more=b.hasRemaining(); // Test if there is still room in the buffer
// The position and limit can also be set with methods of the same name
// Suppose you want to read the bytes you've written into the buffer
b.limit(b.position()); // Set limit to current position
b.position(0); // Set limit to 0; start reading at beginning
// Instead of the two previous calls, you usually use a convenience method
b.flip(); // Set limit to position and position to 0; prepare for reading
b.rewind(); // Set position to 0; don't change limit; prepare for rereading
b.clear(); // Set position to 0 and limit to capacity; prepare for writing
// Assuming you've called flip(), you can start reading bytes from the buffer
buf2.put(b); // Read all bytes from b and put them into buf2
b.rewind(); // Rewind b for rereading from the beginning
byte b0 = b.get(); // Read first byte; increment buffer position
byte b1 = b.get(); // Read second byte; increment buffer position
byte[] fourbytes = new byte[4];
b.get(fourbytes); // Read next four bytes, add 4 to buffer position
byte b9 = b.get(9); // Read 10th byte, without changing current position
int i = b.getInt(); // Read next four bytes as an integer; add 4 to position
// Discard bytes you've already read; shift the remaining ones to the beginning
// of the buffer; set position to new limit and limit to capacity, preparing
// the buffer for writing more bytes into it.
b.compact();
You may notice that many buffer methods return the object on which they operate. This is done so that method calls can be "chained" in code, as follows:
ByteBuffer bb=ByteBuffer.allocate(32).order(ByteOrder.BIG_ENDIAN).putInt(1234);
Many methods throughout
java.nio and its subpackages return the current object to
enable this kind of method chaining. Note that the use of this
kind of chaining is a stylistic choice (which I have avoided in
this chapter) and does not have any significant impact on
efficiency.
ByteBuffer is the most important of the
buffer classes. However, another commonly used class is
CharBuffer. CharBuffer
objects can be created by wrapping a string and can also be
converted to strings. CharBuffer
implements the new java.lang.CharSequence
interface, which means that it can be used like a
String or StringBuffer in
certain applications, (e.g., for regular expression pattern
matching.
// Create a read-only CharBuffer from a string
CharBuffer cb = CharBuffer.wrap("This string is the data for the CharBuffer");
String s = cb.toString(); // Convert to a String with toString() method
System.out.println(cb); // or rely on an implicit call to toString().
char c = cb.charAt(0); // Use CharSequence methods to get characters
char d = cb.get(1); // or use a CharBuffer absolute read.
// A relative read that reads the char and increments the current position
// Note that only the characters between the position and limit are used when
// a CharBuffer is converted to a String or used as a CharSequence.
char e = cb.get();
Bytes in a ByteBuffer are commonly converted to characters in a
CharBuffer and vice versa. We'll see how to do this when
we consider the java.nio.charset package.
Buffers are not all that useful on their own -- there isn't
much point in storing bytes into a buffer only to read them out
again. Instead, buffers are typically used with channels:
your program stores bytes into a buffer, then passes the buffer
to a channel, which reads the bytes out of the buffer and writes
them to a file, network socket, or some other destination. Or,
in the reverse, your program passes a buffer to a channel, which
reads bytes from a file, socket, or other source, and stores
those bytes into the buffer, where they can then be retrieved by
your program. The java.nio.channels package
defines several channel classes that represent files, sockets,
datagrams, and pipes. (We'll see examples of these
concrete classes later in this chapter.) The following code, however,
is based on the capabilities of the various channel interfaces
defined by java.nio.channels and should work
with any Channel object:
Channel c; // Object that implements Channel interface; initialized elsewhere
if (c.isOpen()) c.close(); // These are the only methods defined by Channel
// The read() and write() methods are defined by the
// ReadableByteChannel and WritableByteChannel interfaces.
ReadableByteChannel source; // Initialized elsewhere
WritableByteChannel destination; // Initialized elsewhere
ByteBuffer buffer = ByteBuffer.allocateDirect(16384); // Low-level 16 KB buffer
// Here is the basic loop to use when reading bytes from a source channel
// and writing them to a destination channel until there are no more bytes to
// read from the source and no more buffered bytes to write to the destination.
while(source.read(buffer) != -1 || buffer.position() > 0) {
// Flip buffer: set limit to position and position to 0. This prepares
// the buffer for reading (which is done by a channel *write* operation).
buffer.flip();
// Write some or all of the bytes in the buffer to the destination
destination.write(buffer);
// Discard the bytes that were written, copying the remaining ones to
// the start of the buffer. Set position to limit and limit to capacity,
// preparing the buffer for writing (done by a channel *read* operation).
buffer.compact();
}
// Don't forget to close the channels
source.close();
destination.close();
In addition to the ReadableByteChannel and
WritableByteChannel interfaces illustrated in
the preceding code, java.nio.channels
defines several other channel interfaces.
ByteChannel simply extends the readable and
writable interfaces without adding any new methods. It is a
useful shorthand for channels that support both reading and
writing. GatheringByteChannel is an
extension of WritableByteChannel that defines
write() methods that gather bytes from more than
one buffer and write them out. Similarly,
ScatteringByteChannel is an extension of
ReadableByteChannel that defines
read() methods that read bytes from the
channel and scatter or distribute them into more than one
buffer. The gathering and scattering write()
and read() methods can be useful when working
with network protocols that use fixed-size headers that you want
to store in a buffer separate from the rest of the transferred
data.
One confusing point to be aware of is that a channel read
operation involves writing (or putting) bytes into a buffer, and
a channel write operation involves reading (or getting) bytes
from a buffer. Thus, when I say that the
flip() method prepares a buffer for reading,
I mean that it prepares a buffer for use in a channel
write() operation! The reverse is true for
the buffer's compact() method.
A java.nio.charset.Charset object represents
a character set plus an encoding for that character set.
Charset and its associated classes,
CharsetEncoder and
CharsetDecoder, define methods for encoding
strings of characters into sequences of bytes and decoding
sequences of bytes into strings of characters. Since these
classes are part of the New I/O API, they use the
ByteBuffer and CharBuffer
classes:
// The simplest case. Use Charset convenience routines to convert.
Charset charset = Charset.forName("ISO-8859-1"); // Get Latin-1 Charset
CharBuffer cb = CharBuffer.wrap("Hello World"); // Characters to encode
// Encode the characters and store the bytes in a newly allocated ByteBuffer
ByteBuffer bb = charset.encode(cb);
// Decode these bytes into a newly allocated CharBuffer and print them out
System.out.println(charset.decode(bb));
Note the use of the ISO-8859-1 (a.k.a. "Latin-1") charset in this
example. This 8-bit charset is suitable for most Western
European languages, including English. Programmers who work only with English may also use the 7-bit "US-ASCII"
charset. The Charset class does not do
encoding and decoding itself, and the previous convenience routines create CharsetEncoder and
CharsetDecoder classes internally. If you
plan to encode or decode multiple times, it is more
efficient to create these objects yourself:
Charset charset = Charset.forName("US-ASCII"); // Get the charset
CharsetEncoder encoder = charset.newEncoder(); // Create an encoder from it
CharBuffer cb = CharBuffer.wrap("Hello World!"); // Get a CharBuffer
WritableByteChannel destination; // Initialized elsewhere
destination.write(encoder.encode(cb)); // Encode chars and write
The preceding CharsetEncoder.encode() method must allocate a new ByteBuffer each
time it is called. For maximum efficiency, there are
lower-level methods you can call to do the encoding and decoding
into an existing buffer:
ReadableByteChannel source; // Initialized elsewhere
Charset charset = Charset.forName("ISO-8859-1"); // Get the charset
CharsetDecoder decoder = charset.newDecoder(); // Create a decoder from it
ByteBuffer bb = ByteBuffer.allocateDirect(2048); // Buffer to hold bytes
CharBuffer cb = CharBuffer.allocate(2048); // Buffer to hold characters
while(source.read(bb) != -1) { // Read bytes from the channel until EOF
bb.flip(); // Flip byte buffer to prepare for decoding
decoder.decode(bb, cb, true); // Decode bytes into characters
cb.flip(); // Flip char buffer to prepare for printing
System.out.print(cb); // Print the characters
cb.clear(); // Clear char buffer to prepare for decoding
bb.clear(); // Prepare byte buffer for next channel read
}
source.close(); // Done with the channel, so close it
System.out.flush(); // Make sure all output characters appear
The preceding code relies on the fact that ISO-8895-1 is an 8-bit encoding charset and that there is one-to-one mapping between characters and bytes. For more complex charsets, such as the UTF-8 encoding of Unicode or the EUC-JP charset used with Japanese text, however, this does not hold, and more than one byte is required for some (or all) characters. When this is the case, there is no guarantee that all bytes in a buffer can be decoded at once (the end of the buffer may contain a partial character). Also, since a single character may encode to more than one byte, it can be tricky to know how many bytes a given string will encode into. The following code shows a loop you can use to decode bytes in a more general way:
ReadableByteChannel source; // Initialized elsewhere
Charset charset = Charset.forName("UTF-8"); // A Unicode encoding
CharsetDecoder decoder = charset.newDecoder(); // Create a decoder from it
ByteBuffer bb = ByteBuffer.allocateDirect(2048); // Buffer to hold bytes
CharBuffer cb = CharBuffer.allocate(2048); // Buffer to hold characters
// Tell the decoder to ignore errors that might result from bad bytes
decoder.onMalformedInput(CodingErrorAction.IGNORE);
decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
decoder.reset(); // Reset decoder if it has been used before
while(source.read(bb) != -1) { // Read bytes from the channel until EOF
bb.flip(); // Flip byte buffer to prepare for decoding
decoder.decode(bb, cb, false); // Decode bytes into characters
cb.flip(); // Flip char buffer to prepare for printing
System.out.print(cb); // Print the characters
cb.clear(); // Clear the character buffer
bb.compact(); // Discard already decoded bytes
}
source.close(); // Done with the channel, so close it
// At this point, there may still be some bytes in the buffer to decode
bb.flip(); // Prepare for decoding
decoder.decode(bb, cb, true); // Pass true to indicate this is the last call
decoder.flush(cb); // Output any final characters
cb.flip(); // Flip char buffer
System.out.print(cb); // Print the final characters
FileChannel is a concrete
Channel class that performs file I/O and
implements ReadableByteChannel and
WritableByteChannel (although its
read() method works only if the underlying
file is open for reading, and its write()
method works only if the file is open for writing). Obtain a
FileChannel object by using the
java.io package to create a
FileInputStream, a
FileOutputStream, or a
RandomAccessFile, and then call the getChannel() method (new in Java 1.4) of that
object. As an example, you can use two
FileChannel objects to copy a file with code
such as the following:
String filename = "test"; // The name of the file to copy
// Create streams to read the original and write the copy
FileInputStream fin = new FileInputStream(filename);
FileOutputStream fout = new FileOutputStream(filename + ".copy");
// Use the streams to create corresponding channel objects
FileChannel in = fin.getChannel();
FileChannel out = fout.getChannel();
// Allocate a low-level 8KB buffer for the copy
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
while(in.read(buffer) != -1 || buffer.position() > 0) {
buffer.flip(); // Prepare to read from the buffer and write to the file
out.write(buffer); // Write some or all buffer contents
buffer.compact(); // Discard all bytes that were written and prepare to
} // read more from the file and store them in the buffer.
in.close(); // Always close channels and streams when done with them
out.close();
fin.close(); // Note that closing a FileChannel does not automatically
fout.close(); // close the underlying stream.
FileChannel has special
transferTo() and
transferFrom() methods that make it
particularly easy (and on many operating systems, particularly
efficient) to transfer a specified number of bytes from a
FileChannel to some other specified channel,
or from some other channel to a FileChannel.
These methods allow us to simplify the preceding file-copying code
to the following:
FileChannel in, out; // Assume these are initialized as in the
// preceding example.
long numbytes = in.size(); // Number of bytes in original file
in.transferTo(0, numbytes, out); // Transfer that amount to output channel
This code could be equally well-written using
transferFrom() instead of
transferTo() (note that these two methods
expect their arguments in different orders):
long numbytes = in.size();
out.transferFrom(in, 0, numbytes);
FileChannel also has other capabilities that
are not shared by other channel classes. One of the most important
is the ability to "memory map" a file or a portion of a
file, i.e., to obtain a MappedByteBuffer (a
subclass of ByteBuffer) that represents the
contents of the file and allows you to read (and optionally
write) file contents simply by reading from and writing to the
buffer. Memory mapping a file is a somewhat expensive
operation, so this technique is usually efficient only when
you are working with a large file to which you need repeated access. Memory mapping offers you yet another way to perform the
same file-copy operation shown previously:
long filesize = in.size();
ByteBuffer bb = in.map(FileChannel.MapMode.READ_ONLY, 0, filesize);
while(bb.hasRemaining()) out.write(bb);
The channel interfaces defined by
java.nio.channels include
ByteChannel but not
CharChannel. The channel API is low-level
and provides methods for reading bytes only. All of the
previous examples have treated files as binary files. It is
possible to use the CharsetEncoder and
CharsetDecoder classes introduced earlier to
convert between bytes and characters, but when you want to work
with text files, the Reader and
Writer classes of the
java.io package are usually much easier to
use than CharBuffer. Fortunately, the
Channels class defines convenience methods
that bridge between the new and old APIs. Here is code that
wraps a Reader and a
Writer object around input and output
channels, reads lines of Latin-1 text from the input channel, and
writes them back out to the output channel, with the encoding
changed to UTF-8:
ReadableByteChannel in; // Assume these are initialized elsewhere
WritableByteChannel out;
// Create a Reader and Writer from a FileChannel and charset name
BufferedReader reader=new BufferedReader(Channels.newReader(in, "ISO-8859-1"));
PrintWriter writer = new PrintWriter(Channels.newWriter(out, "UTF-8"));
String line;
while((line = reader.readLine()) != null) writer.println(line);
reader.close();
writer.close();
Unlike the FileInputStream and
FileOutputStream classes, the
FileChannel class allows random access to the
contents of the file. The zero-argument
position() method returns the file pointer (the position of the next byte to be read), and the one-argument
position() method allows you to set this
pointer to any value you want. This allows you to skip around
in a file in the way that the
java.io.RandomAccessFile does. Here is an
example:
// Suppose you have a file that has data records scattered throughout, and the
// last 1,024 bytes of the file are an index that provides the position of
// those records. Here is code that reads the index of the file, looks up the
// position of the first record within the file, and then reads that record.
FileChannel in = new FileInputStream("test.data").getChannel(); // The channel
ByteBuffer index = ByteBuffer.allocate(1024); // A buffer to hold the index
long size = in.size(); // The size of the file
in.position(size - 1024); // Position at start of index
in.read(index); // Read the index
int record0Position = index.getInt(0); // Get first index entry
in.position(record0Position); // Position file at that point
ByteBuffer record0 = ByteBuffer.allocate(128); // Get buffer to hold data
in.read(record0); // Finally, read the record
The final feature of FileChannel that we'll
consider here is its ability to lock a file or a portion of
a file against all concurrent access (an exclusive lock) or
against concurrent writes (a shared lock). (Note that some
operating systems strictly enforce all locks, while others only
provide an advisory locking facility that requires programs to
cooperate and to attempt to acquire a lock before reading or
writing portions of a shared file.) In the previous random-access
example, suppose we wanted to ensure that no other program
was modifying the record data while we read it. We could acquire
a shared lock on that portion of the file with the following code:
FileLock lock = in.lock(record0Position, // Start of locked region
128, // Length of locked region
true); // Shared lock: prevent concurrent updates
// but allow concurrent reads.
in.position(record0Position); // Move to start of index
in.read(record0); // Read the index data
lock.release(); // You're done with the lock, so release it
The New I/O API includes networking capabilities as well as
file-access capabilities. To communicate over the network, you
can use the SocketChannel class. Create a
SocketChannel with the static
open() method, then read and write bytes
from and to it as you would with any other channel object. The following
code uses SocketChannel to send an HTTP
request to a web server and saves the server's response
(including all of the HTTP headers) to a file. Note the use of
java.net.InetSocketAddress, a subclass of
java.net.SocketAddress, to tell the
SocketChannel what to connect to. These
classes are also new in Java 1.4 and were introduced as part of
the New I/O API.
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
// Create a SocketChannel connected to the web server at www.oreilly.com
SocketChannel socket =
SocketChannel.open(new InetSocketAddress("www.oreilly.com",80));
// A charset for encoding the HTTP request
Charset charset = Charset.forName("ISO-8859-1");
// Send an HTTP request to the server. Start with a string, wrap it to
// a CharBuffer, encode it to a ByteBuffer, then write it to the socket.
socket.write(charset.encode(CharBuffer.wrap("GET / HTTP/1.0\r\n\r\n")));
// Create a FileChannel to save the server's response to
FileOutputStream out = new FileOutputStream("oreilly.html");
FileChannel file = out.getChannel();
// Get a buffer for holding bytes while transferring from socket to file
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
// Now loop until all bytes are read from the socket and written to the file
while(socket.read(buffer) != -1 || buffer.position() > 0) { // Are we done?
buffer.flip(); // Prepare to read bytes from buffer and write to file
file.write(buffer); // Write some or all bytes to the file
buffer.compact(); // Discard those that were written
}
socket.close(); // Close the socket channel
file.close(); // Close the file channel
out.close(); // Close the underlying file
Another way to create a SocketChannel is with
the no-argument version of open(), which
creates an unconnected channel. This allows you to call the
socket() method to obtain the underlying
socket, configure the socket as desired, and connect to the
desired host with the connect method. For example:
SocketChannel sc = SocketChannel.open(); // Open an unconnected socket channel
Socket s = sc.socket(); // Get underlying java.net.Socket
s.setSOTimeout(3000); // Time out after three seconds
// Now connect the socket channel to the desired host and port
sc.connect(new InetSocketAddress("www.davidflanagan.com", 80));
ByteBuffer buffer = ByteBuffer.allocate(8192); // Create a buffer
try { sc.read(buffer); } // Try to read from socket
catch(SocketTimeoutException e) { // Catch timeouts here
System.out.println("The remote computer is not responding.");
sc.close();
}
In addition to the SocketChannel class, the
java.nio.channels package defines a
DatagramChannel for networking with datagrams
instead of sockets. DatagramChannel is not
demonstrated here, but you can read about it in .
One of the most powerful features of the New I/O API is that
channels such as SocketChannel and
DatagramChannel can be used in nonblocking
mode. We'll see examples of this in later sections.
The java.net package defines a
Socket class for communication between a
client and a server and defines a ServerSocket
class used by the server to listen for and accept connections
from clients. The java.nio.channels package
is analogous: it defines a SocketChannel
class for data transfer and a
ServerSocketChannel class for accepting
connections. ServerSocketChannel is an
unusual channel because it does not implement
ReadableByteChannel or
WritableByteChannel. Instead of
read() and write()
methods, it has an accept() method for
accepting client connections and obtaining a
SocketChannel through which it communicates
with the client. Here is the code for a simple, single-threaded
server that listens for connections on port 8000 and reports the
current time to any client that connects:
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
public class DateServer {
public static void main(String[] args) throws java.io.IOException {
// Get a CharsetEncoder for encoding the text sent to the client
CharsetEncoder encoder = Charset.forName("US-ASCII").newEncoder();
// Create a new ServerSocketChannel and bind it to port 8000
// Note that this must be done using the underlying ServerSocket
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new java.net.InetSocketAddress(8000));
for(;;) { // This server runs forever
// Wait for a client to connect
SocketChannel client = server.accept();
// Get the current date and time as a string
String response = new java.util.Date().toString() + "\r\n";
// Wrap, encode, and send the string to the client
client.write(encoder.encode(CharBuffer.wrap(response)));
// Disconnect from the client
client.close();
}
}
}
The preceding DateServer class is a
simple network server. Because it does not maintain a
connection with any client, it never needs to communicate with
more than one at a time, and there is never more than one
SocketChannel in use. More realistic servers
must be able to communicate with more than one client at a time.
The java.io and java.net
APIs allow only blocking I/O, so servers written using these
APIs must use a separate thread for each client. For large-scale
servers with many clients, this approach does not scale well.
To solve this problem, the New I/O API allows most channels (but
not FileChannel) to be used in nonblocking
mode and allows a single thread to manage all pending
connections. This is done with a Selector
object, which keeps track of a set of registered channels and can
block until one or more of those channels is ready for I/O, as the
following code illustrates. This is a longer example than most in
this chapter, but it is a complete working server class that
manages a ServerSocketChannel and any number
of SocketChannel connections to clients
through a single Selector object.
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.*; // For Set and Iterator
public class NonBlockingServer {
public static void main(String[] args) throws IOException {
// Get the character encoders and decoders you'll need
Charset charset = Charset.forName("ISO-8859-1");
CharsetEncoder encoder = charset.newEncoder();
CharsetDecoder decoder = charset.newDecoder();
// Allocate a buffer for communicating with clients
ByteBuffer buffer = ByteBuffer.allocate(512);
// All of the channels in this code will be in nonblocking mode.
// So create a Selector object that will block while monitoring
// all of the channels and stop blocking only when one or more
// of the channels is ready for I/O of some sort.
Selector selector = Selector.open();
// Create a new ServerSocketChannel and bind it to port 8000
// Note that this must be done using the underlying ServerSocket
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new java.net.InetSocketAddress(8000));
// Put the ServerSocketChannel into nonblocking mode
server.configureBlocking(false);
// Now register it with the Selector (note that register() is called
// on the channel, not on the selector object, however).
// The SelectionKey represents the registration of this channel with
// this Selector.
SelectionKey serverkey = server.register(selector,
SelectionKey.OP_ACCEPT);
for(;;) { // The main server loop. The server runs forever.
// This call blocks until there is activity on one of the
// registered channels. This is the key method in nonblocking
// I/O.
selector.select();
// Get a java.util.Set containing the SelectionKey objects for
// all channels that are ready for I/O.
Set keys = selector.selectedKeys();
// Use a java.util.Iterator to loop through the selected keys
for(Iterator i = keys.iterator(); i.hasNext(); ) {
// Get the next SelectionKey in the set, and remove it
// from the set. It must be removed explicitly, or it will
// be returned again by the next call to select().
SelectionKey key = (SelectionKey) i.next();
i.remove();
// Check whether this key is the SelectionKey obtained when
// you registered the ServerSocketChannel.
if (key == serverkey) {
// Activity on the ServerSocketChannel means a client
// is trying to connect to the server.
if (key.isAcceptable()) {
// Accept the client connection and obtain a
// SocketChannel to communicate with the client.
SocketChannel client = server.accept();
// Put the client channel in nonblocking mode
client.configureBlocking(false);
// Now register it with the Selector object,
// telling it that you'd like to know when
// there is data to be read from this channel.
SelectionKey clientkey =
client.register(selector,
SelectionKey.OP_READ);
// Attach some client state to the key. You'll
// use this state when you talk to the client.
clientkey.attach(new Integer(0));
}
}
else {
// If the key obtained from the Set of keys is not the
// ServerSocketChannel key, then it must be a key
// representing one of the client connections.
// Get the channel from the key.
SocketChannel client = (SocketChannel) key.channel();
// If you are here, there should be data to read from
// the channel, but double-check.
if (!key.isReadable()) continue;
// Now read bytes from the client. Assume that all the
// client's bytes are in one read operation.
int bytesread = client.read(buffer);
// If read() returns -1, it indicates end-of-stream,
// which means the client has disconnected, so
// deregister the selection key and close the channel.
if (bytesread == -1) {
key.cancel();
client.close();
continue;
}
// Otherwise, decode the bytes to a request string
buffer.flip();
String request = decoder.decode(buffer).toString();
buffer.clear();
// Now reply to the client based on the request string
if (request.trim().equals("quit")) {
// If the request was "quit", send a final message
// Close the channel and deregister the
// SelectionKey
client.write(encoder.encode(CharBuffer.
wrap("Bye.")));
key.cancel();
client.close();
}
else {
// Otherwise, send a response string comprised of
// the sequence number of this request plus an
// uppercase version of the request string. Note
// that you keep track of the sequence number by
// "attaching" an Integer object to the
// SelectionKey and incrementing it each time.
// Get sequence number from SelectionKey
int num = ((Integer)key.attachment()).intValue();
// For response string
String response = num + ": " +
request.toUpperCase();
// Wrap, encode, and write the response string
client.write(encoder.encode(CharBuffer.
wrap(response)));
// Attach an incremented sequence nubmer to the key
key.attach(new Integer(num+1));
}
}
}
}
}
}
Nonblocking I/O is most useful for writing network servers.
It is also useful in clients that have more than one network
connection pending at the same time. For example, consider a web browser downloading a web page and the images
referenced by that page at the same time. One other interesting
use of nonblocking I/O is to perform nonblocking socket
connection operations. The idea is that you can ask a
SocketChannel to establish a connection to a
remote host and then go do other stuff (such as build a GUI, for
example) while the underlying OS is setting up the connection
across the network. Later, you do a select()
call to block until the connection has been established, if it
hasn't been already. The code for a nonblocking connect looks
like this:
// Create a new, unconnected SocketChannel. Put it in nonblocking
// mode, register it with a new Selector, and then tell it to connect.
// The connect call will return instead of waiting for the network
// connect to be fully established.
Selector selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_CONNECT);
channel.connect(new InetSocketAddress(hostname, port));
// Now go do other stuff while the connection is set up
// For example, you can create a GUI here
// Now block if necessary until the SocketChannel is ready to connect.
// Since you've registered only one channel with this selector, you
// don't need to examine the key set; you know which channel is ready.
while(selector.select() == 0) /* empty loop */;
// This call is necessary to finish the nonblocking connections
channel.finishConnect();
// Finally, close the selector, which deregisters the channel from it
selector.close();
JAXP, the Java API for XML Processing, was originally defined as an optional extension to the Java platform and was available as a separate download. In Java 1.4, however, JAXP has been made part of the core platform. It consists of the following packages (and their subpackages):
javax.xml.parsersThis package provides high-level interfaces for instantiating SAX and DOM parsers; it is a "pluggability layer" that allows the end user or system administrator to choose or even replace the default parser implementation with another.
javax.xml.transformThis package and its subpackages define a Java API for transforming XML document content and representation using the XSLT standard. This package also provides a pluggability layer that allows new XSLT engines to be "plugged in" and used in place of the default implementation.
org.xml.sax
This package and its two subpackages define the de facto
standard SAX (SAX stands for Simple API for XML) API. SAX
is an event-driven, XML-parsing API: a SAX parser invokes
methods of a specified ContentHandler
object (as well as some other related handler objects) as it parses an XML document. The structure and
content of the document are fully described by the method
calls. This is a streaming API that does not build any
permanent representation of the document. It is up to the
ContentHandler implementation to store
any state or perform any actions that are appropriate. This package includes classes for the SAX 2 API and deprecated classes for SAX 1.
org.w3c.domThis package defines interfaces that represent an XML document in tree form. The Document Object Model (DOM) is a recommendation (essentially a standard) of the World Wide Web Consortium (W3C). A DOM parser reads an XML document and converts it into a tree of nodes that represent the full content of the document. Once the tree representation of the document is created, a program can examine and manipulate it however it wants.
Examples of each of these packages are presented in the following subsections.
The first step in parsing an XML document with SAX is to obtain
a SAX parser. If you have a SAX parser implementation of your
own, you can simply instantiate the appropriate parser class.
It is usually simpler, however, to use the
javax.xml.parsers package to instantiate
whatever SAX parser is provided by the Java implementation. The
code looks like this:
import javax.xml.parsers.*;
// Obtain a factory object for creating SAX parsers
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
// Configure the factory object to specify attributes of the parsers it creates
parserFactory.setValidating(true);
parserFactory.setNamespaceAware(true);
// Now create a SAXParser object
SAXParser parser = parserFactory.newSAXParser(); //May throw exceptions
The SAXParser class is a simple wrapper
around the org.xml.sax.XMLReader class. Once
you have obtained one, as shown in the previous code, you can parse a document
by simply calling one of the various parse()
methods. Some of these methods use the deprecated SAX 1
HandlerBase class, and others use the current
SAX 2 org.xml.sax.helpers.DefaultHandler
class. The DefaultHandler class
provides an empty implementation of all the methods of the ContentHandler,
ErrorHandler, DTDHandler,
and EntityResolver interfaces. These are all the
methods that the SAX parser can call while parsing an XML
document. By subclassing DefaultHandler and
defining the methods you care about, you can perform whatever
actions are necessary in response to the method calls generated
by the parser. The following code shows a method that uses SAX
to parse an XML file and determine the number of XML elements
that appear in a document as well as the number of characters of plain
text (possibly excluding "ignorable whitespace") that appear
within those elements:
import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
public class SAXCount {
public static void main(String[] args)
throws SAXException,IOException, ParserConfigurationException
{
// Create a parser factory and use it to create a parser
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
SAXParser parser = parserFactory.newSAXParser();
// This is the name of the file you're parsing
String filename = args[0];
// Instantiate a DefaultHandler subclass to do your counting for you
CountHandler handler = new CountHandler();
// Start the parser. It reads the file and calls methods of the
// handler.
parser.parse(new File(filename), handler);
// When you're done, report the results stored by your handler object
System.out.println(filename + " contains " + handler.numElements +
" elements and " + handler.numChars +
" other characters ");
}
// This inner class extends DefaultHandler to count elements and text in
// the XML file and saves the results in public fields. There are many
// other DefaultHandler methods you could override, but you need only
// these.
public static class CountHandler extends DefaultHandler {
public int numElements = 0, numChars = 0; // Save counts here
// This method is invoked when the parser encounters the opening tag
// of any XML element. Ignore the arguments but count the element.
public void startElement(String uri, String localname, String qname,
Attributes attributes) {
numElements++;
}
// This method is called for any plain text within an element
// Simply count the number of characters in that text
public void characters(char[] text, int start, int length) {
numChars += length;
}
}
}
The DOM API is much different from the SAX API. While SAX is an efficient way to scan an XML document, it is not well-suited for programs that want to modify documents. Instead of
converting an XML document into a series of method calls, a DOM
parser converts the document into an
org.w3c.dom.Document object, which is a tree
of org.w3c.dom.Node objects. The conversion
of the complete XML document to tree form allows random access
to the entire document but can consume substantial amounts of
memory.
In the DOM API, each node in the document tree implements the
Node interface and a
type-specific subinterface. (The most common types of node in a DOM document are
Element and Text nodes.)
When the parser is done parsing the document, your program can
examine and manipulate that tree using the various methods
of Node and its subinterfaces. The
following code uses JAXP to obtain a DOM parser (which, in JAXP
parlance, is called a DocumentBuilder). It
then parses an XML file and builds a document tree from it.
Next, it examines the Document tree to search
for <sect1> elements and prints the
contents of the <title> of each.
import java.io.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class GetSectionTitles {
public static void main(String[] args)
throws IOException, ParserConfigurationException,
org.xml.sax.SAXException
{
// Create a factory object for creating DOM parsers and configure it
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringComments(true); // We want to ignore comments
factory.setCoalescing(true); // Convert CDATA to Text nodes
factory.setNamespaceAware(false); // No namespaces: this is default
factory.setValidating(false); // Don't validate DTD: also default
// Now use the factory to create a DOM parser, a.k.a. DocumentBuilder
DocumentBuilder parser = factory.newDocumentBuilder();
// Parse the file and build a Document tree to represent its content
Document document = parser.parse(new File(args[0]));
// Ask the document for a list of all <sect1> elements it contains
NodeList sections = document.getElementsByTagName("sect1");
// Loop through those <sect1> elements one at a time
int numSections = sections.getLength();
for(int i = 0; i < numSections; i++) {
Element section = (Element)sections.item(i); // A <sect1>
// The first Element child of each <sect1> should be a <title>
// element, but there may be some whitespace Text nodes first, so
// loop through the children until you find the first element
// child.
Node title = section.getFirstChild();
while(title != null && title.getNodeType() != Node.ELEMENT_NODE)
title = title.getNextSibling();
// Print the text contained in the Text node child of this element
if (title != null)
System.out.println(title.getFirstChild().getNodeValue());
}
}
}
The javax.xml.transform package defines a
TransformerFactory class for creating
Transformer objects. A
Transformer can transform a document from its
Source representation into a new
Result representation and optionally
apply an XSLT transformation to the document content in the
process. Three subpackages define concrete implementations of
the Source and Result
interfaces, which allow documents to be transformed among three
representations:
javax.xml.transform.streamRepresents documents as streams of XML text
javax.xml.transform.dom Represents
documents as DOM Document trees
javax.xml.transform.saxRepresents documents as sequences of SAX method calls
The following code shows one use of these packages to transform
the representation of a document from a DOM
Document tree into a stream of XML text. An
interesting feature of this code is that it does not create the
Document tree by parsing a file; instead, it
builds it up from scratch.
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class DOMToStream {
public static void main(String[] args)
throws ParserConfigurationException,
TransformerConfigurationException,
TransformerException
{
// Create a DocumentBuilderFactory and a DocumentBuilder
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
// Instead of parsing an XML document, however, just create an empty
// document that you can build up yourself.
Document document = db.newDocument();
// Now build a document tree using DOM methods
Element book = document.createElement("book"); // Create new element
book.setAttribute("id", "javanut4"); // Give it an attribute
document.appendChild(book); // Add to the document
for(int i = 1; i <= 3; i++) { // Add more elements
Element chapter = document.createElement("chapter");
Element title = document.createElement("title");
title.appendChild(document.createTextNode("Chapter " + i));
chapter.appendChild(title);
chapter.appendChild(document.createElement("para"));
book.appendChild(chapter);
}
// Now create a TransformerFactory and use it to create a Transformer
// object to transform our DOM document into a stream of XML text.
// No arguments to newTransformer() means no XSLT stylesheet
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
// Create the Source and Result objects for the transformation
DOMSource source = new DOMSource(document); // DOM document
StreamResult result = new StreamResult(System.out); // to XML text
// Finally, do the transformation
transformer.transform(source, result);
}
}
The most interesting uses of
javax.xml.transform involve XSLT stylesheets.
XSLT is a complex but powerful XML grammar that describes
how XML document content should be converted to another form
(e.g., XML, HTML, or plain text). A tutorial on
XSLT stylesheets is beyond the scope of this book, but the
following code (which contains only six key lines) shows
how you can apply such a stylesheet (which is an XML document
itself) to another XML document and write the resulting document
to a stream:
import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class Transform {
public static void main(String[] args)
throws TransformerConfigurationException,
TransformerException
{
// Get Source and Result objects for input, stylesheet, and output
StreamSource input = new StreamSource(new File(args[0]));
StreamSource stylesheet = new StreamSource(new File(args[1]));
StreamResult output = new StreamResult(new File(args[2]));
// Create a transformer and perform the transformation
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer(stylesheet);
transformer.transform(input, output);
}
}
Earlier in the chapter, we saw how easy it is to create and
manipulate multiple threads of execution running within the same
Java interpreter. Java also has a java.lang.Process
class that represents a program running externally to the
interpreter. A Java program can communicate with an external
process using streams in the same way that it might communicate
with a server running on some other computer on the network. Using a Process is always platform-dependent
and is rarely portable, but it is sometimes a useful thing to do:
// Maximize portability by looking up the name of the command to execute
// in a configuration file.
java.util.Properties config;
String cmd = config.getProperty("sysloadcmd");
if (cmd != null) {
// Execute the command; Process p represents the running command
Process p = Runtime.getRuntime().exec(cmd); // Start the command
InputStream pin = p.getInputStream(); // Read bytes from it
InputStreamReader cin = new InputStreamReader(pin); // Convert them to chars
BufferedReader in = new BufferedReader(cin); // Read lines of chars
String load = in.readLine(); // Get the command output
in.close(); // Close the stream
}
The java.security package defines quite a few
classes related to the Java access-control architecture, which is
discussed in more detail in . These classes allow Java
programs to run untrusted code in
a restricted environment from which it can do no harm. While
these are important classes, you rarely need to use them.
The more interesting classes are the ones used for
authentication; examples of their use are shown below.
A message digest is a value, also known as cryptographic checksum or secure hash, that is computed over a sequence of bytes. The length of the digest is typically much smaller than the length of the data for which it is computed, but any change, no matter how small, in the input bytes, produces a change in the digest. When transmitting data (a message), you can transmit a message digest along with it. Then, the recipient of the message can recompute the message digest on the received data and, by comparing the computed digest to the received digest, determine whether the message or the digest was corrupted or tampered with during transmission. We saw a way to compute a message digest earlier in the chapter when we discussed streams. A similar technique can be used to compute a message digest for nonstreaming binary data:
import java.security.*;
// Obtain an object to compute message digests using the "Secure Hash
// Algorithm"; this method can throw a NoSuchAlgorithmException.
MessageDigest md = MessageDigest.getInstance("SHA");
byte[] data, data1, data2, secret; // Some byte arrays initialized elsewhere
// Create a digest for a single array of bytes
byte[] digest = md.digest(data);
// Create a digest for several chunks of data
md.reset(); // Optional: automatically called by digest()
md.update(data1); // Process the first chunk of data
md.update(data2); // Process the second chunk of data
digest = md.digest(); // Compute the digest
// Create a keyed digest that can be verified if you know the secret bytes
md.update(data); // The data to be transmitted with the digest
digest = md.digest(secret); // Add the secret bytes and compute the digest
// Verify a digest like this
byte[] receivedData, receivedDigest; // The data and the digest we received
byte[] verifyDigest = md.digest(receivedData); // Digest the received data
// Compare computed digest to the received digest
boolean verified = java.util.Arrays.equals(receivedDigest, verifyDigest);
A digital signature combines a message-digest algorithm with public-key cryptography. The sender of a message, Alice, can compute a digest for a message and then encrypt that digest with her private key. She then sends the message and the encrypted digest to a recipient, Bob. Bob knows Alice's public key (it is public, after all), so he can use it to decrypt the digest and verify that the message has not been tampered with. In performing this verification, Bob also learns that the digest was encrypted with Alice's private key, since he was able to decrypt the digest successfully using Alice's public key. As Alice is the only one who knows her private key, the message must have come from Alice. A digital signature is called such because, like a pen-and-paper signature, it serves to authenticate the origin of a document or message. Unlike a pen-and-paper signature, however, a digital signature is very difficult, if not impossible, to forge, and it cannot simply be cut and pasted onto another document.
Java makes creating digital signatures easy. In order to create a
digital signature, however, you need a
java.security.PrivateKey object. Assuming that
a keystore exists on your system (see the
keytool documentation in ), you can get one with code like the
following:
// Here is some basic data we need
File homedir = new File(System.getProperty("user.home"));
File keyfile = new File(homedir, ".keystore"); // Or read from config file
String filepass = "KeyStore password" // Password for entire file
String signer = "david"; // Read from config file
String password = "No one can guess this!"; // Better to prompt for this
PrivateKey key; // This is the key we want to look up from the keystore
try {
// Obtain a KeyStore object and then load data into it
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(new BufferedInputStream(new FileInputStream(keyfile)),
filepass.toCharArray());
// Now ask for the desired key
key = (PrivateKey) keystore.getKey(signer, password.toCharArray());
}
catch (Exception e) { /* Handle various exception types here */ }
Once you have a PrivateKey object, you create a
digital signature with a
java.security.Signature object:
PrivateKey key; // Initialized as shown previously
byte[] data; // The data to be signed
Signature s = // Obtain object to create and verify signatures
Signature.getInstance("SHA1withDSA"); // Can throw a
// NoSuchAlgorithmException
s.initSign(key); // Initialize it; can throw an InvalidKeyException
s.update(data); // Data to sign; can throw a SignatureException
/* s.update(data2); */ // Call multiple times to specify all data
byte[] signature = s.sign(); // Compute signature
A Signature object can verify
a digital signature:
byte[] data; // The signed data; initialized elsewhere
byte[] signature; // The signature to be verified; initialized elsewhere
String signername; // Who created the signature; initialized elsewhere
KeyStore keystore; // Where certificates stored; initialize as shown earlier
// Look for a public-key certificate for the signer
java.security.cert.Certificate cert = keystore.getCertificate(signername);
PublicKey publickey = cert.getPublicKey(); // Get the public key from it
Signature s = Signature.getInstance("SHA1withDSA"); // Or some other algorithm
s.initVerify(publickey); // Setup for verification
s.update(data); // Specify signed data
boolean verified = s.verify(signature); // Verify signature data
The java.security.SignedObject class is a
convenient utility for wrapping a digital signature around an
object. The SignedObject can then be
serialized and transmitted to a recipient, who can deserialize it
and use the verify() method to verify the
signature:
Serializable o; // The object to be signed; must be Serializable
PrivateKey k; // The key to sign with; initialized elsewhere
Signature s = Signature.getInstance("SHA1withDSA"); // Signature "engine"
SignedObject so = new SignedObject(o, k, s); // Create the SignedObject
// The SignedObject encapsulates the object o; it can now be serialized
// and transmitted to a recipient.
// Here's how the recipient verifies the SignedObject
SignedObject so; // The deserialized SignedObject
Object o; // The original object to extract from it
PublicKey pk; // The key to verify with
Signature s = Signature.getInstance("SHA1withDSA"); // Verification "engine"
if (so.verify(pk,s)) // If the signature is valid,
o = so.getObject(); // retrieve the encapsulated object.
The java.security package includes
cryptography-based classes, but it does not contain classes
for actual encryption and decryption. That is the job of the
javax.crypto package. This package supports
symmetric-key cryptography, in which the same key is used for both
encryption and decryption and must be known by both the sender
and the receiver of encrypted data.
The
SecretKey interface represents an encryption
key; the first step of any cryptographic operation is to
obtain an appropriate SecretKey. Unfortunately, the keytool program supplied
with the Java SDK cannot generate and store secret keys, so
a program must handle these tasks itself. Here is some code
that shows various ways to work with SecretKey
objects:
import javax.crypto.*;
import javax.crypto.spec.*;
// Generate encryption keys with a KeyGenerator object
KeyGenerator desGen = KeyGenerator.getInstance("DES"); // DES algorithm
SecretKey desKey = desGen.generateKey(); // Generate a key
KeyGenerator desEdeGen = KeyGenerator.getInstance("DESede"); // Triple DES
SecretKey desEdeKey = desEdeGen.generateKey(); // Generate a key
// SecretKey is an opaque representation of a key. Use SecretKeyFactory to
// convert to a transparent representation that can be manipulated: saved
// to a file, securely transmitted to a receiving party, etc.
SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES");
DESKeySpec desSpec = (DESKeySpec)
desFactory.getKeySpec(desKey, javax.crypto.spec.DESKeySpec.class);
byte[] rawDesKey = desSpec.getKey();
// Do the same for a DESede key
SecretKeyFactory desEdeFactory = SecretKeyFactory.getInstance("DESede");
DESedeKeySpec desEdeSpec = (DESedeKeySpec)
desEdeFactory.getKeySpec(desEdeKey, javax.crypto.spec.DESedeKeySpec.class);
byte[] rawDesEdeKey = desEdeSpec.getKey();
// Convert the raw bytes of a key back to a SecretKey object
DESedeKeySpec keyspec = new DESedeKeySpec(rawDesEdeKey);
SecretKey k = desEdeFactory.generateSecret(keyspec);
// For DES and DESede keys, there is an even easier way to create keys
// SecretKeySpec implements SecretKey, so use it to represent these keys
byte[] desKeyData = new byte[8]; // Read 8 bytes of data from a file
byte[] tripleDesKeyData = new byte[24]; // Read 24 bytes of data from a file
SecretKey myDesKey = new SecretKeySpec(desKeyData, "DES");
SecretKey myTripleDesKey = new SecretKeySpec(tripleDesKeyData, "DESede");
Once you have obtained an appropriate SecretKey
object, the central class for
encryption and decryption is Cipher. Use it
like this:
SecretKey key; // Obtain a SecretKey as shown earlier
byte[] plaintext; // The data to encrypt; initialized elsewhere
// Obtain an object to perform encryption or decryption
Cipher cipher = Cipher.getInstance("DESede"); // Triple-DES encryption
// Initialize the cipher object for encryption
cipher.init(Cipher.ENCRYPT_MODE, key);
// Now encrypt data
byte[] ciphertext = cipher.doFinal(plaintext);
// If we had multiple chunks of data to encrypt, we can do this
cipher.update(message1);
cipher.update(message2);
byte[] ciphertext = cipher.doFinal();
// We simply reverse things to decrypt
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedMessage = cipher.doFinal(ciphertext);
// To decrypt multiple chunks of data
byte[] decrypted1 = cipher.update(ciphertext1);
byte[] decrypted2 = cipher.update(ciphertext2);
byte[] decrypted3 = cipher.doFinal(ciphertext3);
The Cipher class can also be used with
CipherInputStream or
CipherOutputStream to encrypt or decrypt while
reading or writing streaming data:
byte[] data; // The data to encrypt
SecretKey key; // Initialize as shown earlier
Cipher c = Cipher.getInstance("DESede"); // The object to perform encryption
c.init(Cipher.ENCRYPT_MODE, key); // Initialize it
// Create a stream to write bytes to a file
FileOutputStream fos = new FileOutputStream("encrypted.data");
// Create a stream that encrypts bytes before sending them to that stream
// See also CipherInputStream to encrypt or decrypt while reading bytes
CipherOutputStream cos = new CipherOutputStream(fos, c);
cos.write(data); // Encrypt and write the data to the file
cos.close(); // Always remember to close streams
java.util.Arrays.fill(data, (byte)0); // Erase the unencrypted data
Finally, the
javax.crypto.SealedObject class provides
an especially easy way to perform encryption. This class
serializes a specified object and encrypts the resulting stream of
bytes. The SealedObject can then be serialized
itself and transmitted to a recipient. The recipient is only
able to retrieve the original object if she knows the required
SecretKey:
Serializable o; // The object to be encrypted; must be Serializable
SecretKey key; // The key to encrypt it with
Cipher c = Cipher.getInstance("Blowfish"); // Object to perform encryption
c.init(Cipher.ENCRYPT_MODE, key); // Initialize it with the key
SealedObject so = new SealedObject(o, c); // Create the sealed object
// Object so is a wrapper around an encrypted form of the original object o;
// it can now be serialized and transmitted to another party.
// Here's how the recipient decrypts the original object
Object original = so.getObject(key); // Must use the same SecretKey
View catalog information for Java in a Nutshell, 4th Edition.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.