// constructor method
Date date = new Date();
// static factory
Calendar calendar = Calendar.getInstance();
Integer number = Integer.valueOf("3");
优点:
缺点:
// many parameters in constructor
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
// javaBeans pattern
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setXXX...
// builder pattern
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
两种常见方法来构造单例,都采用了构造方法私有和导出公共静态成员。
// Singleton with public final field
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public void leaveTheBuilding() { ... }
}
// Singleton with static factory
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() { ... }
}
为避免序列化和反序列化时创建新实例,需要:
// readResolve method to preserve singleton property
private Object readResolve() {
// Return the one true Elvis and let the garbage collector
// take care of the Elvis impersonator.
return INSTANCE;
}
// Noninstantiable utility class
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
}
一旦构造方法被意外调用,直接抛异常。
// Dependency injection provides flexibility and testability
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
将资源或工厂传递给构造方法。依赖注入极大地增强类的灵活性、可重用性和可测试性。
String s = new String("bikini"); // DON'T DO THIS!
String s = "bikini"; // DO THIS
优先使用基本类型而不是包装类型,例如使用 long 而不是 Long 。
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
不要把 Java 中的 Finalizer 或 Cleaner 机制当成的 C ++析构函数的等价物。
// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
Object 的规范如下: equals 方法实现了一个等价关系
(equivalence relation)。它有以下这些属性:
综合起来,以下是编写高质量 equals 方法的配方(recipe):
一个例子:
public final class PhoneNumber {
private final short areaCode, prefix, lineNum;
public PhoneNumber(int areaCode, int prefix, int lineNum) {
this.areaCode = rangeCheck(areaCode, 999, "area code");
this.prefix = rangeCheck(prefix, 999, "prefix");
this.lineNum = rangeCheck(lineNum, 9999, "line num");
}
private static short rangeCheck(int val, int max, String arg) {
if (val < 0 || val > max)
throw new IllegalArgumentException(arg + ": " + val);
return (short) val;
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
}
}
// default
PhoneNumber@163b91
有时重写 toString 方法是必要的,方便调试。
clone 方法的通用规范很薄弱:
创建并返回此对象的副本。 “复制(copy)”的确切含义可能取决于对象的类。 一般是,对于任何对象 x,表
达式 x.clone() != x 返回 true,并且 x.clone().getClass() == x.getClass() 也返回 true,但它们不是
绝对的要求,但通常情况下, x.clone().equals(x) 返回 true,当然这个要求也不是绝对的。
通常,复制功能最好由构造方法或工厂提供。
new TreeSet<>(s);
这个规则的一个例外是数组,它最好用 clone 方法复制。
public interface Comparable<T> {
int compareTo(T t);
}
一个例子:
// BROKEN difference-based comparator - violates transitivity!
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return o1.hashCode() - o2.hashCode();
}
};
// Comparator based on static compare method
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return Integer.compare(o1.hashCode(), o2.hashCode());
}
};
比较 compareTo 方法的实现中的字段值时,请避免使用"<"和">"运算
符。 推荐使用包装类中的静态 compare 方法或 Comparator 接口中的构建方法。
尽可能地减少程序元素的可访问性(在合理范围内)。除了作为常量的公共静态 final 属性之外,公共类不应该有公共属性。 确保 public static final 属性引用的对象是不可变的。
// Degenerate classes like this should not be public!
class Point {
public double x;
public double y;
}
// Encapsulation of data by accessor methods and mutators
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
}
不可变类既是它的实例不能被修改的类,例如 String,BigInteger 类。
五条规则:
当满足 “B is-an A”关系时使用继承。
否则,考虑使用组合(既包装类),B 通常包含一个 A 的私有实例,并暴露一个不同的 API:A 不是 B 的重要部分 ,只是其实现细节。
子类可能会依赖于父类的实现细节,并且如果父类的实现发生改变,子类可能会损坏。
Java 有两种机制来定义允许多个实现的类型:接口和抽象类。
抽象类定义的类型,类必须是抽象类的子类。但 Java 只允许单一继承,所以限制很大;而一个类却可以实现多个接口,灵活性非常高。
如果需要导出一个重要的接口,强烈考虑提供一个骨架的实现类(采用抽象类)。
在 Java 8 中,添加了默认方法(default method),目的
是允许将方法添加到现有的接口。
不应该使用常量接口来导出常量,应使用不可实例化的类来导出常量。
标签类例子:
// Tagged class - vastly inferior to a class hierarchy!
class Figure {
enum Shape {
RECTANGLE, CIRCLE
};
// Tag field - the shape of this figure
final Shape shape;
// These fields are used only if shape is RECTANGLE
double length;
double width;
// This field is used only if shape is CIRCLE
double radius;
// Constructor for circle
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// Constructor for rectangle
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch (shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError(shape);
}
}
}
类层次例子:
// Class hierarchy replacement for a tagged class
abstract class Figure {
abstract double area();
}
class Circle extends Figure {
final double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * (radius * radius);
}
}
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
double area() {
return length * width;
}
}
有四种嵌套类:静态成员类,非静态成员类,匿名类和局部类。 除第一种以外,剩下三种都被称为内部类(inner class)。
静态成员类和非静态成员类之间的唯一区别是静态成员类在其声明中具有 static 修饰符。
如果你声明了一个不需要访问宿主实例的成员类,把 static 修饰符放在它的声明中,使它成为一个静态成员类,而不是非静态的成员类。
匿名类是创建小函数对象和处理对象的首选方法,但 lambda 表达式现在是首选。
永远不要将多个顶级类或接口放在一个源文件中。
一个类或接口,它的声明有一个或多个类型参数(type parameters ),被称之为泛型类或泛型接口。例如 List<E> 。
不推荐使用 List ,而是应该 显示指定 E ,例如 List<String> 。
| 术语 | 中文含义 | 举例 |
|---|---|---|
| Parameterized type | 参数化类型 | List |
| Actual type parameter | 实际类型参数 | String |
| Generic type | 泛型类型 | List |
| Formal type parameter | 形式类型参数 | E |
| Unbounded wildcard type | 无限制通配符类型 | List<?> |
| Raw type | 原始类型 | List |
| Bounded type parameter | 限制类型参数 | |
| Recursive type bound | 递归类型限制 | <T extends Comparable> |
| Bounded wildcard type | 限制通配符类型 | List<? extends Number> |
| Generic method | 泛型方法 | static List asList(E[] a) |
| Type token | 类型令牌 | String.class |
尽可能消除每一个未经检查的警告。
如果不能消除警告,但坚信引发警告的代码是类型安全的,那么可用 @SuppressWarnings(“unchecked”) 注解来抑制警告。
数组是协变和具体化的; 泛型是不变的,类型擦除的。
推荐使用 List 而不是 Array。
泛型类型比需要在客户端代码中强制转换的类型更安全,更易于使用。
// The element type in s1 may be different from s2
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
// Generic method
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
// Using a recursive type bound to express mutual comparability
public static <E extends Comparable<E>> E max(Collection<E> c);
助记符来帮助记住使用哪种通配符类型:
PECS 代表: producer-extends,consumer-super。
如果一个参数化类型代表一个 T 生产者,使用 <? extends T> ;如果它代表 T 消费者,则使用 <? super T> 。
在 Stack 示例中, pushAll 方法的 src 参数生成栈使用的 E 实例,因此 src 的合适类型为 Iterable<? extends E> ;
popAll 方法的 dst 参数消费 Stack 中的 E 实例,因此 dst 的合适类型是 C ollection <? super E>。
于是,可以改进上面的例子:
public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
// List as a typesafe alternative to a generic varargs parameter
static <T> List<T> flatten(List<List<? extends T>> lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
Class<T> 可以是 Class<String> 或 Class<Integer>
// The int enum pattern - severely deficient!
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;。
// Use this
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }
// Enum type with constant-specific class bodies and data
public enum Operation {
PLUS("+") {
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
public double apply(double x, double y) {
return x - y;
}
},
TIMES("*") {
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
public abstract double apply(double x, double y);
}
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y);
}
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8), NONET(9),
DECTET(10), TRIPLE_QUARTET(12);
private final int numberOfMusicians;
Ensemble(int size) {
this.numberOfMusicians = size;
}
public int numberOfMusicians() {
return numberOfMusicians;
}
}
// EnumSet - a modern replacement for bit fields
public class Text {
public enum Style {
BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
}
// Any Set could be passed in, but EnumSet is clearly best
public void applyStyles(Set<Style> styles) { ... }
}
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
使用序数来索引数组很不合适:改用 EnumMap。
// Using an EnumMap to associate data with an enum
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle =new EnumMap<>(Plant.LifeCycle.class);
for(Plant.LifeCycle lc:Plant.LifeCycle.values())
plantsByLifeCycle.put(lc,new HashSet<>());
for(Plant p:garden)
plantsByLifeCycle.get(p.lifeCycle).add(p);
System.out.println(plantsByLifeCycle);
// Emulated extensible enum using an interface
public interface Operation {
double apply(double x, double y);
}
public enum BasicOperation implements Operation {
PLUS("+") {
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
public double apply(double x, double y) {
return x - y;
}
};
private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
}
虽然不能编写可扩展的枚举类型,但可以编写一个接口来配合实现接口的基本的枚举类型,来对它进行模拟。
// naming pattern
tsetSafetyOverride
// anotation
@Test
在你认为要重写父类声明的每个方法声明上使用 Override 注解。
// Lambda expression as function object (replaces anonymous class)
Collections.sort(words,
(s1, s2) -> Integer.compare(s1.length(), s2.length()));
| 接口 | 方法 | 示例 |
|---|---|---|
| UnaryOperator | T apply(T t) | String::toLowerCase |
| BinaryOperator | T apply(T t1, T t2) | BigInteger::add |
| Predicate | boolean test(T t) | Collection::isEmpty |
| Function<T,R> | R apply(T t) | Arrays::asList |
| Supplier | T get() | Instant::now |
| Consumer | void accept(T t) | System.out::println |
在 Java 8 中添加了 Stream API,以简化顺序或并行执行批量操作的任务。 该 API 提供了两个关键的抽象:
最重要的收集器工厂是 toList , toSet , toMap , groupingBy 和 join 。
Java 于 1996 年发布时,内置了对线程的支持,包括同步和 wait / notify 机制。
Java 5 引入了 java.util.concurrent 类库,带有并发集合和执行器框架。
Java 7 引入了 fork-join 包,这是一个用于并行分解的高性能框架。
Java 8 引入了流,可以通过对 parallel 方法的单个调用来并行化。
总之,甚至不要尝试并行化流管道,除非你有充分的理由相信它将保持计算的正确性并提高其速度。
在 Java 7 中添加的 Objects.requireNonNull 方法可以执行 null 检测。
Assert 断言。
总之,每次编写方法或构造方法时,都应该考虑对其参数存在哪些限制,并在方法体开头使用显式检查来强制执行这些限制。
有问题的例子,因为 Date 类型 可变。
// Broken "immutable" time period class
public final 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;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
}
// Attack the internals of a Period instance
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // Modifies internals of p!
必须将每个可变参数的防御性拷贝应用到构造方法中。
// Repaired constructor - 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);
}
public enum TemperatureScale { FAHRENHEIT, CELSIUS }
一个安全和保守的策略是永远不要导出两个具有相同参数数量的重载。
对于构造方法,无法使用不同的名称:类的多个构造函数总是被重载。
可变参数机制首先创建一个数组,其大小是在调用位置传递的参数数量,然后将参数值放入数组中,最后将数组传递给方法。
在性能关键的情况下使用可变参数时要小心。每次调用可变参数方法都会导致数组分配和初始化。
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) { }
/**
* @return a list containing all of the cheeses in the shop, or null if no
* cheeses are available for purchase.
*/
public List<Cheese> getCheeses() {
return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock);
}
// The right way to return a possibly empty collection
public List<Cheese> getCheeses() {
return new ArrayList<>(cheesesInStock);
}
数组的情况与集合的情况相同。 永远不要返回 null,而是返回长度为零的数组。
在 Java 8 之前,编写在特定情况下无法返回任何值的方法时,可以采用两种方法。
在 Java 8 中,有第三种方法来编写可能无法返回任何值的方法。
Optional 类表示一个不可变的容器,它可以包含一个非 null 的 T 引用,也可以什么都不包含。
// Returns maximum value in collection as an Optional<E>
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
if (c.isEmpty())
return Optional.empty();
E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return Optional.of(result);
}
总之,如果发现自己编写的方法不能总是返回值,并且认为该方法的用户在每次调用时考虑这种可能性很重要,那么或许应该返回一个 Optional 的方法。
有三种常见的情况是你不能分别使用 for-each 循环:
从 Java 7 开始,不应该再使用 Random。
在大多数情况下,选择的随机数生成器现在是 ThreadLocalRandom 。
对于 fork 连接池和并行流,使用 SplittableRandom 。
每个程序员都应该熟悉 java.lang、java.util 和 java.io 的基础知识及其子包。
对于任何需要精确答案的计算,不要使用 float 或 double 类型。
不要使用字符串连接操作符合并多个字符串,除非性能无关紧要。否则使用 StringBuilder 的 append 方法。
// Good - uses interface as type
Set<Son> sonSet = new LinkedHashSet<>();
// Bad - uses class as type!
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
如果存在合适的接口类型,那么应该使用接口类型声明参数、返回值、变量和字段。
如果没有合适的接口,那么用类引用对象是完全合适的,推荐使用类层次结构中提供所需功能的最底层的类。
核心反射机制 java.lang.reflect 提供对任意类的编程访问。给定一个 Class 对象,你可以获得 Constructor、Method 和 Field 实例。
如果必须在编译时处理未知的类,则应该尽可能只使用反射实例化对象,并使用在编译时已知的接口或超类访问对象。
Java 本地接口(JNI)允许 Java 程序调用本地方法,主要有 3 种用途:
不要努力写快的程序,要努力写好程序;速度自然会提高。
命名约定分为两类:排版和语法。
设计良好的 API 不应该强迫它的客户端为了正常的控制流程而使用异常。
Java 程序设计语言提供了三种 throwable:
如果期望调用者能够合理的恢复程序运行,就应该使用受检异常。
用运行时异常来表明编程错误。
你实现的所有非受检的 throwable 都应该是 RuntimeExceptiond 子类。
| 异常 | 使用场合 |
|---|---|
| IllegalArgumentException | 非 null 的参数值不正确 |
| IllegalStateException | 不适合方法调用的对象状态 |
| NullPointerException | 在禁止使用 null 的情况下参数值为 null |
| IndexOutOfBoundsExecption | 下标参数值越界 |
| ConcurrentModificationException | 禁止并发修改时,检测到对象的并发修改 |
| UnsupportedOperationException | 对象不支持用户请求的方法 |
利用 Javadoc 的 @throws 标签, 准确地记录下抛出每个异常的条件。
为了捕获失败,异常的细节信息应该包含 对该异常有贡献 的所有参数和字段的值。
失败的方法调用应该使对象保持在被调用之前的状态。
获取失败原子性的三种方法:
try-catch 语句块中不要忽略对异常的处理。
关键字 synchronized 可以保证在同一时刻,只有一个线程可以执行某一个方法,或者某一个代码块。
许多程序员把同步的概念仅仅理解为一种互斥( mutual exclusion )的方式,即,当一个对象被一个线程修改的时候,可以阻止
另一个线程观察到对象内部不一致的状态。
// Lock-free synchronization with java.util.concurrent.atomic
private static final Atomiclong nextSerialNum = new Atomiclong();
public static long generateSerialNumber() {
return nextSerialNum.getAndIncrement();
}
当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。
CopyOnWriteArrayList 是 ArrayList 的一种变体,它通过重新拷贝整个底层数组,在这里实现所有的写操作。
由于内部数组永远不改动,因此迭代不需要锁定,速度也非常快。
// Thread-safe observable set with CopyOnWriteArrayList
private final List<SetObserver<E>> observers = new CopyOnWriteArrayList<>();
public void addObserver(SetObserver<E> observer) {
observers.add(observer);
}
public Boolean removeObserver(SetObserver<E> observer) {
return observers.remove(observer);
}
private void notifyElementAdded(E element) {
for (SetObserver<E> observer : observers)
observer.added(this, element);
}
通常来说,应该在同步区域内做尽可能少的工作:
获得锁,检查共享数据,根据需要转换数据,然后释放锁。
ExecutorService exec = Executors.newSingleThreadExecutor();
exec.execute(runnable);
exec.shutdown();
在 Java 7 中, Executor 框架被扩展为支持 fork-join 任务,这些任务是通过一种称作 fork-join 池的特殊 executor 服务运行的。
java.util.concurrent 中更高级的工具分成三类:
没有理由在新代码中使用 wait 方法和 notify 方法,即使有,也是极少的。
如果一个字段只在类的一小部分实例上访问,并且初始化该字段的代价很高,那么延
迟初始化可能是值得的。
// Lazy initialization of instance field - synchronized accessor
private FieldType field;
private synchronized FieldType getField() {
if (field == null)
field = computeFieldValue();
return field;
}
// Lazy initialization holder class idiom for static fields
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() {
return FieldHolder.field;
}
// Double-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)
synchronized (this) {
if (field == null) // Second check (with locking)
field = result = computeFieldValue();
}
}
return result;
}
// 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;
}
因为单检查没有 第二重同步检查(synchronized),可能会被多线程并发初始化。
不要依赖线程调度器来判断程序的正确性。生成的程序既不健壮也不可移植。
只有在合理描述对象的逻辑状态时,才使用默认的序列化形式;否则,设计一个适合描述对象的自定义序列化形式。
readResolve 可以保证唯一单例属性:
// readResolve for instance control - you can do better!
private Object readResolve() {
// Return the one true Elvis and let the garbage collector
// take care of the Elvis impersonator.
return INSTANCE;
}
// 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));
}
}
// 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
}
将下面的 writeReplace 方法添加到外围类中。
// writeReplace method for the serialization proxy pattern
private Object writeReplace() {
return new SerializationProxy(this);
}
在外围类中添加如下 readObject 方法, 防止伪造违反该类约束条件的示例。
// readObject method for the serialization proxy pattern
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
