1.考虑使用静态方法代替构造方法

对于类而言,最常用的获取其实例的方法就是通过构造方法,还用一种方法,就是提供公有的静态工厂方法返回此类的实例。

静态工厂方法的优势:

1.静态工厂方法有名称,可以更确切的描述被返回的对象。并且一个类只有一个给定签名的构造方法,虽然程序员们可以通过重载构造方法的方式来实现,但是这种方式并不优雅。
2.不必在每次调用都返回一个新对象,工厂方法可以重复利用实例进行实例控制,提升性能
3.可以返回原类型的子类型对象,适用于基于接口的框架,比如Collection接口和Collections实现类,因为接口中不能出现静态工厂方法,所以静态工厂方法都放在了不可实例化的Collections中
如:

  1. List list = Collections.synchronizedList(new ArrayList())

4.返回对象的类可以根据输入的变化而变化

  1. //这样很麻烦
  2. Map<String, List<String>> m = new HashMap<String, List<String>>();
  3. //这样很简洁
  4. public static <K, V> HashMap<K, V> newInstance() {
  5. return new HashMap<K, V>();
  6. }

5.返回对象不需要再写这个方法的时候就存在,服务提供者框架的基础,参考JDBC的实现,使服务提供者和服务使用者解耦

缺点:

1.类如果不含有公有的或者受保护的构造方法,就不能被继承。如果使用静态工厂方法获得实例,而不提供公有的构造方法,把构造方法写为私有private,那么该类就不能被继承扩展,这就是使用复合而不是继承来扩展类了。
2.静态工厂方法的第二个缺点是,程序员很难找到它们

总之,静态工厂方法和公共构造方法都有它们的用途,并且了解它们的相对优点是值得的。通常,
静态工厂更可取,因此避免在没有考虑静态工厂的情况下直接选择使用公共构造方法

2.当构造方法参数过多时使用 builder 模式

比如要创建这么一个对象:

创建对象的方式:
1.可伸缩构造方法,但是参数过多会变得难以阅读和维护,读者不知道这些值是什么意思,并且必须仔细地去数参数才能找到答案。一长串相同类型的参数可能会导致一些细微的 bug。如果客户端不小心写反了两个这样的参数,编译器并不会报错,但是程序在运行时会出现错误行为。

  1. // Telescoping constructor pattern - does not scale well!
  2. public class NutritionFacts {
  3. private final int servingSize; // (mL) required
  4. private final int servings; // (per container) required
  5. private final int calories; // (per serving) optional
  6. private final int fat; // (g/serving) optional
  7. private final int sodium; // (mg/serving) optional
  8. private final int carbohydrate; // (g/serving) optional
  9. public NutritionFacts(int servingSize, int servings) {
  10. this(servingSize, servings, 0);
  11. }
  12. public NutritionFacts(int servingSize, int servings,
  13. int calories) {
  14. this(servingSize, servings, calories, 0);
  15. }
  16. public NutritionFacts(int servingSize, int servings,
  17. int calories, int fat) {
  18. this(servingSize, servings, calories, fat, 0);
  19. }
  20. public NutritionFacts(int servingSize, int servings,
  21. int calories, int fat, int sodium) {
  22. this(servingSize, servings, calories, fat, sodium, 0);
  23. }
  24. public NutritionFacts(int servingSize, int servings,
  25. int calories, int fat, int sodium, int carbohydrate) {
  26. this.servingSize = servingSize;
  27. this.servings = servings;
  28. this.calories = calories;
  29. this.fat = fat;
  30. this.sodium = sodium;
  31. this.carbohydrate = carbohydrate;
  32. }
  33. }

2.通过java bean模式,在构造过程中 JavaBean 可能处于不一致的状态

  1. public class NutritionFacts {
  2. // Parameters initialized to default values (if any)
  3. private int servingSize = -1; // Required; no default value
  4. private int servings = -1; // Required; no default value
  5. private int calories = 0;
  6. private int fat = 0;
  7. private int sodium = 0;
  8. private int carbohydrate = 0;
  9. public NutritionFacts() { }
  10. // Setters
  11. public void setServingSize(int val) { servingSize = val; }
  12. public void setServings(int val) { servings = val; }
  13. public void setCalories(int val) { calories = val; }
  14. public void setFat(int val) { fat = val; }
  15. public void setSodium(int val) { sodium = val; }
  16. public void setCarbohydrate(int val) { carbohydrate = val; }
  17. }
  18. NutritionFacts cocaCola = new NutritionFacts();
  19. cocaCola.setServingSize(240);
  20. cocaCola.setServings(8);
  21. cocaCola.setCalories(100);
  22. cocaCola.setSodium(35);
  23. cocaCola.setCarbohydrate(27);

3.通过建造者模式,即能保证像重叠构造器的安全性,也能保证像 JavaBeans 模式那么好的可读性

  1. public class NutritionFacts {
  2. private final int servingSize;
  3. private final int servings;
  4. private final int calories;
  5. private final int fat;
  6. private final int sodium;
  7. private final int carbohydrate;
  8. public static class Builder {
  9. // Required parameters
  10. private final int servingSize;
  11. private final int servings;
  12. // Optional parameters - initialized to default values
  13. private int calories = 0;
  14. private int fat = 0;
  15. private int sodium = 0;
  16. private int carbohydrate = 0;
  17. public Builder(int servingSize, int servings) {
  18. this.servingSize = servingSize;
  19. this.servings = servings;
  20. }
  21. public Builder calories(int val) {
  22. calories = val;
  23. return this;
  24. }
  25. public Builder fat(int val) {
  26. fat = val;
  27. return this;
  28. }
  29. public Builder sodium(int val) {
  30. sodium = val;
  31. return this;
  32. }
  33. public Builder carbohydrate(int val) {
  34. carbohydrate = val;
  35. return this;
  36. }
  37. public NutritionFacts build() {
  38. return new NutritionFacts(this);
  39. }
  40. }
  41. private NutritionFacts(Builder builder) {
  42. servingSize = builder.servingSize;
  43. servings = builder.servings;
  44. calories = builder.calories;
  45. fat = builder.fat;
  46. sodium = builder.sodium;
  47. carbohydrate = builder.carbohydrate;
  48. }
  49. }
  50. //使用
  51. NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
  52. .calories(100).sodium(35).carbohydrate(27).build();

3.私有构造方法或者枚举类实现单例

Singleton(单例)指仅仅被实例化一次的类. 通常用来代表那些本质上唯一的系统组件

单例的实现: 私有构造方法, 类中保留一个字段实例(static, final), 用public直接公开字段或者用一个public static的getInstance()方法返回该字段(懒汉,饿汉)

java1.5之后可以用枚举来实现单例,这样做的好处是可以防止反射来多次实例化

4.使用私有构造器执行非实例化

只包含静态方法和静态域的类名声不太好, 因为有些人会滥用它们来编写过程化的程序. 尽管如此, 它们确实也有特有的用处,比如: java.lang.Math, java.util.Arrays把基本类型的值或数组类型上的相关方法组织起来; java.util.Collections把实现特定接口的对象上的静态方法组织起来; 还可以利用这种类把final类上的方法组织起来, 以取代扩展该类的做法。
这种工具类(utility class)不希望被实例化, 然而在缺少显式构造器的情况下, 系统会提供默认构造器, 可能会造成这些类被无意识地实例化。通过做成抽象类来强制该类不可被实例化, 这是行不通的, 因为可能会造成”这个类是用来被继承的”的误解, 而继承它的子类又可以被实例化。所以只要让这个类包含一个私有的构造器, 它就不能被实例化了. 进一步地, 可以在这个私有构造器中抛出异常。
这种做法还会导致这个类不能被子类化, 因为子类构造器必须显式或隐式地调用super构造器. 在这种情况下, 子类就没有可访问的超类构造器可调用了。
总结一下就是,工具类可以考虑私有化构造器,不让其实例化

5.依赖注入优于硬连接资源(hardwiring resources)

就是让类所依赖的对象作为参数让使用者调用的时候注入

让常变化的东西由使用者决定,可以结合builder和工厂模式

什么是硬连接资源,我们可以理解为,一个类,依赖其它基础类,但是过于理想化,指定了它只连接一个基础类。例如我们有一个工具类:拼音检查器,它可以检查拼音是否正确,那么我们的字典里是有所有拼音的,可以给我们提供一个基础的检查数据,当我们用硬连接资源的方式去思考时,这个拼音检查器的基础数据只由《新华字典》提供。是不是感觉这是不合理的,因为每种语言都有自己的字典,数据只由《新华字典》来提供的话,感觉是不灵活,并且单一的。

哪里用
当一个类依赖一个或其他多个底部资源类的时候,我们可以考虑使用依赖注入优先于硬连接资源。

怎么实现

  • 使用静态方法来替代硬连接资源,这种方式是不灵活,并且不能够测试的。代码实现如下:

    1. /**
    2. * 通过静态方法来替代硬资源连接
    3. *
    4. * @author gongguowei01@gmail.com
    5. * @since 2020-01-12
    6. */
    7. public class SpellChecker {
    8. //仅提供了一个资源
    9. private static final Lexicon dictionary = "依赖的资源";
    10. //私有构造方法,非实例化
    11. private SpellChecker() {
    12. }
    13. //拼音检查器提供给外部类调用的方法
    14. public static boolean isValid(String word) {
    15. return true;
    16. }
    17. }
  • 使用单例来替代硬连接资源,同样也是不灵活的 ```java /**

    • 使用单例来替代硬连接资源 *
    • @author gongguowei01@gmail.com
    • @since 2020-01-12 */ public class SpellChecker02 { private final Lexicon dictionary = “依赖的资源”;

      public static NSTANCE = new SpellChecker02();

      private SpellChecker02() {

      }

      //拼音检查器提供给外部类调用的方法 public static boolean isValid(String word) {

      1. return true;

      }

}

  1. - 使用依赖注入替代硬连接资源,依赖注入提供了灵活性和可测试性。虽然下方的实例仍是一个基础资源,但是它由外部类提供,这样保证了我们的SepllChecker提供的功能不变,但是数据却是灵活多变的。
  2. ```java
  3. /**
  4. * 使用依赖注入替代硬资源连接
  5. *
  6. * @author gongguowei01@gmail.com
  7. * @since 2020-01-12
  8. */
  9. public class SpellChecker03 {
  10. private final Lexicon dictionary;
  11. //需要检查什么资源,由外部类提供基础的数据
  12. public SpellChecker03(Lexicon dictionary) {
  13. this.dictionary = Objects.requireNonNull(dictionary);
  14. }
  15. public boolean isValid(String word) { ... }
  16. }

不要用单例静态方法类来实现依赖一个或多个底层资源的类,且该资源的行为会影响到该类的行为,需要使用一个或多个底部资源类的一个类,静态方法、单例来实现是不适合的,因为它们不够灵活,也不能够测试。使用静态方法,我们如果希望它能够使用一个以上的底部资源类,我们可以通过使 dictionary 属性设置为非 final,并添加一个方法来更改现有拼写检查器中的字典,从而让SpellChecker 支持多个字典,但这感觉是笨拙的。使用依赖注入来实现硬资源连接,也有一定的弊端,一般大型的项目,可能有数千个依赖项,这让我们的项目变得混乱,不过我们可以使用Spring框架来消除这些混乱。依赖注入,它极大地提升了类的灵活性、可重用性和可测试性。

6.避免创建不必要的对象

比如:String s = “bikini”; 不要 String s = new String(“bikini”);

  1. static boolean isRomanNumeral(String s) {
  2. return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
  3. }
  4. //可以考虑提出常亮字符串
  5. public class RomanNumerals {
  6. private static final Pattern ROMAN = Pattern.compile( "^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
  7. static boolean isRomanNumeral(String s) {
  8. return ROMAN.matcher(s).matches();
  9. }
  10. }

7.消除过期的对象引用

过期对象的引用:指的是永远不会再被解除的引用。为什么会出现这样的情况,这是因为我们的栈在显示增长,然后再收缩,那么从栈中弹出来的对象将不会被当做垃圾回收,即使使用的栈的程序不再引用这些对象,它们也不会被回收。因为,栈内部维护着对这些对象的过期引用。

没有消除过期对象实例,代码如下:

  1. /**
  2. * @author gongguowei01@gmail.com
  3. * @since 2020-01-19
  4. */
  5. public class Stack {
  6. public Object[] elements;
  7. public int size = 0;
  8. private static final int DEFAULT_VALUE = 16;
  9. public Stack() {
  10. elements = new Object[DEFAULT_VALUE];
  11. }
  12. public void push(Object e) {
  13. ensureCapacity();
  14. elements[size++] = e;
  15. }
  16. public Object pop() {
  17. if (0 == size) {
  18. throw new EmptyStackException();
  19. }
  20. return elements[--size];
  21. }
  22. public void ensureCapacity() {
  23. if (elements.length == size) {
  24. elements = Arrays.copyOf(elements, 2 * size + 1);
  25. }
  26. }
  27. }

上方代码测试实例如下:

  1. /**
  2. * @author gongguowei01@gmail.com
  3. * @since 2020-01-19
  4. */
  5. public class Test {
  6. public static void main(String[] args) {
  7. Stack stack = new Stack();
  8. stack.push("1");
  9. stack.push("2");
  10. stack.pop();
  11. stack.push("3");
  12. }
  13. }

运行结果如下:
image.png
当我们调用pop()方法时,size变为1,但是elements数组中还存在过期对象“1”的引用。
消除过期对象的引用,代码实例如下:

  1. /**
  2. * @author gongguowei01@gmail.com
  3. * @since 2020-01-19
  4. */
  5. public class Stack02 {
  6. public Object[] elements;
  7. public int size = 0;
  8. private static final int DEFAULT_VALUE = 16;
  9. public Stack02() {
  10. elements = new Object[DEFAULT_VALUE];
  11. }
  12. public void push(Object e) {
  13. ensureCapacity();
  14. elements[size++] = e;
  15. }
  16. public Object pop() {
  17. if (0 == size) {
  18. throw new EmptyStackException();
  19. }
  20. Object result = elements[--size];
  21. //消除过期对象引用
  22. elements[size] = null;
  23. return result;
  24. }
  25. public void ensureCapacity() {
  26. if (elements.length == size) {
  27. elements = Arrays.copyOf(elements, 2 * size + 1);
  28. }
  29. }
  30. }

哪里用
在有可能出现内存泄露的场景:
1.缓存
2.自己管理内存的场景
3.监听器和其他回调
总结
不消除过期对象的引用,将会引起内存泄露,内存泄漏通常不会表现为明显的故障,所以它们可能会在系统中保持多年。 通常仅在仔细的代码检查或借助堆分析器(heap profiler)的调试工具才会被发现。 因此,学习如何预见这些问题,并防止这些问题发生,是非常值得的

8.避免使用 Finalizer 和 Cleaner 机制

终结方法(finalizer)和清除方法(cleaner)都是不可预测的,两者都有非常严重的性能损失
终结方法还有个严重的安全问题:终结方法攻击
java9中清除方法代替了终结方法

合理用途:
当资源所有者忘记调用它的close方法时,终结方法和清洁方法可以充当安全网
与对象的本地对等体一起使用

9.使用 try-with-resources 语句替代 try-finally语句

单个资源需要关闭时

  1. // try-finally - No longer the best way to close resources!
  2. static String firstLineOfFile(String path) throws IOException {
  3. BufferedReader br = new BufferedReader(new FileReader(path));
  4. try {
  5. return br.readLine();
  6. } finally {
  7. br.close();
  8. }
  9. }
  10. //可改为:
  11. // try-with-resources - the the best way to close resources!
  12. static String firstLineOfFile(String path) throws IOException {
  13. try (BufferedReader br = new BufferedReader( new FileReader(path))) {
  14. return br.readLine();
  15. }
  16. }

多个资源时显得混乱:

  1. // try-finally is ugly when used with more than one resource!
  2. static void copy(String src, String dst) throws IOException {
  3. InputStream in = new FileInputStream(src);
  4. try {OutputStream out = new FileOutputStream(dst);
  5. try {byte[] buf = new byte[BUFFER_SIZE];
  6. int n;
  7. while ((n = in.read(buf)) >= 0)
  8. out.write(buf, 0, n); }
  9. finally {
  10. out.close();
  11. } }
  12. finally {
  13. in.close();
  14. }
  15. }
  16. //第二个异常完全冲掉了第一个异常,导致在异常堆栈跟踪中没有第一个异常的记录,
  17. //可改为:
  18. // try-with-resources on multiple resources - short and sweet
  19. static void copy(String src, String dst) throws IOException {
  20. try (InputStream in = new FileInputStream(src);
  21. OutputStream out = new FileOutputStream(dst)) {
  22. byte[] buf = new byte[BUFFER_SIZE];
  23. int n;
  24. while ((n = in.read(buf)) >= 0)
  25. out.write(buf, 0, n);
  26. }
  27. }
  28. //更清晰

可以在 try-with-resources 语句中添加 catch 子句,就像在常规的 try-finally 语句中一样。这允许你
处理异常,而不会在另一层嵌套中污染代码。作为一个稍微有些做作的例子,这里有一个版本的
firstLineOfFile 方法,它不会抛出异常,但是如果它不能打开或读取文件,则返回默认值:

  1. // try-with-resources with a catch clause
  2. static String firstLineOfFile(String path, String defaultVal) {
  3. try (BufferedReader br = new BufferedReader(
  4. new FileReader(path))){
  5. return br.readLine();
  6. } catch (IOException e) {
  7. return defaultVal;
  8. }
  9. }

结论很明确:在处理必须关闭的资源时,使用 try-with-resources 语句替代 try-finally 语句。 生成的
代码更简洁,更清晰,并且生成的异常更有用。 try-with-resources 语句在编写必须关闭资源的代码时会
更容易,也不会出错,而使用 try-finally 语句实际上是不可能的。

10. 重写 equals 方法时遵守通用约定

不需要重写equals的情况:
1.每个类的实力都是固定唯一的,如Thread这样代表活动实体而不是值的类
2.不需要判断逻辑相等
3.父类已经重写过,子类可以继承
4.类是私有的或者包级私有的,equals方法永远不会被调用,不需要重写

重写准则:
1.自反性: 对于任何非空引用 x, x.equals(x) 必须返回 true。
2.对称性: 对于任何非空引用 x 和 y,如果且仅当 y.equals(x) 返回 true 时 x.equals(y) 必须返回 true。
3.传递性: 对于任何非空引用 x、y、z,如果 x.equals(y) 返回 true, y.equals(z) 返回 true,则 x.equals(z)必须返回 true。
4.一致性: 对于任何非空引用 x 和 y,如果在 equals 比较中使用的信息没有修改,则x.equals(y) 的多次调用必须始终返回 true 或始终返回 false。
5.对于任何非空引用 x, x.equals(null) 必须返回 false。

一些提醒:
1.重写equals同时也要重写hashcode方法
2.不要让equals方法试图太聪明。如果只是简单地测试用于相等的属性,那么要遵守equals约定并不困难。如果你在寻找相等方面过于激进,那么很容易陷入麻烦。一般来说,考虑到任何形式的别名通常是一个坏主意。例如,File类不应该试图将引用的符号链接等同于同一文件对象。幸好 File 类并没这么做。
3.在equals方法声明时,不要讲参数Object替换成其他类型
如:

  1. // Still broken, but won’t compile
  2. @Override
  3. public boolean equals(MyClass o) { }
  4. //这样编译过不了,去掉@Overrid,就不是重写,而是重载了

11. 重写 equals 方法时同时也要重写 hashcode方法

重写equals一定要重写hashcode方法,如果不这样做,在HashMap和HashSet中会不正确
Object规范:
1.对一个对象多次调用hashcode方法时,返回值必须一致
2.两个对象equals相等,hashcode必须相等
3.两个对象equals不相等,hashcode不做要求(可以相等也可以不相等)

如果HashMap put一个对象作为key,key不重写hashcode方法,get这个key可能得不到,因为hashcode变了

不要试图冲哈希码计算中排除重要的属性来提升性能
不要为hashCode返回的值提供详细的规范,因此客户端不能合理的依赖它;你可以改变它的灵活性

12.始终重写toString方法

toString的通用约定,返回的字符串应该是一个简洁但内容丰富的表示
不要仅仅反回内存地址

13.谨慎的使用clone方法

Cloneable接口的目的是作为一个mixin接口,它用来做什么的?它决定了Object的受保护的clone方法的实现行为:如果一个类实现了Cloneable接口,那么Object的clone方法将返回改对象的逐个属性的拷贝,否则会抛出CloneNotSupportedException 异常。这是一个非常反常的接口 使用,而不应该被效仿。 通常情况下,实现一个接口用来表示可以为客户做什么。但对于 Cloneable 接 口,它会修改父类上受保护方法的行为

说在前面
有些专家级程序员干脆从来不去覆盖clone方法,也从来不去调用它,除非拷贝数组。
其他方式
可以提供一个构造函数或者工厂去实现clone功能。
相比于clone,它们有如下优势:

  1. 不依赖于某一种很有风险的、语言之外的对象创建机制;
  2. 不要求遵守尚未定制好的文档规范;
  3. 不会与final域发生冲突;
  4. 不会抛出不必要的受检异常;
  5. 不需要进行类型转换;

例如,通用集合的实现都提供了一个拷贝构造函数,它的参数类型为Collection或Map。
假如要把一个HashSet拷贝成一个TreeSet:

  1. HashSet s = ...
  2. new TreeSet(s)

如果一定要覆盖clone方法,那么则需要了解以下它的注意事项了。
Clone规范

  1. x.clone() != x //true
  2. x.clone().getClass() == x.getClass() //true
  3. x.clone.equals(x) // true

行为良好的clone方法可以调用构造器来创建对象,构造之后再复制内部数据。
Clone做法

  1. 所有实现了Cloneable接口的类都应该用一个公有方法覆盖clone;
  2. 此公有方法首先要调用super.clone,然后在修正需要修正的域; ```java // 伪代码 class User implements Cloneable {

    @Override public User clone() {

    1. User user = (User)super.clone(); // 1.先调用super.clone
    2. user.set ... // 2.在修正

    }

}

  1. **Clone要点**<br />如果覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象,如果类的所有父类都遵守这条规则,那么调用super.clone最终会调用Objectclone方法,从而创建出正确类的实例。这种机制大体上类似于自动的构造器调用链。<br />**简单Clone**<br />如果类中包含的每个域是一个基本类型的值,或者包含的是一个指向不可变对象的引用,那么调用clone被返回的对象则可能正是所需要的对象,在这种情况下不需要在做进一步的处理。<br />**复杂Clone**<br />如果类中包含的域是指向一个可变对象的引用,那么就要小心的对其进行clone。<br />例如,若类中存在一个Object[]数组,则可以参考一下做法:
  2. ```java
  3. // 伪代码
  4. class Stack {
  5. private Object[] elements;
  6. private int size = 0;
  7. @Override
  8. public Stack clone() {
  9. Stack result = (Stack) super.clone();
  10. result.elements = this.elements.clone();
  11. }
  12. }

还有一种情况,若类中存在一个对象或者集合(自定义对象、List、Map等),那么光调用这些对象的clone还不够,例如编写一个散列表的clone方法,它的内部数据包含一个散列桶数组:

  1. // 伪代码
  2. class HashTable implements Cloneable {
  3. private Entry[] buckets = ...
  4. private static class Entry {
  5. final Object key;
  6. Object value;
  7. Entry next;
  8. Entry(key, value, next) ...
  9. }
  10. }

如果只调用了buckets.clone,其实克隆出来的buckets和被克隆的buckets内的entry是引用着同一对象的。
这种情况下,必须单独拷贝并组成每个桶的链表,例如:

  1. // 伪代码
  2. class HashTable implements Cloneable {
  3. private Entry[] buckets = ...
  4. private static class Entry {
  5. final Object key;
  6. Object value;
  7. Entry next;
  8. Entry(key, value, next) ...
  9. }
  10. // 提供一个深拷贝函数
  11. Entry deepCopy() {
  12. return new Entry(key, value, next == null ? null : next.deepCopy());
  13. }
  14. @Override
  15. public HashTable clone() {
  16. try ...
  17. HashTable result = (HashTable) super.clone();
  18. result.buckets = new Enrty[buckets.length];
  19. for(int i=0;i<buckets.length;i++) {
  20. if(buckets[i] != null)
  21. result.buckets[i] = buckets[i].deepCopy();
  22. }
  23. return result;
  24. catch CloneNotSupportedException e ...
  25. }
  26. }

提供一个深拷贝方法,遍历源对象的buckets,将它拷贝到新对象中。
这种做法有一个确定,如果散列桶很长,很容易导致栈溢出,因为递归的层级太多!
解决这种问题,可以采用迭代(iteration)来代替递归(recursion),修改一下deepCopy方法:

  1. Entry deepCopy() {
  2. Entry result = new Entry(key, value, next);
  3. for (Entry p = result; p.next != null; p = p.next) {
  4. p.next = new Entry(p.next.key, p.next.value, p.next.next);
  5. }
  6. return result;
  7. }

最好还做到
Object的clone方法被声明为可跑出CloneNotSupportedException异常,但是,覆盖版本的clone方法可能会忽略这个声明。公有的clone方法应该省略这个声明,因为不会跑出受检异常的方法用起来更轻松。
如果专门为了继承而设计的类覆盖类clone方法,覆盖版本的clone方法就应该模拟Object.clone的行为:

  1. 声明为protected;
  2. 抛出CloneNotSupportedException;
  3. 不实现Cloneable接口;