New to Java? We'll help you get started with our revised beginner's tutorial, or our free online textbook.
|
|
![]() Get the latest Java books |
|
|
h t t p : / /w w w . j a v a c o f f e e b r e a k . c
o m /
|
||
This extract from the third edition of Java in a Nutshell gives a good overview of the Java Platform, and the scope of the book. Previous editions of the Java in a Nutshell series have been excellent desktop references, and the third edition proves to be no exception. Extract used with permission. Chapter 4
|
||||||||||||||||||||||||||||||||||||||||||||
| 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.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.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. |
Table 4.1 does not list all the packages in
the Java platform, only those documented in this book. 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 the book 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
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 4 chars of s into ca at position 2
// 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('.', '!'); // Only works 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 ==
Since String
objects are immutable, you cannot manipulate the characters of a String
in place. 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 addition to the String
and StringBuffer classes, there
are a number of other Java classes that operate on strings. 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, ":");
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 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++;
}
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
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(sh); // 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
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 <= d < 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());
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 in 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 millisecondsTo 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. Collection, Set,
List, and Map
are all interfaces, but the java.util
package also defines various concrete implementations (see ). 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.
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 hash table
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 firstHere 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
One particularly useful collection class is java.util.Properties.
Properties is a subclass of Hashtable
that predates the collections framework of Java 1.2, making it a
legacy collection. 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 simple-format 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.getProperty():
import java.util.*;
import java.io.*;
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 */ }
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.getProperties("filterclass", // The property name
"com.davidflangan.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 */ }
// If the class to be loaded is not in the classpath, create a custom
// class loader to load it.
// Use the config file again to specify the custom path
import java.net.URLClassLoader;
String classdir = config.getProperties("classpath");
try {
ClassLoader loader = new URLClassLoader(new URL[] { new URL(classdir) });
Class c = loader.loadClass(classname);
}
catch (Exception e) { /* Handle exceptions */ }
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 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:
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);
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 1000 milliseconds
catch (InterruptedException e) {} // Ignore this exception
}
}
// Ask the thread to stop running
public void pleaseStop() { keepRunning = false; }
}
Notice the pleaseStop()
method in the previous 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 1000 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 only runs // 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've got to wait for the sorter thread to exit, if it hasn't already. sorter.join();
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 any 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 carefu 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, it 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);
}
}
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 input/output streams used in
Java and discussed in the next section. Here are some things you can
do with File:
import java.io.*;
// 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(0);
}
// 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 RandomAccessFile:
// 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!
The java.net
package defines a number of classes that make writing networked
applications surprisingly easy. The easiest 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. 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/javanut3/"); // 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\n\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();
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 Server
Socket. 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\nContent-Type: text/plain\n\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 */ }
}
}
}
Note how elegantly both the URL
and Socket classes use the
input/output streams that we saw earlier in the chapter. This is one
of the features that makes the Java networking classes so powerful.
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.domain.org"; // 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 to receive it through
DatagramPacket p =
new DatagramPacket(buffer, buffer.length); // The packet to receive 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()
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 Chapter 5, Java
Security. 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. 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 non-streaming binary data:
import java.security.*;
// Obtain an object to compute message digests using the "Secure Hash
// Algorithm"; this method can throw 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 Chapter 8, Java Development
Tools), 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 NoSuchAlgorithmException
s.initSign(key); // Initialize it; can throw InvalidKeyException
s.update(data); // Data to sign; can throw 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
|
Copyright 1998, 1999, 2000 David Reilly
|
Privacy | Legal | Linking | Advertise! |
|
Last updated:
Monday, June 05, 2006
|