Effective Java: Write readObject Methods Defensively

Effective Java Review (90 Part Series)

1 Effective Java Tuesday! Let’s Consider Static Factory Methods
2 Effective Java Tuesday! The Builder Pattern!
86 more parts…
3 Effective Java Tuesday! Singletons!
4 Effective Java Tuesday! Utility Classes!
5 Effective Java Tuesday! Prefer Dependency Injection!
6 Effective Java Tuesday! Avoid Creating Unnecessary Objects!
7 Effective Java Tuesday! Don’t Leak Object References!
8 Effective Java Tuesday! Avoid Finalizers and Cleaners!
9 Effective Java Tuesday! Prefer try-with-resources
10 Effective Java Tuesday! Obey the `equals` contract
11 Effective Java Tuesday! Obey the `hashCode` contract
12 Effective Java Tuesday! Override `toString`
13 Effective Java Tuesday! Override `clone` judiciously
14 Effective Java Tuesday! Consider Implementing `Comparable`
15 Effective Java Tuesday! Minimize the Accessibility of Classes and Member
16 Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields
17 Effective Java Tuesday! Minimize Mutability
18 Effective Java Tuesday! Favor Composition Over Inheritance
19 Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It.
20 Effective Java Tuesday! Prefer Interfaces to Abstract Classes
21 Effective Java! Design Interfaces for Posterity
22 Effective Java! Use Interfaces Only to Define Types
23 Effective Java! Prefer Class Hierarchies to Tagged Classes
24 Effective Java! Favor Static Members Classes over Non-Static
25 Effective Java! Limit Source Files to a Single Top-Level Class
26 Effective Java! Don’t Use Raw Types
27 Effective Java! Eliminate Unchecked Warnings
28 Effective Java! Prefer Lists to Array
29 Effective Java! Favor Generic Types
30 Effective Java! Favor Generic Methods
31 Effective Java! Use Bounded Wildcards to Increase API Flexibility
32 Effective Java! Combine Generics and Varargs Judiciously
33 Effective Java! Consider Typesafe Heterogenous Containers
34 Effective Java! Use Enums Instead of int Constants
35 Effective Java! Use Instance Fields Instead of Ordinals
36 Effective Java! Use EnumSet Instead of Bit Fields
37 Effective Java! Use EnumMap instead of Ordinal Indexing
38 Effective Java! Emulate Extensible Enums With Interfaces.
39 Effective Java! Prefer Annotations to Naming Patterns
40 Effective Java! Consistently Use the Override Annotation
41 Effective Java! Use Marker Interfaces to Define Types
42 Effective Java! Prefer Lambdas to Anonymous Classes
43 Effective Java! Prefer Method References to Lambdas
44 Effective Java! Favor the Use of Standard Functional Interfaces
45 Effective Java! Use Stream Judiciously
46 Effective Java! Prefer Side-Effect-Free Functions in Streams
47 Effective Java! Prefer Collection To Stream as a Return Type
48 Effective Java! Use Caution When Making Streams Parallel
49 Effective Java! Check Parameters for Validity
50 Effective Java! Make Defensive Copies When Necessary
51 Effective Java! Design Method Signatures Carefully
52 Effective Java! Use Overloading Judiciously
53 Effective Java! Use Varargs Judiciously
54 Effective Java! Return Empty Collections or Arrays, Not Nulls
55 Effective Java! Return Optionals Judiciously
56 Effective Java: Write Doc Comments For All Exposed APIs
57 Effective Java: Minimize The Scope of Local Variables
58 Effective Java: Prefer for-each loops to traditional for loops
59 Effective Java: Know and Use the Libraries
60 Effective Java: Avoid Float and Double If Exact Answers Are Required
61 Effective Java: Prefer Primitive Types to Boxed Types
62 Effective Java: Avoid Strings When Other Types Are More Appropriate
63 Effective Java: Beware the Performance of String Concatenation
64 Effective Java: Refer to Objects By Their Interfaces
65 Effective Java: Prefer Interfaces To Reflection
66 Effective Java: Use Native Methods Judiciously
67 Effective Java: Optimize Judiciously
68 Effective Java: Adhere to Generally Accepted Naming Conventions
69 Effective Java: Use Exceptions for Only Exceptional Circumstances
70 Effective Java: Use Checked Exceptions for Recoverable Conditions
71 Effective Java: Avoid Unnecessary Use of Checked Exceptions
72 Effective Java: Favor The Use of Standard Exceptions
73 Effective Java: Throw Exceptions Appropriate To The Abstraction
74 Effective Java: Document All Exceptions Thrown By Each Method
75 Effective Java: Include Failure-Capture Information in Detail Messages
76 Effective Java: Strive for Failure Atomicity
77 Effective Java: Don’t Ignore Exceptions
78 Effective Java: Synchronize Access to Shared Mutable Data
79 Effective Java: Avoid Excessive Synchronization
80 Effective Java: Prefer Executors, Tasks, and Streams to Threads
81 Effective Java: Prefer Concurrency Utilities Over wait and notify
82 Effective Java: Document Thread Safety
83 Effective Java: Use Lazy Initialization Judiciously
84 Effective Java: Don’t Depend on the Thread Scheduler
85 Effective Java: Prefer Alternatives To Java Serialization
86 Effective Java: Implement Serializable With Great Caution
87 Effective Java: Consider Using a Custom Serialized Form
88 Effective Java: Write readObject Methods Defensively
89 Effective Java: For Instance Control, Prefer Enum types to readResolve
90 Effective Java: Consider Serialization Proxies Instead of Serialized Instances

In a previous item, a date range class was discussed. It includes Date fields and is careful to avoid breaking its invariants of its start date needing to come before its end date. The way that it accomplishes that is via careful coding of its constructor as well as its accessors. Let’s refresh our familiarity with this class:

public final class Period {
  private final Date start;
  private final Date end;

  public Period(Date start, Date end) {
    if (start.compareTo(end) > 0) {
      throw new IllegalArgumentException("Start is after end");
    }
    this.start = start;
    this.end = end;
  }

  public Date start() {
    return start;
  }

  public Date end() {
    return end;
  }
}

Enter fullscreen mode Exit fullscreen mode

Now let’s consider if the need arose to make this class Serializable. Thinking back to our previous item of discussion about the physical and logical models of our classes, it is reasonable to come to the conclusion that we can use this same form as the serialized form. Because of this, we may be tempted to simply throw implements Serializable on the class and call it good. Unfortunately, this would open our class up to not keeping its invariants.

As we have discussed before, Java’s default serialization system effectively creates a new hidden constructor for our class. In our existing implementation of the Period class the constructor is very critical to facilitating the safety of our class invariants. The effectively new constructor that exists with serialization does not have these same checks that protect our invariants so we must provide additional code to keep our internal data safe.

The way that Java facilitates our taking ownership of the construction of our object during deserialization is via the readObject method. This method takes a byte stream as its sole parameter and populates the state of an object. Usually, the byte stream that is consumed by this method will have been generated by serializing a normally constructed (and thus invariant keeping) instance of the object. However, since it is simply a stream of bytes, we can never be sure where those bytes came from and whether we can trust the source. We thus could be presented with an artificially created byte stream that does not meet our invariants and thus we can end up with an object that shouldn’t be possible to create.

With this new consideration in mind we may attempt to resolve the issue by adding a method such as:

private void readObject(ObjectInputStream inputStream)
                  throws IOException, ClassNotFoundException {
  inputStream.defaultReadObject();

  if (start.compareTo(end) > 0) {
    throw new InvalidObjectException(start + " after " + end);
  }
}

Enter fullscreen mode Exit fullscreen mode

While the spirit of the above is reasonable it is not enough. For the same reason that the original blog post was written (making defensive copies) this implementation opens itself up for being passed a byte stream that preserves the invariants of the class initially but then, since the reference could be modified by code outside of the class, the invariants could be broken by simply changing the value outside the class.

To solve this problem we must remember to always defensively copy any field that contains an object reference when using serialization. We thus can extend our above solution as follows to make it safe:

private void readObject(ObjectInputStream inputStream)
                  throws IOException, ClassNotFoundException {
  inputStream.defaultReadObject();

  start = new Date(start.getTime());
  end = new Date(end.getTime());

  if (start.compareTo(end) > 0) {
    throw new InvalidObjectException(start + " after " + end);
  }
}

Enter fullscreen mode Exit fullscreen mode

In this version, we do our defensive copy and then do our validity check. This allows us to have full control of the variables when we do the check. We, unfortunately, do need to remove the final modifier on the member variables for this to work but that is the price we pay for safe deserialization.

The test that you can perform to determine if the default deserialization method will work is, would you be comfortable having a constructor on your class that simply took in the member variables and saved the state without any further validation? If so, then you are likely safe using the default deserialization method as far as defensive copying goes, if not, you should take steps to protect yourself from these possible issues.

The final item of consideration is that of making sure not to call overridable methods from the readObject method. This is the same caution that applies to constructors (because readObject is effectively a constructor) and for the same reason. If you call overridable methods from the readObject method you are open to having those methods called before the whole state of the object is initialized which can lead to issues.

In summary, it is best to remember that when using serialization you are effectively creating a new public constructor for your class. If you wouldn’t be comfortable with having such a public constructor for your class, implement the readObject method and make sure that it takes care of the state in a defensive manner. The byte streams that your readObject method is passed should be handled as if they didn’t come from a trusted source (because it may not have). If an entire object graph must be validated after being deserialized you can use the ObjectInputValidation interface (not discussed in this item).

Effective Java Review (90 Part Series)

1 Effective Java Tuesday! Let’s Consider Static Factory Methods
2 Effective Java Tuesday! The Builder Pattern!
86 more parts…
3 Effective Java Tuesday! Singletons!
4 Effective Java Tuesday! Utility Classes!
5 Effective Java Tuesday! Prefer Dependency Injection!
6 Effective Java Tuesday! Avoid Creating Unnecessary Objects!
7 Effective Java Tuesday! Don’t Leak Object References!
8 Effective Java Tuesday! Avoid Finalizers and Cleaners!
9 Effective Java Tuesday! Prefer try-with-resources
10 Effective Java Tuesday! Obey the `equals` contract
11 Effective Java Tuesday! Obey the `hashCode` contract
12 Effective Java Tuesday! Override `toString`
13 Effective Java Tuesday! Override `clone` judiciously
14 Effective Java Tuesday! Consider Implementing `Comparable`
15 Effective Java Tuesday! Minimize the Accessibility of Classes and Member
16 Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields
17 Effective Java Tuesday! Minimize Mutability
18 Effective Java Tuesday! Favor Composition Over Inheritance
19 Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It.
20 Effective Java Tuesday! Prefer Interfaces to Abstract Classes
21 Effective Java! Design Interfaces for Posterity
22 Effective Java! Use Interfaces Only to Define Types
23 Effective Java! Prefer Class Hierarchies to Tagged Classes
24 Effective Java! Favor Static Members Classes over Non-Static
25 Effective Java! Limit Source Files to a Single Top-Level Class
26 Effective Java! Don’t Use Raw Types
27 Effective Java! Eliminate Unchecked Warnings
28 Effective Java! Prefer Lists to Array
29 Effective Java! Favor Generic Types
30 Effective Java! Favor Generic Methods
31 Effective Java! Use Bounded Wildcards to Increase API Flexibility
32 Effective Java! Combine Generics and Varargs Judiciously
33 Effective Java! Consider Typesafe Heterogenous Containers
34 Effective Java! Use Enums Instead of int Constants
35 Effective Java! Use Instance Fields Instead of Ordinals
36 Effective Java! Use EnumSet Instead of Bit Fields
37 Effective Java! Use EnumMap instead of Ordinal Indexing
38 Effective Java! Emulate Extensible Enums With Interfaces.
39 Effective Java! Prefer Annotations to Naming Patterns
40 Effective Java! Consistently Use the Override Annotation
41 Effective Java! Use Marker Interfaces to Define Types
42 Effective Java! Prefer Lambdas to Anonymous Classes
43 Effective Java! Prefer Method References to Lambdas
44 Effective Java! Favor the Use of Standard Functional Interfaces
45 Effective Java! Use Stream Judiciously
46 Effective Java! Prefer Side-Effect-Free Functions in Streams
47 Effective Java! Prefer Collection To Stream as a Return Type
48 Effective Java! Use Caution When Making Streams Parallel
49 Effective Java! Check Parameters for Validity
50 Effective Java! Make Defensive Copies When Necessary
51 Effective Java! Design Method Signatures Carefully
52 Effective Java! Use Overloading Judiciously
53 Effective Java! Use Varargs Judiciously
54 Effective Java! Return Empty Collections or Arrays, Not Nulls
55 Effective Java! Return Optionals Judiciously
56 Effective Java: Write Doc Comments For All Exposed APIs
57 Effective Java: Minimize The Scope of Local Variables
58 Effective Java: Prefer for-each loops to traditional for loops
59 Effective Java: Know and Use the Libraries
60 Effective Java: Avoid Float and Double If Exact Answers Are Required
61 Effective Java: Prefer Primitive Types to Boxed Types
62 Effective Java: Avoid Strings When Other Types Are More Appropriate
63 Effective Java: Beware the Performance of String Concatenation
64 Effective Java: Refer to Objects By Their Interfaces
65 Effective Java: Prefer Interfaces To Reflection
66 Effective Java: Use Native Methods Judiciously
67 Effective Java: Optimize Judiciously
68 Effective Java: Adhere to Generally Accepted Naming Conventions
69 Effective Java: Use Exceptions for Only Exceptional Circumstances
70 Effective Java: Use Checked Exceptions for Recoverable Conditions
71 Effective Java: Avoid Unnecessary Use of Checked Exceptions
72 Effective Java: Favor The Use of Standard Exceptions
73 Effective Java: Throw Exceptions Appropriate To The Abstraction
74 Effective Java: Document All Exceptions Thrown By Each Method
75 Effective Java: Include Failure-Capture Information in Detail Messages
76 Effective Java: Strive for Failure Atomicity
77 Effective Java: Don’t Ignore Exceptions
78 Effective Java: Synchronize Access to Shared Mutable Data
79 Effective Java: Avoid Excessive Synchronization
80 Effective Java: Prefer Executors, Tasks, and Streams to Threads
81 Effective Java: Prefer Concurrency Utilities Over wait and notify
82 Effective Java: Document Thread Safety
83 Effective Java: Use Lazy Initialization Judiciously
84 Effective Java: Don’t Depend on the Thread Scheduler
85 Effective Java: Prefer Alternatives To Java Serialization
86 Effective Java: Implement Serializable With Great Caution
87 Effective Java: Consider Using a Custom Serialized Form
88 Effective Java: Write readObject Methods Defensively
89 Effective Java: For Instance Control, Prefer Enum types to readResolve
90 Effective Java: Consider Serialization Proxies Instead of Serialized Instances

原文链接:Effective Java: Write readObject Methods Defensively

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容