Java资料整理Java学习笔记技术干货

Effective java笔记(二),所有对象的通用方法

2016-09-07  本文已影响97人  Alent

Object类的所有非final方法(equals、hashCode、toString、clone、finalize)都要遵守通用约定(general contract),否则其它依赖于这些约定的类(HashMap,HashSet等)将不能正常工作。

8、覆盖equals时请遵守通用约定

无需覆盖equals的情形:

需要覆盖equals的情形:

覆盖equals时必须遵守的约定:

违反约定的例子:

在java类库中,java.sql.Timestamp继承自java.util.Date,并增加了nanoseconds域。但Timestampequals实现违反了对称性(date.equals(timestamp) != timestamp.equals(date)),如果TimestampDate对象被混合在一起使用,将引起不正确的行为。

Date的继承结构
equals实现代码:
    
    //Date中equals实现
    public boolean equals(Object obj) {
        return obj instanceof Date && getTime() == ((Date) obj).getTime();
    }

    //Timestamp中equals实现
    /* 
     * Note: This method is not symmetric with respect to the
     * equals(Object) method in the base class.
     */
    public boolean equals(java.lang.Object ts) {
      if (ts instanceof Timestamp) {
        return this.equals((Timestamp)ts);
      } else {
        return false;
      }
    }

注:在equals实现中,对于obj instanceof Date语句,若obj为null,其将返回false。因此把null传个equals方法,无需进行单独的类型检查(判断obj是否为null)。

若将上面Timestampequals代码改为如下形式:

    //Timestamp改进的equals实现
    public boolean equals(java.lang.Object ts) {
      if (ts instanceof Timestamp) {
        return this.equals((Timestamp)ts);
      } else if (ts instanceof Date){
        return ((Date)ts).equals(this);
      } else {
        return false;
      }
    }

这样确实保证了对称性,但却牺牲了Timestamp类的特征。

Timestamp是Date 类的瘦包装器 (thin wrapper),它允许 JDBC API 将该类标识为 SQL TIMESTAMP 值。它添加保存 SQL TIMESTAMP 毫微秒值和提供支持时间戳值的 JDBC 转义语法的格式化和解析操作的能力。

实现equals方法的诀窍:

注意:

9、覆盖equals时总要覆盖hashCode

每个覆盖了equals方法的类中,必须覆盖hashCode方法,否则该类无法用于基于散列表的集合(HashMap,HashSet和HashTable)

对hashCode的约定:

相等的对象必须具有相等的hashCode值;hashCode值不同,对象一定不相等,为不相等的对象产生不相等的散列码可以提高散列表的性能。散列函数应该把不相等的实例均匀分配到所有可能的散列值上。

一种计算散列码的方式:

1、保存一个非零的常数值,result = 17

2、为对象中每个域f(equals方法中涉及的域)计算int型的散列码c

3、将所有的散列码合并到result,递归调用result = result * 31 + c

例如:


    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + fa;
        result = 31 * result + fb;
        result = 31 * result + fc;
        return result;
    }

使用31的原因:

31是一个奇素数并且31可以使用移位和减法来代替乘法以提高性能,如:31 * i == (i << 5) - i。现代的JVM能够自动完成这种优化

对于不可变类,若每次计算hashCode的开销比较大,可将散列码缓存在对象内部,而不是每次请求时都重新计算散列码。如:

    
    private volatile int hashCode = 0; //volatile

    @Override
    public int hashCode() {
        if (hashCode != 0) {
            return hashCode;
        }
        int result = 17;
        result = 31 * result + fa;
        result = 31 * result + fb;
        result = 31 * result + fc;
        hashCode = result;
        return result;
    }

10、始终要覆盖toString

toString的约定,建议所有的类都实现toString方法。toString实现可以使类用起来更加舒服,有利于调试时信息的诊断。

如果对象太大或信息难以用字符串描述,应该返回一个摘要信息。在文档中标明toString的返回格式。

11、谨慎的覆盖clone

一个类实现了Cloneable接口,表名这个类允许被克隆。Cloneable接口是个空接口,仅仅用来标识这个类可以被复制,具体实现将由JVM调用Object中的原生方法clone()完成。所以一个类要能够复制必须实现Cloneable接口并重写clone()方法。

关于clone方法的实现参见博客java中clone方法的实现

12、考虑实现Comparable接口

实现了Comparable接口的类,表明它的实例具有内在的排序关系,可以使用Arrays.sort(comp)方法对其数组进行排序。一旦类实现了Comparable接口,它就可以与许多泛型算法及依赖于该接口的集合实现进行协作。若编写的类是有非常明显的内在排序关系,那么就应该实现Comparable接口。实现Comparable接口必须重写compareTo方法。

依赖于比较关系(compareTo方法)的类包括有序集合类TreeSet和TreeMap,以及工具类Collection和Arrays,它们内部包含有搜索和排序算法。

compareTo方法的约定:

上一篇 下一篇

猜你喜欢

热点阅读