提出问题

项目开发中,对对象都通用的方法要注意那些???

解决问题

覆盖equals时请遵守通用约定

先温习下枯燥的理论知识,很无聊,但很重要。

  • 自反性:对于任何非null的引用值x,x.equals(x)必须返回true.
  • 对称性:对于非空的引用值x,y,当且仅当x.equals(y)返回true时,y.equals(x)必须返回true.
  • 传递性:对于任何非null的引用值x,y,z,如果x.equals(y)=true,y.equals(z)=true,那么x.equals(z)也必须返回true。
  • 一致性:对于任何非null的引用值x,y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或一致地返回false.

对于非null的引用值x,x.equals(null)必须返回false.
高质量equals方法的诀窍

使用==操作符检查参数是否为这个对象的引用。
使用instanceof操作符检查参数是否为正确的类型。
把参数转换成正确的类型。
当编写完成了equals方法之后,应该问自己三个问题,它是否是对称的、传递的、一致的。
我用开发工具自动帮我生成equals方法:

  1. @Override
  2. public boolean equals(Object o) {
  3. //使用==操作符检查参数是否为这个对象的引用
  4. if (this == o) return true;
  5. //使用instanceof操作符检查参数是否为正确的类型
  6. if (!(o instanceof AyTest)) return false;
  7. //把参数转换成正确的类型
  8. AyTest ayTest = (AyTest) o;
  9. if (flowerNum != ayTest.flowerNum) return false;
  10. return true;
  11. }

覆盖equals时总要覆盖hashCode

覆盖equals方法,必须覆盖hashCode方法。如果不这样做,就会违反Object.hashCode的通用约定,从而也导致该类无法结合所有基于散列的结合一块正常运转,这样的结合包括HashMap、HashSet和Hashtable

  1. //这里引用课本的例子,非原创
  2. public final class PhoneNumber {
  3. private final short areaCode;
  4. private final short prefix;
  5. private final short lineNumber;
  6. public PhoneNumber(int areaCode, int prefix, int lineNumber) {
  7. rangeCheck(areaCode, 999, "area code");
  8. rangeCheck(prefix, 999, "prefix");
  9. rangeCheck(lineNumber, 9999, "line number");
  10. this.areaCode = (short)areaCode;
  11. this.prefix = (short)prefix;
  12. this.lineNumber = (short)lineNumber;
  13. }
  14. private static void rangeCheck(int arg, int max, String name) {
  15. if (arg < 0 || arg > max) {
  16. throw new IllegalArgumentException(name + ": " + arg);
  17. }
  18. }
  19. @Override
  20. public boolean equals(Object o) {
  21. if (o == this) {
  22. return true;
  23. }
  24. if (!(o instanceof PhoneNumber)) {
  25. return false;
  26. }
  27. PhoneNumber pNumber = (PhoneNumber)o;
  28. return (pNumber.lineNumber == lineNumber) && (pNumber.prefix == prefix) && (pNumber.areaCode == areaCode);
  29. }
  30. }

测试例子:

  1. public static void hashCodePhoneNumber() {
  2. Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
  3. PhoneNumber phoneNumber = new PhoneNumber(707, 867, 9876);
  4. map.put(phoneNumber, "Jenny");
  5. //这里是重点 重点 重点 一个是new 出来的 一个是 原来的phoneNumber
  6. System.out.println(map.get(new PhoneNumber(707, 867, 9876)));
  7. System.out.println(map.get(phoneNumber));
  8. }

执行结果:

  1. null
  2. Jenny

解释一下:

不去覆盖hashCode,使用map.put时,我们是把这些PhoneNumber对象放在各个不同的盒子里,而我们去map.get()的时候,只是去某一个盒子里去找,而如果我们覆盖了hashCode方法,这时,如果通过hashCode计算出来的值是相等的,就会放在同一个盒子里。这样,只要我们对象中保存的值是完全一致的,就会找到这个key所对应的value。

始终要覆盖toString

如果我们不覆盖类的toString()方法,后果可能是当我们需要去打印这个类的对象时,会有一些并非是我们想要的那种结果。现在开发工具很方便,可以使用开发工具自动帮助我们生成。

谨慎的覆盖clone

拷贝的含义是:

  • x.clone() != x
  • x.clone().getClass() == x.getClass()
  • x.clone().equals(x)

覆盖clone方法要非常小心,如果类里面含有复杂数据类型,要进行深度复制,如果类里面有final属性,则无法进行clone,因为final属性在clone时无法再进行赋值。

最好呢就是别去覆盖这个方法,需要复制的话可以使用拷贝构造器和静态拷贝工厂

考虑实现Comparable接口

  • comparaTo方法不是Object中的方法,而是Comparable接口中唯一的方法。该方法不仅可进行等同性比较,还可以进行顺序比较。

  • 接口的通用约定是按照equals方法来定义的,但有序集合使用了compareTo方法的等同性测试。

  • 如果是是一个值类,而且具有明显的内在排序关系,就因该坚决实现该接口。

  • 如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或者按照年代顺序,那就应该坚决考虑实现这个接口。

参考文章
【1】Effective Java:对于所有对象都通用的方法

【2】Java中equals和==的区别

【3】Effective Java——对所有对象通用的方法

【4】Effective Java 读书笔记之二 对于所有对象都通用的方法

【5】考虑实现Comparable接口