effective java 3ed

Creating and Destroying Objects

1: Consider static factory methods instead of constructors

  • One advantage of static factory methods is that, unlike constructors, they have names.
  • A second advantage of static factory methods is that, unlike constructors, they are not required to create a new object each time they’re invoked. – Boolean.valueOf(boolean)
  • A third advantage of static factory methods is that, unlike constructors, they can return an object of any subtype of their return type. - java.util.Collections
  • A fourth advantage of static factory methods is that the class of the returned object can vary from call to call as a function of the input parameters. - EnumSet
  • A fifth advantage of static factory is that the class of the returned object need not exist when the class containing the method is written. - Service provider framework (JDBC)

  • The main limitation of providing only static factory methods is that classes without public or protected constructors cannot be subclassed. – Use composition instead of inheritance

  • A second shortcoming of static factory methods is that they are hard for programmers to find.
    Naming convention:
    • from
    • of
    • valueOf
    • instance/getInstance
    • create/newInstance
    • get_Type_
    • new_Type_
    • type

2: Consider a builder when faced with many constructor parameters

  • The Builder pattern simulates named optional parameters
  • The Builder pattern is well suited to class hierarchies – simulated self-type

3: Enforce the singleton property with a private constructor or an enum type

  • A single-element
    enum type is often the best way to implement a singleton

4: Enforce noninstantiability with a private constructor

5: Prefer dependency injection to hardwiring resources

  • Static utility classes and singletons are inappropriate for classes whose behavior is parameterized by an underlying resource.

6: Avoid creating unnecessary objects

  • While String.matches is the easiest way to check if a String matches a regular expression, it’s not suitable for repeated use in performance-critical situations. – creating Pattern instance is expensive, cache it as a static final field to improve performance
  • Adapter could be cached as well – Map.keyset()
  • Prefer primitives to boxed primitives, and watch out for unintentional auto-boxing.

7: Eliminate obsolete object references

8: Avoid finalizers and cleaners

9: Prefer try-with-resources to try-finally

Methods common to all objects

10: Obey the general contract when overriding equals

  • There is no way to extend an instantiable class and add a value component while preserving the equals contract. – Favor composition over inheritance

  • Use the == operator to check if the argument is a reference to this object.
  • Use the instanceof operator to check if the argument has the correct type.
  • Cast the argument to the correct type
  • For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object.

  • Use Float.compare(f, f) for float and double fields.
  • Use Arrays.equals for array fields.
  • Use Objects.equals(o, o) to support null.

  • when you are finished writing your equals method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent?

– Use AutoValue or Immutable

11: Always override hashCode when you override equals

  • You MUST override hashCode in every class that overrides equals.
  • equal objects MUST have equal hash codes.
  • com.google.common.hash.Hashing
  • Objects.hash – slow

12: Always override toString

13: Override clone judiciously

  • In practice, a class implementing Cloneable is expected to provide a properly functioning public clone method.
  • immutable classes should never provide a clone method – wasteful copy
  • In effect, the clone method functions as a constructor; you must ensure that it does no harm to the original object and that it properly establishes invariants on the clone. – to handle mutable state
  • The Cloneable architecture is incompatible with normal use of final fields referring to mutable objects.
  • A better approach to object copying is to provide a copy constructor or copy factory.

14: Consider implementing Comparable

  • Violation the provision of compareTo consistent with equals – put BigDecimal(“1.0”) and BigDecimal(“1.00”) into HashSet (using equals) and TreeSet (using compareTo)
  • Use boxed primitive class static method compare or the static compare constructor methods in the Comparator interface, and avoid the use of the < and > operators.

Classes and Interfaces

15: Minimize the accessibility of classes and members

  • it is wrong for a class to have a public static final array field, or an accessor that returns such a field. – use unmodifiableList or return a copy instead

16: In public classes, use accessor methods, not public fields

17: Minimize mutability

Immutable class:

  • Don’t provide methods that modify the object’s state.
  • Ensure that the class can’t be extended. – final class or private constructor + public static factory
  • Make all fields final. – mark fields as final is necessary to ensure correct behavior if a reference to a newly created instance is passed from one thread to another without synchronization
  • Make all fields private.
  • Ensure exclusive access to any mutable components. – defensive copies in constructors, accessor and readObject methods

  • No method may produce an external visible change in the object’s state.

18: Favor composition over inheritance

19: Design and Document for inheritance or else prohibit it

  • constructors must NOT invoke overridable methods

20: Prefer interfaces to abstract classes

  • Interfaces are ideal for defining mixins
  • Interfaces allow for the construction of nonhierarchical type frameworks
  • Interface enable safe, powerful functionality enhancements
    – skeletal implementation class as template method pattern

21: Design interfaces for posterity

Design interface with great care

22: Use interfaces only to define types

  • The constant interface pattern is a poor use of interfaces – constants 1. add to tied interface or class, 2. as enumerated type, 3. define in a noninstantiable utility class

23: Prefer class hierarchies to tagged classes

24: Favor static member classes over nonstatic

  • static member class : has access to all of the enclosing class’s static members
  • nonstatic member class : implicitly associated with an enclosing instance of its containing class. – use as Adapter to allow an instance of outer class to be viewed as an instance of other

25: Limit source files to a single top-level class

Generics

26: Don’t use raw type

  • if you use raw types, you lose all the safety and expressiveness benefits of generics.
  • but not if you use a parameterize type such as List<Object>
  • you can’t put any element (other than null) into a Collection<?>

  • you must use raw types in class literals – List.class
  • Preferred way to use instanceof operator with generic types:
1
2
3
4
5

if (o instanceof Set) { // Raw type
Set<?> s = (Set<?>) o; // Wildcard type
...
}

27: Eliminate unchecked warnings

  • If you can’t eliminate a warning, but you can prove that the code that provoked the warning is typesafe, then (and only then) suppress the warning with an @SuppressWarnings(“unchecked”) annotation.
  • Always use the SuppressWarnings annotation on the smallest scope possible.

28: Prefer lists to arrays

  • arrays and generics have very different type rules. you’d better not mix them.

29: Favor generic types

30: Favor generic methods

  • recursive type bound
1
2
// Using a recursive type bound to express mutual comparability
public static <E extends Comparable<E>> E (Collection<E> c);
  • simulated self-type idiom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}

abstract Pizza build();

// Subclasses must override this method to return "this"
protected abstract T self();
}

Pizza(Builder<?> builder) {
toppings = builder.toppings.clone(); // See Item 50
}

31: Use bounded wildcards to increase API flexibility

  • parameterized types are invariant – List is neither a subtype or supertype of List
  • For maximum flexibility, use wildcard types on input parameters that represent producers or consumers.
  • PECS: producer-extends, consumer-super
  • do not use bounded wildcard types as return types
1
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)
  • use Comparable<? super T> in preference to Comparable
  • Use Comparator<? super T> in preference to Comparator – comparable and comparator are always consumers
1
public static <T extends Comparable<? super T>> T (List<? extends T> list)
  • If a type parameter appears only once in a method declaration, replace it with a wildcard. – but you can’t put any value except null into a List<?>
1
2
3
4
// Two possible declarations for the swap method
public static <E> void swap(List<E> list, int i, int j);
// Preferred
public static void swap(List<?> list, int i, int j);

32: Combine generics and varargs judiciously

  • It is unsafe to store a value in a generic varargs array parameter.
  • The @SafeVarargs annotation constitutes a promise by the author of a method that it is typesafe. – if the varargs parameter array is used only to transmit a variable number of arguments from the caller to the method then the method is safe.
  • Use @SafeVarargs on every method with a varargs parameter of a generic or parameterized type
  • Use List instead of varargs

33. Consider typesafe heterogeneous constainers

  • type token : Class, use Class.cast to check type
  • List.class is a syntax error
  • Class.asSubclass used to cast Class<?> to Class<? extend XXX>

Enums and Annotations

34. Use enums instead of int constants

  • To associate data with enum constants, declare instance fields and write a constructor that takes the data and stores it in the fields.
  • constant-specific method implementations : declare abstract method
  • E.values(), E.valueOf(name)
  • Enum constructor aren’t permitted to access the enum’s static fields, with the exception of constant variables.
  • Switches on enums are good for augmenting enum types with constant-specific behavior. – Otherwise a nested strategy enum could be introduced.
  • Use enums any time you need a set of constants whose members are known at compile time.
  • The arguments passed to enum constructors are evaluated in a static context. – cannot access instance member

35. Use instance fields instead of ordinals

  • Never derive a value associated with an enum from its ordinal; store it in an instance field instead

36. Use EnumSet instead of bit fields

  • because an enumerated type will be used in sets, there is no reason to represent it with bit fields.

37. Use EnumMap instead of ordinal indexing

  • Use EnumMap when the key is enums.
1
2
3
4
// Using a stream and an EnumMap to associate data with an enum
System.out.println(Arrays.stream(garden)
.collect(groupingBy(p -> p.lifeCycle,
() -> new EnumMap<>(LifeCycle.class), toSet())));
  • if the relationship is multidimensional, use EnumMap<…, EnumMap<…>>

38. Emulate extensible enums with interfaces

  • While you cannot write an extensible enum type, you can emulate it by writing an interface to accompany a basic enum type that implements the interface.
    • You don’t have to declare the abstract method in the interface
    • Class.getEnumConstants() / enum.getValues()

39. Prefer annotations to naming patterns

  • @Repeatable introduced from Java 8, but error-prone

    • To detect repeated and non-repeated annotations with isAnnotationPresent, you much check for both the annotation type and its containing annotation type.

      1
      2
      3
      	// Processing repeatable annotations
      if (m.isAnnotationPresent(ExceptionTest.class)
      || m.isAnnotationPresent(ExceptionTestContainer.class))

40. Consistently use the Override annotation

41. Use marker interfaces to define types

  • Marker interfaces advantages:

    • Marker interfaces define a type that is implemented by instances of the marked class; marker annotations do not.
    • Marker interfaces can be targeted more precisely.
  • Marker annotations advantages:

    • they are part of the larger annotation facility

Lambdas and Streams

42. Prefer lambdas to anonymous classes

  • Omit the types of all lambda parameters unless their presence makes your program cleaner.
  • Lambdas lack names and documentation; if a computation isn’t self-explanatory, or exceeds a few lines, don’t put it in a lambda.
  • Lambdas cannot access themselves

43. Prefer method references to lambdas

  • Where method references are shorter and cleaner, use them; where they aren’t, stick with lambda.

44. Favor the use of standard functional interfaces

  • Prefer passing functional object over Template Method pattern.
  • If one of the standard functional interfaces does the job, you should generally use it in preference to a purpose-built functional interface.
  • 6 basic interfaces:
Interface Function Signature Example
UnaryOperator<T> T apply(T t) String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) BigInteger::add
Predicate<T> boolean test(T t) Collection::isEmpty
Function<T, R> R apply(T t) Arrays::asList
Supplier<T> T get() Instant::now
Consumer<T> void accept(T t) System.out::println
  • variants:

    • int, long, double variants
    • 9 additional variants of the SrcToResultFunction and SrcToOjbFunction
    • BiPredicate<T, U>, BiFunction<T, U, R> and BiConsumer<T, U>
      • ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, and
        ToDoubleBiFunction<T,U>.
      • ObjDoubleConsumer<T>, ObjIntConsumer<T>, and
        ObjLongConsumer<T>
      • BooleanSupplier
  • Don’t be tempted to use basic functional interfaces with boxed primitives instead of primitive functional interfaces.

  • Always annotate your functional interfaces with the @FunctionalInterface annotation

45. Use streams judiciously

  • Overusing streams makes programs hard to read and maintain.
  • In the absence of explicit types, careful naming of lambda parameters is essential to the readability of stream pipeline.
  • Using helper methods is even more important for readability in stream pipelines than in iterative code.
  • Should refrain from using stream to process char values.

  • For iterative code with code block
    • read and modify any local variable in scope
    • can return, break, continue or throw exception
  • For Stream with lambda
    • Uniformly transform sequences of elements
    • Filter sequences of elements
    • Combine sequences of elements using a single operation
    • Accumulate sequences of elements into a collection, perhaps grouping by
    • Search

46. Prefer side-effect-free functions in streams

  • The Stream.forEach operation should be used only to report the result of a stream computation, not to perform the computation.
  • java.util.stream.Collectors:
    • toList(), toSet(), and toCollection(collectionFactory)
    • to map:
      • toMap(keyMapper, valueMapper)
      • if any key conflict:
        • toMap(keyMapper, valueMapper, merge)
        • merge function can be
          • BinaryOperator.maxBy(comparator)
          • BinaryOperator.minBy(comparator)
      • toMap(keyMapper, valueMapper, merge, mapFactory)
      • toConcurrentMap()
    • groupingBy
      • groupingBy(key classifier)
      • groupingBy(key classifier, downstream collector)
      • groupingBy(key classifier, map factory, downstream collector)
      • counting, summing, averaging, summarizing, and reducing, filtering, mapping, flatMapping and collectingAndThen methods to make downstream collectors can act as “ministreams”
    • maxBy, minBy
    • joining – only for CharSequence stream, concatenates the elements with delimiter

47. Prefer Collection to Stream as return type

  • Collection limitations

    • max size is Integer.MAX_VALUE = 2^31 - 1 (AbstractCollection.size())
  • Stream.concat

48. Use caution when making stream parallel

  • Parallelizing a pipeline is unlikely to increase its performance if the source is from Stream.iterate(), or the intermediate operation limit() is used. – the default parallelization strategy deals with unpredictability of limit by processing a few extra elements.
  • Do not parallelize stream pipelines indiscriminately
  • Performance gains from parallelism are best on streams over ArrayList, HashMap, HashSet, and ConcurrentHashMap instances; arrays; int ranges; and long ranges – which can be split into subranges accurately and cheaply & locality of reference
  • Not only can parallelizing a stream lead to poor performance, including liveness failures; it can lead to incorrect results and unpredictable behavior
  • Under the right circumstances, it is possible
    to achieve near-linear speedup in the number of processor cores simply by adding a parallel call to a stream pipeline. – data processing and ML
  • Use SplittableRandom over ThreadLocalRandom

Chapter 8. Methods

49. Check parameter for validity

  • The Objects.requireNonNull method, added in Java 7, is flexible and convenient, so there’s no reason to perform null checks manually anymore.

50. Make defensive copies when needed

  • You must program defensively, with the assumption that clients of your class will do their best to destroy its invariants.
  • Date is obsolete and should no longer be used in new code – use Instant, LocalDateTime, ZonedDateTime
  • it is essential to make a defensive copy of each mutable parameter to the constructor.
  • defensive copies are made before checking the validity of the parameters, and the validity check is performed on the copies rather than on the originals.
  • do not use the clone method to make a defensive copy of a parameter whose type is subclassable by untrusted parties.
  • return defensive copies of mutable internal fields

51. Design method signatures carefully

  • Choose method names carefully
  • Don’t go overboard in providing convenience methods
  • Avoid long parameter list
  • For parameter types, favor interfaces over classes
  • Prefer two-element enum types to boolean parameters

52. Use Overloading judiciously

  • the choice of which overloading to invoke is made at compile
    time – runtime type will not affect the choice of overloading.
  • selection among overloaded methods is static, while selection among overridden methods is dynamic.
  • avoid confusing uses of overloading
  • A safe, conservative policy is never to export two overloadings with the same number of parameters.
  • do not overload methods to take different functional interfaces in the same argument position.

53. Use varargs judiciously

54. Return empty collections or arrays, not nulls

  • passing a zero-length array into the toArray method to indicate the desired return type.
1
2
// Don’t do this - preallocating the array harms performance!
return cheesesInStock.toArray(new Cheese[cheesesInStock.size()]);

55. Return Optionals judiciously

  • Never return a null value from an Optional-returning method.
  • Optionals are similar in spirit to checked exceptions
  • Container types, including collections, maps, streams, arrays, and optionals should not be wrapped in optionals.
  • you should declare a method to return Optional if it might not be able to return a result and clients will have to perform special processing if no result is returned.
  • you should never return an optional of a boxed primitive type – use OptionalInt, OptionalLong, OptionalDouble
  • it is almost never appropriate to use an optional as a key, value, or element in a collection or array.

56. Write doc comments for all exposed API elements

  • To document your API properly, you must precede every exported class, interface, constructor, 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.
  • no two members or constructors in a class or interface should have the same summary description.
  • When documenting a generic type or method, be sure to document all type parameters
  • When documenting an enum type, be sure to document the constants
  • When documenting an annotation type, be sure to document any members
  • Whether or not a class or static method is thread-safe, you should document its thread-safety level

Chapter 9. General Programming

57. Minimize the scope of local variables

  • The most powerful technique for minimizing the scope of a local variable is to declare it where it is first used.
  • Nearly every local variable declaration should contain an initializer.
  • Keep method small and focused.

58. Prefer for-each loops to traditional for loops

  • you cannot use for-each
    • Destructive filtering – use iterator.remove() or collection.removeIf()
    • Transforming
    • Parallel iteration

59. Know and use the libraries

  • By using a standard library, you take advantage of the knowledge of the experts who wrote it and the experience of those who used it before you.
  • the random number generator of choice is now ThreadLocalRandom. – For fork join pools and parallel streams, use SplittableRandom.
  • Numerous features are added to the libraries in every major release, and it pays to keep abreast of these additions.

60. Avoid float and double if exact answers are required

  • The float and double types are particularly ill-suited for monetary calculations – use BigDecimal, int, or long for monetary calculations.

61. Prefer primitive types to boxed types

  • Applying the == operator to boxed primitives is almost always wrong.
  • when you mix primitives and boxed primitives in an operation, the boxed primitive is auto-unboxed.

62. Avoid strings where other types are more appropriate

  • Strings are poor substitutes for other value types.
  • Strings are poor substitutes for enum types.
  • Strings are poor substitutes for aggregate types.
  • Strings are poor substitutes for capabilities. – ThreadLocal implementation as sample use Key-based APIs rather than String-based APIs

63. Beware the performance of string concatenation

  • Using the string concatenation operator repeatedly to concatenate n strings requires time quadratic in n. – Use StringBuilder instead

64. Refer to objects by their interfaces

  • If appropriate interface types exist, then parameters, return values, variables, and fields should all be declared using interface types.
  • It is entirely appropriate to refer to an object by a class rather than an interface if no appropriate interface exists.

65. Prefer interfaces to reflection

66. Use native methods judiciously

67. Optimize judiciously

  • 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 warp an API to achieve good performance
  • measure performance before and after each attempted optimization.

  • do think about performance while you’re designing systems, especially while you’re designing APIs, wire-level protocols, and
    persistent data formats
    .
  • locate the source of the problem with the aid of a profiler
  • examine your choice of algorithm

68. Adhere to generally accepted naming conventions

69. 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.
  • A well-designed API must not force its clients to use exceptions for ordinary control flow.

70. Use checked exception for recoverable conditions and runtime exceptions for programming error

  • use checked exceptions for conditions from which the caller can reasonably be expected to recover.
  • Use runtime exceptions to indicate programming errors.
  • all of the unchecked throwables you implement should subclass RuntimeException

71. Avoid unnecessary use of checked 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.

72. Favor the use of standard exceptions

  • throw IllegalStateException if no argument values would have worked, otherwise throw IllegalArgumentException.

73. 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. – exception translation –> exception chain

74. Document all exceptions thrown by each method

  • Always declare checked exceptions individually, and document precisely the conditions under which each one is thrown
  • Use the Javadoc @throws tag to document each exception that a method can throw, but do not use the throws keyword on unchecked exceptions.

75. Include failure-capture information in detail messages

  • 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.

76. Strive for failure atomicity

  • Generally speaking, a failed method invocation should leave the object in the state that it was in prior to the invocation. – failure-atomic

77. Don’t ignore exceptions

  • If you choose to ignore an exception, the catch block should contain a comment explaining why it is appropriate to do so, and the variable should be named ignored

Concurrency

78. Synchronize access to shared mutable data

  • 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.
  • confine mutable data to a single thread
  • safely publish an object reference:
    • store it in a static field as part of class initialization
    • store it in a volatile filed | final field | a field that is accessed with normal locking
    • put it into a concurrent collection

79. Avoid excessive synchronization

  • To avoid liveness and safety failures, never cede control to the client within a synchronized method or block.
  • As a rule, you should do as little work as possible inside synchronized regions.

80. Prefer executors, tasks, and streams to threads

Use Executor Framework

81. Prefer concurrency utilities to wait and notify

  • Given the difficulty of using wait and notify correctly, you should use the higher-level concurrency utilities instead.
  • it is impossible to exclude concurrent activity from a concurrent collection; locking it will only slow the program. – putIfAbsent
  • use ConcurrentHashMap in preference to Collections.synchronizedMap.
  • Use Thread.currentThread().interrupt() when catch InterruptedException
  • Always use the wait loop idiom to invoke the wait method; never invoke it outside of a loop.
  • There is seldom, if ever, a reason to use wait and notify in new code.

82. Document thread safety

  • To enable safe concurrent use, a class must clearly document what level of thread safety it supports.
    • Immutable – String, BigInteger
    • Unconditional thread-safe – AtomicLong, ConcurrentHashMap
    • Conditional thread-safe – some methods require external synchronization: Collections.synchronized
    • Not thread-safe
    • Thread-hostile – bug
  • Lock fields should always be declared final.

83. Use lazy initialization judiciously

  • Under most circumstances, normal initialization is preferable to lazy initialization.
  • If you use lazy initialization to break an initialization circularity, use a synchronized accessor
  • If you need to use lazy initialization for performance on a static field, use the lazy initialization holder class idiom.
  • If you need to use lazy initialization for performance on an instance field, use the double-check idiom. – volatile needed

84. Don’t depend on the thread scheduler

  • Any program that relies on the thread scheduler for correctness or performance is likely to be nonportable. – make the number of runnable threads not significantly greater than the number of processors.
  • Threads should not run if they aren’t doing useful work. – sizing thread pools appropriately
  • No Thread.yield
  • No thread priorities

Serialization

85. Prefer alternatives to Java serialization

  • The best way to avoid serialization exploits is never to deserialize anything. – use JSON or protobuf instead
  • There is no reason to use Java serialization in any new system you write.
  • never deserialize untrusted data.
  • or use java.io.ObjectInputFilter with whitelisting rather than balcklisting

86. Implement Serialization with great caution

  • 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
  • A third cost of implementing Serializable is that it increases the testing burden associated with releasing a new version of a class.
  • Implementing Serializable is not a decision to be undertaken lightly.
  • Classes designed for inheritance (Item 19) should rarely implement Serializable, and interfaces should rarely extend it.
  • Inner classes should not implement Serializable.

87. Consider using a custom serialized form

  • 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 an object’s physical representation differs substantially from its logical data 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.
  • Before deciding to make a field nontransient, convince yourself that its value is part of the logical state of the object.
  • you must impose any synchronization on object serialization that you would impose on any other method that reads the entire state of the object.
  • Regardless of what serialized form you choose, declare an explicit serial version UID in every serializable class you write.
  • Do not change the serial version UID unless you want to break compatibility with all existing serialized instances of a class.

88. Write readObject methods defensively

  • When an object is deserialized, it is critical to defensively copy any field containing an object reference that a client must not possess. – every serializable immutable class containing private mutable components must defensively copy these components in its readObject method.

Summary:

  • 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.

89. 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.

90. Consider serialization proxies instead of serialized instances

  • Use writeReplace and readResolve