- Item 49: Check parameters for validity.
- Item 50: Make defensive copies when needed
- Item 51: Design method signatures carefully
- Item 52: Use overloading judiciously
- Item 53: Use
varargs
judiciously - Item 54: Return empty collections or arrays, not nulls
- Item 55: Return optionals judiciously
- Item 56: Write doc comments for all exposed API elements
- Item 57: Minimize the scope of local variables
- Item 58: Prefer for-each loops to traditional
for
loops - Item 59: Know and use the libraries
- Item 60: Avoid
float
anddouble
if exact answers are required - Item 61: Prefer primitive types to boxed primitives
- Item 62: Avoid strings where other types are more appropriate
- Item 63: Beware the performance of string concatenation
- Item 64: Refer to objects by their interfaces
- Item 65: Prefer interfaces to reflection
- Item 66: Use native methods judiciously
- Item 67: Optimize judiciously
- Item 68: Adhere to generally accepted naming conventions
- Item 69: Use exceptions only for exceptional conditions
- Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors.
- Item 71: Avoid unnecessary use of checked exceptions
- Item 72: Favor the use of standard exceptions
- Item 73: Throw exceptions appropriate to the abstraction
- Item 74: Document all exceptions thrown by each method
- Item 75: Include failure-capture information in detail messages
- Item 76: Strive for failure atomicity
- Item 77: Don't ignore exceptions
- Item 78: Synchronize access to shared mutable data
- Item 79: Avoid excessive synchronization
- Item 80: Prefer executors, tasks, and streams to threads
- Item 81: Prefer concurrency utilities to
wait
andnotify
- Item 82: Document thread safety
- Item 83: Use lazy initialization judiciously
- Item 84: Don't depend on the thread scheduler
- Item 85: Prefer alternatives to Java serialization
- Item 86: Implement
Serializable
with great caution - Item 87: Consider using a custom serialized form
- Item 88: Write
readObject
methods defensively - Item 89: For instance control, prefer enum types to
readResolve
- Item 90: Consider serialization proxies instead of serialized instances
Objects.requireNonNull
, assert
등의 활용. -ea
(or -enableassertions)
flag 없이는 assert가 동작하지 않는다.
- To summarize, each time you write a method or constructor, you should think about what restrictions exist on its parameters. You should document these restrictions and enforce them with explicit checks at the beginning of the method body. It is important to get into the habit of doing this. The modest work that it entails will be paid back with interest the first time a validity check fails.
- You must program defensively, with the assumption that clients of your class will do their best to destroy its invariants.
- To protect the internals of a
Period
instance from this sort of attack, it is essential to make a defensive copy of each mutable parameter to the constructor and to use the copies as components of thePeriod
instance in place of the originals:
// Reparied consstructor - makes 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(
this.start + " after " + this.end);
}
- With the new constructor in place, the previous attack will have no effect on the
Period
instance. Note that defensive copies are made before checking the validity of the parameters (Item 49), and the validity check is performed on the copies rather than on the originals. While this may seem unnatural, it is necessary. It protects the class against changes to the parameters from another thread during the window of vulnerability between the time the parameters are checked and the time they are copied. In the computer security community, this is known as a time-of-check/time-of-use or TOCTOU attack [Viega01]. - Do not use the
clone
method to make a defensive copy of a parameter whose type is sub-classable by untrusted parties. - In summary, if a class has mutable components that it gets from or returns to its clients, the class must defensively copy these components. If the cost of the copy would be prohibitive and the class trusts its clients not to modify the components inappropriately, then the defensive copy may be replaced by documentation outlining the client's responsibility not to modify the affected components.
- Choose method names carefully.
- Don't go overboard in providing convenience methods.
- Consider providing a "shorthand" only if it will be used often. When in doubt, leave it out.
- Avoid long parameter lists. Long sequences of identically typed parameters are especially harmful.
- For parameter types, favor interfaces over classes (Item 64).
- Prefer two-element enum types to
boolean
parameters.
The following program is a well-intentioned attempt to classify collections according to whether they are sets, lists, or some other kind of collection:
// Broken! - What does this program print?
public class CollectionClassifier {
public static String classify(Set<?> s) {
return "Set";
}
public static String classify(List<?> s) {
return "List";
}
public static String classify(Collection<?> s) {
return "Unknown Collection";
}
public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String, String>().values()
};
for (Collection<?> c : collections)
System.out.println(classify(c));
}
}
You might expect this program to print Set
, followed by List
and Unknown Collection
, but it doesn't. It prints Unknown Collection
three times. Why does this happen? Because the classify
method is overloaded, and the choice of which overloading to invoke is made at compile time. For all three iterations of the loop, the compile-time type of the parameter is the same: Collection<?>
. The runtime type is different in each iteration, but this does not affect the choice of overloading. Because the compile-time type of the parameter is Collection<?>
, the only applicable overloading is the third one, classify(Collection<?>)
, and this overloading is invoked in each iteration of the loop.
The behavior of this program is counterintuitive because selection among overloaded methods is static, while selection among overridden methods is dynamic. The correct version of an overridden method is chosen at runtime, based on the runtime type of the object on which the method is invoked.
public static String classify(Collection<?> c) {
return c instanceof Set ? "Set" :
c instanceof List ? "List" : "Unknown Collection";
}
- A safe, conservative policy is never to export two overloadings with the same number of parameters. These restrictions are not terribly onerous because you can always give methods different names instead of overloading them.
- Do not overload methods to take different functional interfaces in the same argument position. In the parlance of this item, different functional interfaces are not radically different. The Java compiler will warn you about this sort of problematic overload if you pass the command line switch
-Xlint:overloads
. - To summarize, just because you can overload methods doesn't mean you should. It is generally best to refrain from overloading methods with multiple signatures that have the same number of parameters. In some cases, especially where constructors are involved, it may be impossible to follow this advice. In these cases, you should at least avoid situations where the same set of parameters can be passed to different overloadings by the addition of casts. If this cannot be avoided, for example, because you are retrofitting an existing class to implement a new interface, you should ensure that all overloadings behave identically when passed the same parameters. If you fail to do this, programmers will be hard pressed to make effective use of the overloaded method or constructor, and they won't understand why it doesn't work.
// The right way to use varargs to pass one or more arguments
static int min(int firstArg, int... remainingArgs) {
int min = firstArg;
for (int arg : remainingArgs)
if (arg < min)
min = arg;
return min;
}
Exercise care when using varargs in performance-critical situations. Every invocation of a varargs method causes an array allocation and initialization.
Suppose you've determined that 95 percent of the calls to a method have three or fewer parameters.
public void foo() { }
public void foo(int a1) { }
public void foo(int a1, int a2) { }
public void foo(int a1, int a2, int a3) { }
public void foo(int a1, int a2, int a3, int... rest) { }
Now you know that you'll pay the cost of the array creation only in the 5 percent of all invocations where the number of parameters exceeds three. Like most performance optimizations, this technique usually isn't appropriate, but when it is, it's a lifesaver.
// The right way to return a possibly empty array
public Cheese[] getCheeses() {
return cheesesInStock.toArray(new Cheese[0]);
}
// Optimization - avoids allocating empty arrays
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheeses() {
return cheeseInStock.toArray(EMPTY_CHEESE_ARRAY);
}
// Don't do this - preallocating the array harms performance!
public Cheese[] getCheeses() {
return cheesesInStock.toArray(new Cheese[cheesesInStock.size()]);
}
- In summary, never return
null
in place of an empty array or collection. It makes your API more difficult to use and more prone to error, and it has no performance advantages.
- Optionals are similar in spirit to checked exceptions (Item 71), in that they force the user of an API to confront the fact that there may be no value returned. Throwing an unchecked exception or returning a
null
allows the user to ignore this eventuality, with potentially dire consequences. However, throwing a checked exception requires additional boilerplate code in the client. - Container types, including collections, maps, streams, arrays, and optionals should not be wrapped in optionals.
- So when should you declare a method to return
Optional<T>
rather thanT
? As a rule, you should declare a method to returnOptional<T>
if it might not be able to return a result and clients will have to perform special processing if no result is returned. That said, returning anOptional<T>
is not without cost. AnOptional
is an object that has to be allocated and initialized, and reading the value out of the optional requires an extra indirection. This makes optionals inappropriate for use in some performance-critical situations. Whether a particular method falls into this category can only be determined by careful measurement(Item 67). - Returning an optional that contains a boxed primitive type is prohibitively expensive compared to returning a primitive type because the optional has two levels of boxing instead of zero. Therefore, the library designers saw fit to provide analogues of
Optional<T>
for the primitive typesint
,long
, anddouble
. These optional types areOptionalInt
,OptionalLong
, andOptionalDouble
. They contain most, but not all, of the methods onOptional<T>
. Therefore, you should never return an optional of a boxed primitive type, with the possible exception of the "minor primitive types,"Boolean
,Byte
,Character
,Short
, andFloat
. - It is almost never appropriate to use an optional as a key, value, or element in a collection or array.
- To document your API properly, you must precede every exported class, interface, constructors, method, and field declaration with a doc comment.
- The doc comment for a method should describe succinctly the contract between the method and its client.
- Doc comments should be readable both in the source code and in the generated documentation.
- To avoid confusion, no two members of constructors in a class or interface should have the same summary description.
- When documenting an annotation type, be sure to document any members as well as the type itself.
- Whether or not a class or static method is thread-safe, you should document its thread-safety level, as described in Item 82. If a class is serializable, you should document its serialized form, as described in Item 87.
If you need access to the iterator, perhaps to call its remove
method, th preferrd idiom usse a traditional for
loop in plac of the for-each loop:
// Idiom for iteeratting when you need the iterator
for (Iterator<Element> i = c.iterator(); i.hasNext();) {
Element e = i.next();
... // Do something with e and i
}
- The
for
loop has one more advantage over thewhile
loop: it is shorter, which enhances readability. Here is another loop idiom that minimizes the scope of local variables:
for (int i = 0; n = expensiveComputation(); i < n; i++) {
... // Do something with i;
}
- The important thing to notice about this idiom is that it has two loop variables,
i
andn
, both of which have exactly the right scope. The second variable,n
, is used to store the limit of the first, thus avoiding the cost of a redundant computation in every iteration. As a rule, you should use this idiom if the loop test involves a method invocation that is guaranteed to return the same result on each iteration.
- In summary, the for-each loop provides compelling advantages over the traditional
for
loop in clarity, flexibility, and bug prevention, with no performance penalty. Use for-each loops in preference tofor
loops wherever you can.
// Common but deeply flawed!
static Random rnd = new Random();
static int random(int n) {
return Math.abs(rnd.nextInt()) % n;
}
- The third flaw in the
random
method is that it can, on rare occasions, fail catastrophically, returning a number outside the specified range. This is so because the method attempts to map the value returned byrnd.nextInt()
to a non-negativeint
by callingMath.abs
. IfnextInt()
returnsInteger.MIN_VALUE
,Math.abs
will also returnInteger.MIN_VALUE
, and the remainder operator (%) will return a negative number, assumingn
is not a power of two. This will almost certainly cause your program to fail, and the failure may be difficult to reproduce. - Use
Random.nextInt(int)
- As of Java 7, you should no longer use
Random
. For most uses, the random number generator of choice is nowThreadLocalRandom
. It produces higher quality random numbers, and it's very fast. - Numerous features are added to the libraries in every major release, and it pays to keep abreast of these additions.
- The libraries are too big to study all the documentation [Java9-api], but every programmer should be familiar with the basics of
java.lang
,java.util
, andjava.io
, and their subpackages.
int
, long
, BigDecimal
써라.
- Applying the
==
operator to boxed primitives is almost always wrong.
Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
int i = iBoxed, j= jBoxed; // Auto-unboxing
return i < j ? - 1 : (i == j ? 0 : 1);
};
- When you mix primitives and boxed primitives in an operation, the boxed primitives is auto-unboxed. If a null object reference is auto-unboxed, you get a
NullPointerException
.
public class Unbelievable {
static Integer i;
public static void main(String[] argrs) {
if (i == 42) // Thorws NullPointerException
System.out.println("Unbelievable");
}
}
- To summarize, avoid the natural tendency to represent objects as strings when better data types exist or can be written. Used inappropriately, strings are more cumbersome, less flexible, slower, and more error-prone than other types. Types for which strings are commonly misused include primitive types, enums, and aggregate types.
- Using the sting concatenation operator repeatedly to concatenate n strings requires time quadratic in n.
- The moral is simple: Don't use the string concatenation operator to combine more than a few strings unless performance is irrelevant. Use
StringBuilder
's append method instead. Alternatively, use a character array, or process the strings one at a time instead of combining them.
- If appropriate interface types exist, then parameters, return values, variables, and fields should all be declared using interface types.
- If there is no appropriate interface, just use the least specific class in the class hierarchy that provides the required functionality.
Reflection allows one class to use another, even if the latter class did not exist when the former was compiled. This power, however, comes at a price.
- You lose all the benefits of compile-time type checking, including exception checking. If a program attempts to invoke a nonexistent or inaccessible method reflectively, it will fail at runtime unless you've taken special precautions.
- The code required to perform reflective access is clumsy and verbose. It is tedious to write and difficult to read.
- Performance suffers. Reflective method invocation is much slower than normal method invocation. Exactly how much slower is hard to say, as there are many factors at work. On my machine, invoking a method with no input parameters and an
int
return was eleven times slower when done reflectively.
In summary, reflection is a powerful facility that is required for certain sophisticated system programming tasks, but it has many disadvantages. If you are writing a program that has to work with classes unknown at compile time, you should, if at all possible, use reflection only to instantiate objects, and access the objects using some interface or superclass that is known at compile time.
The Java Native Interface (JNI) allows Java programs to call native methods, which are methods written in native programming languages such as C or c++. Historically, native methods have had three main uses. They provide access to platform-specific facilities such as registries. They provide access to existing libraries of native code, including legacy libraries that provide access to legacy data. Finally, native methods are used to write performance-critical parts of applications in native languages for improved performance.
It is rarely advisable to use native methods for improved performance.
In summary, think twice before using native methods, It is rare that you need to use them for improved performance. If you must use native methods to access low-level resources or native libraries, use as little native code as possible and test it throughly. A single bug in the native code can corrupt your entire application.
- Strive to write good programs rather than fast ones.
- Strive to avoid design decisions that limit performance.
- Consider the performance consequences of your API design decisions.
- It is a very bad idea to wrap an API to achieve good performance.
- Measure performance before and after each attempted optimization.
제목이 곧 내용... checkstyle 애용하자.
- Exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow.
- A well-designed API must not force its clients to use exceptions for ordinary control flow.
Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors.
- Use checked exceptions for conditions from which the caller can reasonably be expected to recover.
- Use runtime exceptions to indicate programming errors.
- When in doubt, throw unchecked exceptions.
- Don't define any throwables that are neither checked exceptions nor runtime exceptions.
- Provide methods on your checked exceptions to aid in recovery.
- In summary, when used sparingly, checked exceptions can increase the reliability of programs; when overused, they make APIs painful to use. If callers won't be able to recover from failures, throw unchecked exceptions. If recovery may be possible and you want to force callers to handle exceptional conditions, first consider returning an optional. Only if this would provide insufficient information in the case of failure should you throw a checked exception.
- Choosing which exception to reuse can be tricky because the "occasions for use" in the table above do not appear to be mutually exclusive. Consider the case of an object representing a deck of cards, and suppose there were a method to deal a hand from the deck that took as an argument the size of the hand. If the caller passed a value larger than the number of cards remaining in the deck, it could be construed as an
IllegalArgumentException
(thehandSize
parameter value is too high) or andIllegalStateExecption
(the deck contains too few cards). Under these circumstances, the rule is to throwIllegalStateException
if no argument values would have worked, otherwise throwIllegalArguemtException
.
- 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:
// Exception Translation
try {
... // Use lower-level abstraction to do our bidding
} catch (LowerLevelException e) {
throw new HigherLevelException(...);
}
- While exception translation is superior to mindless propagation of exceptions from lower layers, it should not be overused.
- Always declare checked exceptions individually, and document precisely the conditions under which each one is thrown using the Javadoc
@throws
tag. - Do not use
@throws
keyword on unchecked exceptions. - If an exception is thrown by many methods in a class for the same reason, you can document the exception in the class's documentation comment rather than documenting it individually for each method.
- To capture a failure, the detail message of an exception should contain the values of all parameters and fields that contributed to the exception.
- Do not include passwords, encryption keys, and the like in detail messages.
- A failed method invocation should leave the object in the state that it was in prior to the invocation.
- In summary, 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. Unfortunately, plenty of existing API documentation fails to live up to this ideal.
// Empty catch block ignores exception - Highly suspect!
try {
...
} catch (SomeException e) {
}
- If you choose to ignore exception, the
catch
block should contain a comment explaining why it is appropriate to do so, and the variable should be nameignored
:
Future<Integer> f = exec.submit(planarMap::chromaticNumber);
int numColors = 4; // Default; guaranteed sufficient for any map
try {
numColors = f.get(1L, TimeUnit.SECONDS);
} catch (TimeOutException | ExecutionException ignored) {
// Use default: minimal coloring is desirable, not required.
}
The synchronized
keyword ensures that only a single thread can execute a method or block at one time. Many programmers think of synchronization solely as a means of mutual exclusion, to prevent an object from being seen in an inconsistent state by one thread while it's being modified by another.
The view is correct, but its' only half the story. Without synchronization, one thread's changes might not be visible to other threads. Not only does synchronization prevents threads from observing an object in an inconsistent state, but it ensures that each threads from observing an object in an inconsistent state, but it ensures that each thread entering a synchronized method or block sees the effects of all previous modifications that were guarded by the same lock.
- Synchronization is required for reliable communication between threads as well as for mutual exclusion.
- Synchronization is not guaranteed to work unless both read and write operations are synchronized.
- To avoid liveness and safety failures, never code control to the client within a synchronized method or block.
- In summary, to avoid deadlock and data corruption, never call an alien method from within a synchronized region. More generally, keep the amount of work that you do from within synchronized regions to a minimum. When you are designing a mutable class, think about whether it should do its own synchronization. In the multicore era, it is more important than ever not to oversynchronize. Synchronize your class internally only if there is a good reason to do so, and document your decision clearly (Item 82).
제목이 곧 내용..
// Concurrent canonicalizing map atop ConcurrentMap - faster!
public static String intern(String s) {
String result = map.get(s);
if (result == null) {
result = map.putIfAbsent(s, s);
if (result == null) {
result = s;
}
}
return result;
}
- Use ConcurrentHashMap in preference to Collections.synchronizedMap. Simply replacing synchronized maps with concurrent maps can dramatically increase the performance of concurrent applications.
- For interval timing, always use
System.nanoTime
rather thanSystem.concurrentTimeMillis
.System.nanoTime
is both more acfcurate and more precise and is unaffected by adjustment to the system's real-time clock. - Always use the wait loop idiom to invoke the
wait
method; never invoke it outside of a loop
// The standard idiom for using the wait method
synchronized (obj) {
while (<condition does not hold>)
obj.wait(); // (Release lock, and reacquires on wakeup)
... // Perform action appropriate to condition
}
- There is seldom, if ever, a reason to use
wait
andnotify
in new code. If you maintain code that useswait
andnotify
, make sure that it always invokeswait
from within awhile
loop using the standard idiom. ThenotifyAll
method should generally be used in preference tonotify
. Ifnotify
is used, great care must be taken to ensure liveness.
- The presence of the
synchronized
modifier in a method declaration is an implementation detail, not a part of its API. - To enable safe concurrent use, a class must clearly document what level of thread safety it supports.
- Immutable
- Unconditionally thread-safe
- Conditionally thread-safe
- Not thread-safe
- Thread-hostile
- This class is unsafe for concurrent use event if every method invocation is surrounded by external synchronization. Thread hostility usually results from modifying static data without synchronization.
- Lock fields should always be declared
final
.
- If you use lazy initialization to break an initialization circularity, use a synchronized accessor because it is the simplest, clearest alternative:
// Lazy initialization of instance field - synchronized accessor
private FieldType field;
private synchronized FieldType getField() {
if (field == null)
field = computeFieldValue();
return field;
}
- If you need to use lazy initialization for performance on a static field, use the lazy initialization holder class idiom. This idiom exploits the guarantee that a class will not be initialized until it is used [JLS, 12.4.1]. Here'w how it looks:
// Lazy initialization holder class idiom for static fields
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() { return FieldHolder.field; }
The beauty of this idiom is that the getField
method is not synchronized and performs only a field access, so lazy initialization adds practically nothing to the cost of access.
- If you need to use lazy initialization for performance on an instance field, use the double-check idiom.
// Dobule-check idiom for lazy initialization of instance fields
private **volatile** FieldType field;
private FieldType getField() {
FieldType result = field;
if (result != null) // First check (no locking)
return result;
synchronized(this) {
if (field == null) // Second check (with locking)
field = computeFieldValue();
return field;
}
}
// Single-check idiom - can cause repeated initialization!
private **volatile** FieldType field;
private FieldType getField() {
FieldType result = field;
if (field == null) // Second check (with locking)
field = result = computeFieldValue();
return result;
}
}
- In summary, you should initialize most fields normally, not lazily. 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. For instance fields, it is the double-check idiom; for static fields, the lazy initialization holder class idiom. For instance fields that can tolerate repeated initialization, you may also consider the single-check idiom.
- Any program that relies on the thread scheduler for correctness or performance is likely to be nonportable.
- Resist the temptation to "fix" the program by putting in calls to
Thread.yield
. **Thread.yield
has no testable semantics.**
- The leading cross-platform structured data representations are JSON and Protocol Buffers, also known as protobuf. JSON was designed by Dougals Crockford for browser-server communication, and protocol buffers were designed by Google for storing and interchanging structured data among its server. Even though these representations are sometimges called language-neutral, JSON was originally developed for JavaScript and protobuf for C++; both representations retain vestiges of their origins.
- The most significant differences between JSON and protobuf are that JSON is text-based and human-readable, whereas protobuf is binary and substantially more efficient; and that JSON is exclusively a data representation, whereas protobuf offers schemas (types) to document and enforce appropriate usage. Although protobuf is more efficient that JSON, JSON is extremely efficient for a text-based representation. And while protobuf is a binary representation, it does provide an alternative text representation for use where human-readability is desired (pbtxt).
- If you can't avoid Java serialization entirely, never deserialize untrusted data.
- Prefer allowlisting to blocklisting.
- A major cost of implementing
Serializable
is that it decreases the flexibility to change a class's implementation once it has been released. - A second cost of implementing
Serializable
is that it increases the likelihood of bugs and security holes (Item 85). - A third cost of implementing
Serializable
is that it increases the testing burden associated with releasing a new version of a class. - Classes designed for inheritance (Item 19) should rarely implement
Serializable
, and interfaces should rarely extend it. - Inner classes (Item 24) should not implement
Serializable
.
그냥 Serializable
은 안 쓰는게 좋지 않을까
- Do not accept the default serialized form without first considering whether it is appropriate.
- The default serialized form is likely to be appropriate if an object's physical representation is identical to its logical content.
- Even if you decide that the default serialized form is appropriate, you often must provide a
readObject
method to ensure invariants and security. - Using the default serialized form when a object's physical representation differs substantially from its logical content has four disadvantages.
- It permanently ties the exported API to the current internal representation.
- It can consume excessive space.
- It can consume excessive time.
- It can cause stack overflows.
- Regardless of what serialized form you choose, declare an explicit serial version UID in every serializable class you write. If no serial version UID is provided, an expensive computation is performed to generate one at runtime.
- Do not change the serial version UID unless you want to break compatibility with all existing serialized instances of a class.
- To summarize, anytime you write a
readObject
method, adopt the mind-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. While the examples in this item concern a class that uses the default serialized form, all of the issues what were raised apply equally to classes with custom serialized forms. Here, in summary form, are the guidelines for writing areadObject
method:- 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.
- Check any invariants and throw an
InvalidObjectException
if a check fails. The checks should follow any defensive copying. - If an entire object graph must be validated after it is deserialized, use the
ObjectInputValidation
interface (not discussed in this book). - Do not invoke any overridable methods in the class, directly or indirectly.
- To summarize, use enum type to enforce instance control invariants wherever 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 either primitive or transient.
- Consider the immutable
Period
class written in Item 50 and made serializable in Item 88. Here is a serialization proxy for this class.Period
is so imple that its serialization proxy has exactly the same fields as the class:
// Serialization proxy for Period 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;
}
private static final long serialVersionUID =
234098243823485285L; // Any number will do (Item 87)
}
Next, add the following writeReplace
method to the enclosing class. This method can be copied verbatim into any class with a serialization proxy:
// writeReplace method for the serialization proxy pattern
private Object writeReplace() {
return new SerializationProxy(this);
}
// readObject 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); // Uses public constructor
}
- In summary, consider the serialization proxy pattern whenever you find yourself having to write a
readObject
orwriteObject
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.