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
|