Eight: Exceptions
Item 57: Use exceptions only for exceptional conditions
Exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow:
1).Because exception are designed for exceptional circumstances, there is little incentive for JVM implementors to make them as fast as explicit tests;
2).Placing code inside a try-catch block inhibits certain optimizations that modern JVM implementations might otherwise perform;
3).The standard idiom for looping through an array doesn’t necessarily result in redundant checks. Modern JVM implementations optimize them away;
A will-designed API must not force its clients to use exceptions for ordinary control flow:
1).A class with a “state-dependent” method that can be invoked only under certain unpredictable conditions should generally have a separate “state-testing” method indicating whether it is appropriate to invoke the state-dependent method;
2).An alternative to providing a separate state-testing method is to have the state-dependent method return a distinguished value such as null if is invoked with the object in an inappropriate state;
For example:
...
for (Iterator i = collection.iterator(); i.hasNext();) {
Foo foo = i.next();
...
}
// Do not use this hideous code for iterator over a collection!\
try {
Iterator i = collection.iterator();
while(true) {
Foo foo = i.next();
...
}
} catch(NoSuchElementException e) {
}
Item 58: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
The java programming language provides three kinds of throwables: checked exceptions, runtime exceptions, and errors. There are some rules that provide strong guidance.
1).Use checked exceptions for conditions from which the caller can reasonably by expected to recover;
2).If a program throws an unchecked exception or an error, it is generally the case that recovery is impossible and continued execution would do more harm than good;
3).If a program does not catch such a throwable, it will cause the current thread to halt with an appropriate error message;
4).Use runtime exceptions to indicate programming errors;
5).All of the unchecked throwables you implement should subclass RuntimeException;
6).It has no benefits over an ordinary checked exception and would merely serve to confuse the user of your API;
7).If you believe a condition is likely to allow for recovery, use a checked exception; if not, use a runtime exception. If it isn’t clear whether recovery is possible, you’re probably better off using an unchecked exception.
Item 59: Avoid unnecessary use of checked exception
Checked exceptions are a wonderful feature of the Java programming. They force the programmer to deal with exception conditions.
1).If the exceptional condition cannot be prevented by proper use of the API and the programmer using the API can take some useful action once confronted with the exception. Unless both of these conditions hold, an unchecked exception is more appropriate;
2).If the programmer using the API can do no better, an unchecked exception would be more appropriate;
3).The additional burden on the programmer caused by a checked exception is substantially higher if it is the sole checked exception thrown by a method;
4).One technique for turning a checked exception into an unchecked exception to break the method that throws the exception into two methods, the first of which returns a boolean that indicates whether the exception would be thrown;
Here’s the code:
...
// Invocation with checked exception
try {
obj.action(args);
} catch(TheCheckedException e) {
//Handle exceptional condition
...
}
...
// Invocation with state-testing method and unchecked exception
if(obj.actionPermitted(args)) {
obj.action(args);
} else {
//Handle exceptional condition
...
}
Item 60: Favor the use of standard exception
Exceptions are no exception to the general rule that code reuse is good. The Java platform libraries provide a basic set of unchecked exceptions that cover a large fraction of the exception-throwing needs of most APIs. Not only it make your API easier to learn and use but also programs using your API are easier to read.
The commonly reused exception are here:
1).IllegalArgumentException–This is generally the exception to throw when the caller passes in an argument hose value is inappropriate;
2).IllegalStateException—This is generally the exception to throw if the invocation is illegal because of the state of the receiving object;
3).If a caller passes null in some parameter for which null values are prohibited, convention dictates that NullPointerException be thrown;
4).If a caller passes an out-of-range value in a parameter representing an index into a sequence, IndexOutOgBoundsException should be thrown;
5).ConcurrentModificationException—This exception should be thrown if an object that was designed for use by a single thread or with external synchronization detects that it is being(or has been) concurrently modified;
6).UnsupportedOperationException—This is the exception to throw if an object does not support an attempted operation;
In summary, if an exception fits your needs, go ahead and use it, but only if the conditions under which you would throw it are consistent with the exception’s documentation.
Item 61: Throw exceptions appropriate to the abstraction
Higher layers should catch lower-level exceptions and, in their place, throw exceptions that can be explained in terms of the higher-level abstraction. This idiom is known as exception translation. A special form of exception translation called exception chaining is appropriate in cases where the lower-level exception might be helpful to someone debugging the problem that caused the higher-level exception.
While exception translation is superior to mindless propagation of exception from lower layers, it should not be overused:
1).The best way to deal with exceptions from lower layers is to avoid them, by ensuring that lower-level methods succeed;
2).Checking the validity of the higher-level method’s parameters before passing them on to lower layers;
In summary, if it isn’t feasible to prevent or to handle exception from lower layers, use exception translation, unless the lower-level method happens to guarantee that all of its exceptions are appropriate to the higher-level. Chaining provides the best of both worlds: it allows you to throw an appropriate higher-level exception, while capturing the underlying cause for failure analysis.
1).Exception translation:
public class AbstractList {
/**
* Return the element at the specified position in this list.
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()}).
*/
public E get(int index) {
ListIterator i = listIterator(index);
try {
return i.next();
} catch(NoSuchElementException e) {
throw new IndexOutOfBoundsException("Index: " + index);
}
}
}///:~
2).Exception Chaining:
...
try {
...// Use lower-level abstraction to do bidding
} catch(LowerLevelException cause) {
throw new HigherLevelException(cause);
}
// Exception with chaining-aware constructor
class HigherLevelException extendes Exception {
HigherLevelException(Throwable cause) {
super(cause);
}
}
Item 62: Document all exceptions thrown by each method
1).Always declare checked exception individually, and document precisely the conditions under which each one is thrown using the Javadoc @throws tag;
2).Use the Javadoc @throws tag to documents each unchecked exception that a method can throw, but do not use throws keyword to include unchecked exceptions in the method declaration;
3).If an exception is thrown by many methods in a class for the same reason, it is acceptable to document the exception in the class’s documentation comment;
In summary, document every exception that can be thrown by each method that you write. This is true for unchecked as well as checked exceptions, and for abstract as well as concrete methods. Provide individual throws clauses for each checked exception and do not provide throws clauses for unchecked exception. If you fail to document the exceptions that your methods can throw, it will be difficult or impossible for others to make effective use of you classes and interfaces.
Item 63: Include failure-capture information in detail messages
1).To capture the failure, the detail message of an exception should contain the value of all parameters and fields that “contributed to the exception.”;
2).One way to ensure that exceptions contain adequate failure-capture information in their detail message is to require this information in their constructors instead of a string detail message;
For example:
/**
*Construct an IndexOutOfBoundsException.
*
*@param lowerBound the lowest legal index value.
*@param upperBound the highest legal index value plus one.
*@param index the actual index value.
*/
public IndexOutOfBoundsException(int lowerBound, int upperBound,
int index) {
// Generate a detail message that captures the failure
super("Lower bound: " + lowerBound +
", Upper bound: " + upperBound +
", Index: " +index);
// save failure information for programmatic access
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.index = index;
}
Item 64: Strive for failure atomicity
Generally specking, a failed method invocation should leave the object in the state that it was in prior to the invocation.
1).The simplest is to design immutable objects;
2).For methods that operate on mutable objects, the most common way to achieve failure atomicity is to check parameters for validity before performing the operation. A closely relate approach to achieving failure atomicity is to order the computation so that any part that may fail takes place before any part that modifies the object;
3).A third and far less common approach to achieving failure atomicity is to write recovery code that intercepts a failure that occurs in the midst of an operation begin;
4).A final approach to achieving failure atomicity is to perform the operation on a temporary copy of the object and to replace the contents of the object with the temporary copy once the operation is complete;
As a rule, any generated exception that is part of a method’s specification should leave the object in the same state it was in prior to the method invocation. Where this rule is violated, the API documentation should clearly indicate what state the object will be left in.
For example:
...
public Object pop() {
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
...
Item 65: Don’t ignore exception
An empty catch block defeats the purpose of exceptions. At the least, the catch block should contain a comment explaining why it is appropriate to ignore the exception.
The advice in this item applies equally to checked and unchecked exceptions. Whether an exception represents a predictable exceptional condition or a programming error, ignoring it with an empty catch block will result in a program that continues silently in the face of error. The program might then fail at an arbitrary time in the future, at a point in the code that bears no apparent relation to the source of the problem. Properly handing an exception can avert failure entirely. Merely letting an exception propagate outward can at least cause the program to fail swiftly, preserving information to aid in debugging the failure.
Like this:
...
//Empty catch block ignores exception – Highly suspect!
try {
…
} catch(SomeException e) {
}
…
Nine: Concurrency
Item 66: Synchronize access to shared mutable data
You may hear it said that to improve performance, you should avoid synchronization when reading or writing atomic data. This advice is dangerously wrong.
Here’s the important rules:
1).Synchronization is required for reliable communication between threads as well as for mutual exclusion;
2).Synchronization has no effect unless both read and write operations are synchronized;
3).Confine mutable data to a single thread;
4).There are many ways to safely publish an object reference: you can store it in a static fields as part of class initialization; you can store it in a volatile field, a final field, or a field that is accessed with normal locking; or you can put it into a concurrent collection;
5).When multiple threads share mutable data, each thread that reads or write the data must perform synchronization;
For example:
import java.util.concurrent.TimeUnit;
/** Broken! - How long would you except this program to run?
* The program never terminates: the background thread loops forever!
* It's quite acceptable for the virtual machine to transform this code:
* while(!done)
* i++;
* into this code:
* if(!done)
* while(true)
* i++
* This optimization is known as hoisting, and it is precisely what the
* HotSpot server VM does.
*/
public class OrdinalStopThread {
private static boolean stopRequested;
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while(!stopRequested)
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}///:~
One way to fix the problem is to synchronize access to the stopRequested field:
import java.util.concurrent.TimeUnit;
// Properly synchronized cooperative thread termination
public class SynchronizeStopThread {
private static boolean stopRequested;
private static synchronized void requestStop() {
stopRequested = true;
}
private static synchronized boolean stopRequested() {
return stopRequested;
}
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while(!stopRequested())
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
}///:~
Or you can fix the problem use volatile:
import java.util.concurrent.TimeUnit;
// Cooperative thread termination with a volatile field
public class VolatileStopThread {
private static volatile boolean stopRequested;
public static void main(String[] args)
throws InterruptedException{
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while(!stopRequested)
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}///:~
Item 67: Avoid excessive synchronization
1).To avoid liveness and safety failures, never cede control to the client within a synchronized method or block;
2).As a rule, you should do as little work as possible inside synchronized regions;
3).In multicore world, the real cost of excessive synchronization is not the CPU time spent obtaining locks; it is the lost opportunities for parallelism and the delays imposed by the need to ensure that every core has a consistent view of memory. Another hidden cost of oversynchronization is that it can limit the VM’s ability to optimize code execution;
In summary, to avoid deadlock and data corruption, never call an alien method from within a synchronized region. More generally, try to limit the amount of work that you do from within synchronized regions. When you are designing a mutable class, think about whether it should do its own synchronization. In the modern multicore era, it is more important than ever not to synchronization excessively. Synchronize your class internally only if there is a good reason to do so, and document your decision clerly.
Example:
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExcessiveSynchronization {
public static void main(String[] args) {
ObservableSet set =
new ObservableSet(new HashSet());
/* the following program prints the number from 0 to 9
set.addObserver(new SetObserver() {
public void added(ObservableSet s, Integer e) {
System.out.println(e);
}
});
*/
/* the following program will throw a ConcurrentModificationException
set.addObserver(new SetObserver() {
public void added(ObservableSet s, Integer e) {
System.out.println(e);
if(e == 3) s.removeObserver(this);
}
});*/
/* the following program won't throw a exception but get a deadlock
* observer that uses a background thread beedlessly
set.addObserver(new SetObserver() {
public void added(final ObservableSet s, Integer e) {
System.out.println(e);
if(e == 3) {
ExecutorService executor =
Executors.newSingleThreadExecutor();
final SetObserver observer = this;
try {
executor.submit(new Runnable() {
public void run() {
s.removeObserver(observer);
}
}).get();
} catch(ExecutionException ex) {
throw new AssertionError(ex.getCause());
} catch(InterruptedException ex) {
throw new AssertionError(ex.getCause());
} finally {
executor.shutdown();
}
}
}
});*/
for(int i = 0; i < 10; i++)
set.add(i);
}
}
class ObservableSet extends ForwardingSet {
public ObservableSet(Set set) { super(set); }
private final List> observers =
new ArrayList>();
public void addObserver(SetObserver observer) {
synchronized(observers) {
observers.add(observer);
}
}
public boolean removeObserver(SetObserver observer) {
synchronized(observer) {
return observers.remove(observer);
}
}
private void notifyElementAdded(E element) {
synchronized(observers) {
for(SetObserver observer : observers)
observer.added(this, element);
}
}
@Override public boolean add(E element) {
boolean added = super.add(element);
if(added)
notifyElementAdded(element);
return added;
}
}
interface SetObserver {
void added(ObservableSet set, E element);
}///:~
To fix the problem:
1).
…
// Alien method moved outside of synchronized block - open calls
private void notifyElementAdded(E element) {
List> snapshot = null;
synchronized(observers) {
snapshot = new ArrayList>(observers);
}
for(SetObserver observer : snapshot)
observer.added(this, element);
}
…
2).
…
class ObservableSet<E> extends ForwardingSet<E> {
public ObservableSet(Set set) { super(set); }
// Thread-safe observable set with CopyOnWriteArrayList
private final List> observers =
new CopyOnWriteArrayList>();
public void addObserver(SetObserver observer) {
observers.add(observer);
}
public boolean removeObserver(SetObserver observer) {
return observers.remove(observer);
}
private void notifyElementAdded(E element) {
for(SetObserver observer : observers)
observer.added(this, element);
}
@Override public boolean add(E element) {
boolean added = super.add(element);
if(added)
notifyElementAdded(element);
return added;
}
}
…
Item 68: Prefer executors and tasks to threads
java.util.concurrent, this package contains an Executor Framework, which is a flexible interface-based task execution facility and including these hits:
1).If you want more one thread to process requests from the queue, simply call a different static factory that creates a different kind of executor service called a thread pool;
2).If you’re writing a small program, or a lightly loaded server, using Executors.newCachedThreadPool is a generally a good choice;
3).In a heavily loaded production server, you are mush better off using Executors.newFixedThreadPool;
4).The Framework also has a replacement for java.util.Timer, which is ScheduledThreadPoolExecutor;
The key abstraction is no longer Thread, which served as both the unit of work and the mechanism for executing it. Now the unit of work and mechanism are separate. The key abstraction is the unit of work, which is called a task. There are two kinds of tasks; Runnable and its close cousin, Callable(which is like Runnable, except that it returns a value).
Item 69: Prefer concurrency to wait and notify
Given the difficulty of using wait and notify correctly you should use the higher-level concurrency utilities instead. The higher-level utilities in java.util.concurrency fall unto three categories: the Executor Framework, concurrent collections and synchronizers.
It is impossible to exclude concurrent activity from a concurrent collection; locking it will have no effect. Using concurrentHashMap in preference to Collections.synchronizedMap or Hashtable. Like this example:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ConcurrentUtility {
// Concurrent canonicalizing map atop ConcurrentMap - not optimal
private static final ConcurrentMap<String, String> map =
new ConcurrentHashMap<String, String>();
public static String intern(String s) {
String previousValue = map.putIfAbsent(s, s);
return previousValue == null ? s : previousValue;
}
// concurrent canonicalizing map atop ConcurrentMap - faster!
public static String enhenceIntern(String s) {
String result = map.get(s);
if(result == null) {
result = map.putIfAbsent(s, s);
if(result == null)
result = s;
}
return result;
}
}///:~
In summary, using wait and notify directly is like programming in “concurrency assembly language,” as compared to the higher-level language provided by java.util.concurrent. There is seldom, if ever, a reason to use wait and notify in new code. If you maintain code that uses wait and notify, make sure that it always invokes wait from within a while loop using the standard idiom. The notifyAll method should generally be used in preference to notify. If notify is used, great care must be taken to ensure liveness.
Example code:
1).CountDownLatch:
`import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
// Simple framework for timing concurrent execution
public class ConcurrentExecution {
public static void main(String[] args) throws InterruptedException {
Executor executor = Executors.newFixedThreadPool(1000);
System.out.println(time(executor, 1000, new Runnable() {
public void run() {
}
}));
}
public static long time(Executor executor, int concurrency,
final Runnable action) throws InterruptedException {
final CountDownLatch ready = new CountDownLatch(concurrency);
final CountDownLatch start = new CountDownLatch(1);
final CountDownLatch done = new CountDownLatch(concurrency);
for(int i = 0; i < concurrency; i++) {
executor.execute(new Runnable() {
public void run() {
ready.countDown(); // Tell timer we're ready
try {
start.await(); // Wait till peers are ready
action.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
done.countDown();
}
}
});
}
ready.await(); // Wait for all workers to be ready
long startNanos = System.nanoTime();
start.countDown(); // And they're off!
done.await(); //Wait for all workers to finish
return System.nanoTime() - startNanos;
}
}/*Output:
21569113
*///:~
`2).wait():
…
// The standard iiom for using the wait method
synchronized(obj) {
while()
obj.wait(); // (Release lock, and reacquires no wakeup)
...//Perform action appropriate to condition
}
…
Item 70: Document thread safety
1).The presence of the synchronized modifier in a method declaration in an implementation detail, not a part of its exported API.
2).To enable safe concurrent use, a class must clearly document what level of thread safety it supports.
(1).immutable—Instances of this class appear constant. No external synchronization is necessary;
(2).unconditionally thread-safe—Instances of this class are mutable, but the class has sufficient internal synchronization that its instances can be used concurrently without the need for any external synchronization;
…
// Private lock object idiom - thwarts denial -of-service attack
private final Object lock = new Object();
public void foo() {
synchronized(lock) {
…
}
}
…
(3).conditionally thread-safe—Like unconditionally thread-safe, except that some methods require external synchronization for safe concurrent use.
(4).not thread-safe—Instances of this class are mutable, To use them concurrently, clients must surround each method invocation(or invocation sequence) with external synchronization of the client’s choosing;
(5).thread-hostile—This class is not safe for concurrent use event if all method in vocations are surrounded by external synchronization.
To summarized, every class should clearly document its thread safety properties a carefully worded prose description or a thread safety annotation. The synchronized modifier plays no part in this documentation. Conditionally thread-safe classes must document which method invocation sequences require external synchronization, and which lock to acquire when executing these sequences. If you write an unconditionally thread-safe class, consider using a private lock object in place of synchronized methods. This protects you the flexibility to adopt a more sophisticated approach to concurrency control in a latter release.
For example:
It is imperative that the user manually synchronize on the returned map when
iterating over any of its collection views:
Map m = Collections.synchronizedMap(new HashMap());
...
Set s = m.keySet(); // Needn't be in synchronized block
...
synchronized(m) { // Synchronizing on m, not s!
for (K key : s)
key.f();
}
Item 71: Use lazy initialization judiciously
Lazy initialization is the act of delaying the initialization of a field until its value is needed. However, lazy initialization is a double-edged sword. It decreases the cost of initializing a class or creating an instance, at the expense of increasing the cost of accessing the lazily initialized field. Following these hits:
1).Under most circumstances, normal initialization is preferable to lazy initialization;
…
// Normal most circumstances of an instance field
private final FieldType field = computeFieldValue();
…
2).If you lazy initialization to break an initialization circularity, use a synchronized accessor;
…
// Lazy initialization of instance field - synchronized accessor
private FieldType field;
synchronized FieldType getField() {
if(field == null)
field = computeFieldValue();
return field;
}
…
3).If you need to use lazy initialization for performance on a static field, use the lazy initialization holder class idiom;
…
// Lazy initialization holder class idiom for static fields
private static class FieldHoler {
static final FieldType field = computeFieldValue();
}
static FieldType getField() { return FieldHolder.field; }
…
4).If you need to use lazy initialization for performance on an instance field, use the double-check idiom;
…
// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if(result == null) { // first check (no locking)
synchronized(this) {
result = field;
if(result == null) // Second check (with locking)
field = result = computeFieldValue();
}
}
return result;
}
…
5).If you can use a variant of the double-check idiom that dispenses with the second check, which is known as single-check idiom;
…
// Single-check idiom - can cause repeated initialization!
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if(result == null)
field = result = computeFieldValue();
return result;
}
…
6).If you must initialize a field lazily in order to achieve your performance goals, or to break a harmful initialization circularity, then use the appropriate lazy initialization technique;
Item 72: Don’t depend on the thread scheduler
Here’s the rules:
1).Any program that relies on the thread scheduler for correctness or performance is likely to be nonportable;
2).Threads should not run if they aren’t doing useful work;
3).Thread.yield has no testable semantics;
4).Thread priorities are among the least portable features of the Java platform;
5).You should use Thread.sleep(1) instead of Thread.yield for concurrency testing;
Here’s the busy-wait idiom, which very bad!
//Awful CountDownLatch implementation - busy-waits incessantly!
public class SlowCountDownLatch {
private int count;
public SlowCountDownLatch(int count) {
if(count < 0)
throw new IllegalArgumentException(count + "< 0");
this.count = count;
}
public void await() {
while(true) {
synchronized(this) {
if(count == 0) return;
}
}
}
public synchronized void countDown() {
if(count != 0)
count--;
}
}///:~
Item 73: Avoid thread groups
Thread groups are obsolete, which don’t provide much in the way of useful functionality, and much of the functionality they do provide is flawed. Thread groups are best viewed as an unsuccessful experiment, and you should simply ignore their existence. If you design a class that deals with logical groups of threads, you should probably use thread pool executors.SlowCount.
Ten: Serialization
Item 74: Implement Serializable judiciously
Cost to make a class serializable can be negligible, the long-term costs are often substantial. Here’s the disadvantages:
1).A major cost of implementing Serializable is that it decreases the flexibility to change a class’s implementation once it has been released;
2).A second cost of implementing serializable is that it increases the likelihood of bugs and security holes;
3).A third cost of implementing Serializable is that it increase the testing burden associated with releasing a new version of a class;
But you need to Serializable sometime, follow these hits:
1).Implementing the Serializable interface is not a decision to be undertaken lightly;
2).Classes designed for inheritance should rarely implement Serializable, and interfaces should rarely extends it;
3).You should providing a parameterless constructor on nonserializable classes designed for inheritance;
Like this:
import java.io.*;
import java.util.concurrent.atomic.AtomicReference;
// Nonserializable stateful class allowing serializable subclass
abstract class AbstractFoo {
private int x, y; // Our state
// This enum and field are used to track initialization
private enum State { NEW, INITIALIZING, INITIALIZED };
private final AtomicReference init =
new AtomicReference(State.NEW);
public AbstractFoo(int x, int y) { initialize(x, y); }
// This constructor and the following method allow
// subclass's readObject method initialize our state.
protected AbstractFoo() { }
protected final void initialize(int x, int y) {
if(!init.compareAndSet(State.NEW, State.INITIALIZING))
throw new IllegalStateException(
"Already initialized");
this.x = x;
this.y = y;
init.set(State.INITIALIZED);
}
// These methods provide access to internal state so it can
// be manually serialized by subclass's writeObject method.
protected final int getX() { checkInit(); return x; }
protected final int getY() { checkInit(); return y; }
// Must call from all public and protected instance methods
private void checkInit() {
if(init.get() != State.INITIALIZED)
throw new IllegalStateException("Uninitialized");
}
}
// Serializable subclass of nonserializable stateful class
public class Foo extends AbstractFoo implements Serializable {
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
// Manually deserialize and initialize superclass state
int x = s.readInt();
int y = s.readInt();
}
private void writeObject(ObjectOutputStream s)
throws IOException {
s.defaultWriteObject();
//Manually serialize superclass state
s.writeInt(getX());
s.writeInt(getY());
}
// Constructor does not use the fancy mechanism
public Foo(int x, int y) { super(x, y); }
private static final long serialVersionUID = 1856835860954L;
}///:~
4).Inner classes should not implement Serializable, the default serialized form of an inner class is ill-defined;
To summarized, the ease of implementing Serializable is specious. Unless a class is to be thrown away after a short period of use, implementing Serializable is a serious commitment that should be made with care. Extra caution is warranted if a class is designed for inheritance. For such classes, an intermediate design point between implementing Serializable and prohibiting it in subclasses is to provide an accessible parameterless constructor. This design point permit, but does not require, subclasses to implement Serializable.
Item 75: Consider using a custom serialized form
1).Do not accept the default serialized form without first considering whether it is appropriate;
2).The default serialized form is likely to be appropriate if an object’s physical representation is identical to its logical content;
For example:
…
// Good condidate for default serialized form
public class Name implements Serializable {
/**
*Last name. Must be not-null
*@serial
*/
private final String lastName;
/**
*First name. Must be non-null
*@serial
*/
private final String firstName;
/**
*Middle name, or null if there is none.
*@serial
*/
private final String middleName;
}
…
3).Even if you decide that the default serialized form is appropriate, you often must provide a readObject method to ensure invariant and security;
4).Using the default serialized form when an object’s physical representation differs substantially form its logical data conent has four disadvantages:
(1).It permanently ties the exported API to the current internal representation;
(2).It can consume excessive space;
(3).It can consume excessive time;
(4).It can cause stack overflow;
5).If all instance fields are transient, it is technically permissible to dispense with invoking defaultWriteObject and defaultReadObject, but it is not recommended;
6).Before deciding to make a field nontransient, convince yourself that its value is part of the logical state of the object;
7).You must impose any synchronization on object serialization that you would impose on any other method that reads the entire state of the object;
8).Regardless of what serialized from you choose, declare an explict serial version UID in every serializable class you write;
Example of custom serialize form:
import java.io.*;
// StringList with a reasonable custom serialized form
public class StringList implements Serializable {
private transient int size = 0;
private transient Entry head = null;
// No longer Serializable
private static class Entry {
String data;
Entry next;
Entry previous;
}
//Appends the specified string to the list
public final void add(String s) {}
/**
*Serialize this {@code StringList} instance.
*
*@serialData The size of the list (the number of strings
*is contains) is emitted ({@code int}), followed by all of
*its elements (each a {@code String}), in the proper
*sequence.
*/
private void writeObject(ObjectOutputStream s)
throws IOException {
s.defaultWriteObject();
s.writeInt(size);
// Write out all elements in the proper order.
for(Entry e = head; e != null; e = e.next)
s.writeObject(e.data);
}
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
int numElements = s.readInt();
// Read in all elements and insert them in list
for(int i = 0; i < numElements; i++)
add((String)s.readObject());
}
}///:~
Item 76: Write readObject methods defensively
In summary, anytime you write a readObject method, adopt the mide-set that you are writing a public constructor that must produce a valid instance regardless of what byte stream it is given. Do not assume that the byte stream represents an actual serialized instance. Here, in summary form, are the guidelines for writing a bulletproof readObject method:
1).For classes with object reference fields that must remain private, defensively copy each object in such a field. Mutable components of immutable classes fall into this category;
2).Check any invariants and throw an InvalidObjectException if a check fails. The checks should follow any defensively copying;
3).If an entire object graph must be validated after it is deserialized, use the objectInputValidation interface;
4).Do not invoke any overridable methods in the class, directly or indirectly;
Here’s the example come from item29:
…
//Broken "immutable" time period class
class Period {
private final Date start;
private final Date end;
/**
*@param start the beginning of the period
*@param end the end of the period; must not precede start
*@throws IllegalArgumentException if start is after end
*@throws NullPointerException if start or end is null
*/
/*
public Period (Date start, Date end) {
if (start.compareTo(end) > 0)
throw new IllegalArgumentException(
start + " after " + end);
this.start = start;
this.end = end;
}*/
//Repaired constructor - make defensive copies of parameters
public Period (Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException(
start + " after " + end);
}
/*
public Date start() {
return start;
}
public Date end() {
return end;
}*/
//Repaired accessors - make defensive copies of internal fields
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
}
…
To make readObject mehod defensively, which can prevent being attacked:
// readObject method with defensive copying and validity checking
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
// Defensively copy our mutable components
start = new Date(start.getTime());
end = new Date(end.getTime());
// Check that our invariants are satisfied
if(start.compareTo(end) > 0)
throw new InvalidObjectException(start + "after" + end);
}
Item 77: For instance control, prefer enum types to readResolve
If you depend on readResolve for instance control, all instance fields with object reference types must be declared transient. The accessibility of readResolve is significant.
To summarized, you should use enum types to enforce instance control invariants whether possible. If this is not possible and you need a class to be both serializable and instance-controlled, you must provide a readResolve method and ensure that all of the class’s instance fields are either primitive or transient.
For example, the class Elvis can be attacked by ElvisStealer:
import java.io.Serializable;
import java.io.ObjectStreamException;
import java.util.Arrays;
// Broken singleton - has nontransient object reference field!
public class Elvis implements Serializable {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {}
private String[] favoriteSongs =
{ "Hound Dog", "Heartbreak Hotel" };
public void printFavorites() {
System.out.println(Arrays.toString(favoriteSongs));
}
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}
class ElvisStealer implements Serializable {
static Elvis impersonator;
private Elvis payload;
private Object readResolve() {
// Save a reference to the "unresolved" Elvis instance
impersonator = payload;
//Return an object of correct type for favorites field
return new String[] { "A Fool Such As I" };
}
private static final long serialVersionUID = 0;
}///:~
To fix the attack, we use enum type:
…
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
private String[] favoriteSongs =
{ "Hound Dog", "Heartbreak Hotel" };
public void printFavorites() {
System.out.println(Arrays.toString(favoriteSongs));
}
}
…
Item 78: Consider serialization proxies instead of serialized
The advantages of serialization proxy:
1).Stopping the bogus byte-stream attack and the internal field theft attack dead in their tracks;
2).Allowing the deserialized instance to have a different class from the originally serialized instance;
The disadvantages of serialization proxy:
1).It is not compatible with classes that are extendable by their clients. Also, it is not compatible with some classes whose object graphs contain circularities;
2).The added power and safety of the serialization proxy pattern are not free.
In summary, consider the serialization proxy pattern whenever you find yourself having to write a readObject or writeObject method on a class that is not extendable by its clients. This pattern is perhaps the easiest way to robustly serialize objects with nontrivial invariants.
Talk is cheap, show you the code:
1).
…
//Serialization proxy for Peroid class
private static class SerializationProxy implements Serializable {
private final Date start;
private final Date end;
SerializationProxy(Period p) {
this.start = p.start;
this.end = p.end;
}
// writeReplace method for the serialization proxy pattern
private Object writeReplace() {
return new SerializationProxy(this);
}
// readReplace method for the serialization proxy pattern
private void readObject(ObjectInputStream stream)
throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
// readResolve method for Period.SerializationProxy
private Object readResolve() {
return new Period(start, end);
}
private static final long serialVersionUID =
23409824823485285L;
}
…
2).
…
// EnumSet's serialization proxy
private static class EnumSetSerializationProxy <E extends Enum<E>>
implements Serializable {
// The element type of this enum set.
private final Class elementType;
// The elements contained in this enum set
private final Enum[] elements;
SerializationProxy(EnumSet set) {
elementType = set.elementType;
elements = set.toArray(EMPTY_ENUM_ARRAY);
}
private Object readResolve() {
EnumSet result = EnumSet.noneOf(elementType);
for(Enum e : elements)
result.add((E)e);
return result;
}
private static final long serialVersionUID =
362491234563181265L;
}
…