Effective Java Note(对于所有对象都通用的方法)

对于所有对象都通用的方法

1. 覆盖equals时的通用约定

equals所期望的结果

  • 类的每个实例本质上都是唯一的。
  • 不关心类是否提供了“逻辑相等”的测试功能。
  • 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。
  • 类时私有的或者包私有的,可以确定它的equals方法永远不会调用。

需要覆盖的时机:

父类没有实现所期望的以上的equals实现

"最多只存在一个对象"的类不需要覆盖equals,例如枚举类型

equals的等价关系

  • 自反性。任何非null引用值,x.equals(x)返回true。

    Set集合中重复添加同意一个引用值会怎样?

  • 对称性。对于任何非null的x和y,x.equals(y) 返回true,那么y.equals(x) 也返回true。

    自定义类A实现了不区分大小写的比较,new A("Aa").equals("aa")返回true,但是“aa”.equals(new A("Aa"))的返回值却由String类中的equals方法决定。

  • 传递性。对于任何非null的x,y,z,如果x.equals(y) 为true,且 y.equals(z) 也为true,那么x.equals(z) 也为true。

    存在扩展可实例化与增加主键值的时候,容易违反传递性。考虑使用抽象类并在其子类中添加属性,例如Shape。

  • 一致性。对于任何非null引用值x,y,只要x.equals(y)返回true,那么在引用对象信息没有被修改,那么每次返回的仍是一致的true。

    要保证一致性,不要使equals依赖于不可靠的的资源。例如java..net.URL的equals依赖于主机的IP,IP可能随着时间的推移而改变。

  • 对于任何非null引用值x,x.equals(null)必须返回false。

高质量equals

  1. 使用==检查是待比较参数是否是同意对象引用本身
  2. 使用instanceof检查参数是否是正确类型
  3. 把参数转换成正确的类型,使用instanceof判断
  4. 检查参数中域与该对象中的对应域相匹配。
  5. 除float 、double外的基本类型使用==比较
  6. 对象引用域可以递归调用equals
  7. 对于float,double分别使用Float.compare(),Double.compare()
  8. 数组判等使用Arrays.equals()。
  9. 对于允许null的域,尽可能避免NullPointException,可以做判空操作
  10. 对个域比较的时候,或者比较比较步骤较多的时候,比较顺序从最有可能不一致的开始

注意点

  • 覆盖equals的时候总要覆盖hashCode
  • 不要让equals过于智能。过度的寻求某种不必要的等价关系。
  • 不要将equals中参数转换成其他类型。某些情况能增加性能,但是比较复杂性会增加(不推荐)。
//本应该是:
public void equals(Object o){
  ....
}
//转换参数成其他类型:
public void equals(MyClass o){
  ....
}

//加上@Override导致变异不通过,因为父类的equals方法不存在此重写方法。
@Override
public void equals(MyClass o){
  ....
}

2.覆盖equals时总要覆盖hashCode

JavaSE6中的Object规范

  • 程序执行期间,对象的equals方法所用到的比较信息没有被修改,对同一个对象的多次调用hashCode始终如一的返回同一个整数,但是多次执行的过程中,所返回的整数可以不一致(信息修改后)。
  • 两个对象的equals返回true,那么这两个对象的hashCode返回的整数必须相等。(所以覆盖equals总要覆盖hashCode)
  • 两个对象的equals返回false,那么不要求两个对象的hashCode返回不一样的整数结果,但是在使用hash的相关集合框架中,返回不一样的结果能提高性能

HashMap,HashSet等hash集合框架,依赖元素的hashCode进行散列,因此当equals返回true的时候,hashCode返回的结果也一样,可以保证是一个相等的元素。在put ,get,remove等操作均会使用到元素的hashCode。因此hashcode能影响到HashMap等Hash结合框架的性能。

计算hashcode的一些方法:

  • boolean 计算(f?1:0)
  • byte,char,short,int 计算(int)f
  • long 计算(int)(f^(f>>>32))
  • float 计算Float.floatToInBits(f);
  • double 计算Double.doubleToLongBits(f),得到的结果再按long类型处理
  • 对象引用 null则返回0,否则递归调用hashCode
  • 数组 用以上规则计算每个元素的hashCode,或则使用Arrays.hashCode()

最后返回将上述计算得到的结果c,(result = 31 * result +c),返回 result。

注意:散列码计算过程中排除掉冗余域,也就是没有参与到equals中的域

上述使用到了31进行最后结果的处理,因为31有一个很好的特性是可以使用移位和减法来代替乘法。31*i = (i<<5)-i 而且VM会自动完成这种优化。

此外散列码的计算可能是开销很大的,可以考虑懒加载,既是只在第一次调用hashCode的时候进行计算,然后结果保存在实例中,下次直接返回保存的结果。但是前提是类是不可变的。

3.始终覆盖toString

Object默认的toString返回的结果是:类名@xxxxxx,其中xxxxxx是该对象的散列码的十六进制表示

覆盖toString方法可以在调试或者打印对象信息的时候更易于阅读理解。

但是在该类被广泛使用的时候要保证toString的返回格式的一致性,可利于维护。

4.谨慎覆盖clone

在JavaSE6中的clone方法的通用约定:

x.clone()!=x true
x.clone().getClass() == x.getClass() true(不绝对要求如此)
x.clone().equals(x) true(不绝对要求如此)

Object中的clone方法是protected的,而一个类实现了Cloneable接口改变了clone的行为,是的clone可以返回一个该对象得到逐域拷贝对象,使得不通过构造器就可以生成一个对象,否则抛出CloneNotSupportedExcetion。

如果覆盖了非final类的clone方法,则应该返回一个通过super.clone()得到的对象。而对于实现了Cloneable接口的类,如果所有超类否提供了良好的clone实现,那么我们可以在实现了Cloneable的子类实现一个共有的clone的方法,否则我们不应该提供任何clone实现。

当需要clone的类中存在引用类型且不是final的域的时候,我们仍需要调用该引用对象的clone方法进行clone。

class Wheel implements Cloneable{
  int size;
  int count;
  
  @Override
  public Wheel clone(){
    return (Wheel)super.clone();
  }
}
class Car implements Cloneable{
  Wheel wheel;
  String name;
  
  @Override
  public Car clone(){
    try{
      Car car = (Car)super.clone();
      car.wheel = wheel.clone();//省略这一行,那么car.wheel == this.wheel(浅拷贝)
      return car;
    }catch(CloneNotSupportedException e){
      throw new AssertionError();
    }
  }
}

另一种情况,存在引用链的情况下,我们需要递归的进行复制

public class HashTable implements Cloneable{
  private Entry[] buckets = .....;
  private static class Entry{
    final Object key;
    Object value;
    Entry next;
    Entry(Object key,Object value,Entry e){
      ....
    }
    
    public deepCopy(){
      //递归调用可能会栈溢出
      return new Entry(key,value,next==null?null:next.deepCopy());
      
      //使用迭代
      Entry e = new Entry(key,value,next);
      for(Entry p = e;p.next!=null;p=p.next)
        p.next = new Entry(key,value,p.next.next);
      return e;
    }
  }
  
  @Override
  public HashTable clone(){
    try{
      
      HashTable ht =(HashTable)super.clone();
      //这样得到的buckets中的元素与this的buckets中的元素时指向同一个对象的(浅拷贝)
      //ht.buckets = buckets.clone();
      
      
      //单独拷贝每个buckets中的链表元素(深拷贝)
      ht.buckets = new Entry[buckets.length];
      for(int i=0,s=buckets.lenght;i<s;i++){
        if(buckets[i]!=null){
          ht.buckets[i] = buckets[i].deepCopy();
        }
      }
      return ht;
    }catch(CloneNotSupportedException e){
      throw new AssertionError();
    }
  }
  
}

多线程环境霞需要自己实现同步的clone

其他实现对象拷贝的方法:

拷贝构造函器

public Car(Car car){
  .....
}

静态工厂拷贝方法

public static Car newInstance(Car car){
  ......
}

Java中的使用例子:

基于接口的拷贝:Collection, Map。HashSet hs = new HashSet(); TreeSet ts = new TreeSet(hs);

4.考虑实现Comparable接口

comparable接口属于一个泛型接口,一个类实现了该接口可以和许多依赖该接口的集合实现协作功能,很明显的一个功能就是内在的排序关系。

Comparable接口中待实现的的方法是compareTo(T t)。返回正整数,0,负整数来表示当前对象大于,等于,小于参数对象。

compareTo同样应该满足:自反性,传递性,对称性

compareTo返回0是,同样应该满足equals返回true

compareTo需要比较的值域较多是,从最右可能产生不一致的域开始比较,依次类推

对于float double类型的比较,使用Float.compare() ,Double.compare();

compareTo与equals的差别是compareTo是参数化的,不必进行参数转化,此外其他的很多equals中的特点同样适用与compareTo。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容