Effective Java 读书笔记(2)

Object 通用方法

Object是一个具体类,但是设计它主要是为了扩展,其所有的非final方法(equals、hashCode、toString、clone、finalize)都有明确的通用约定,我们可以在遵守通用约定的前提下override这些方法。

注:不遵守通用约定(general contract)复写Object类的非final方法,可以会导致依赖于这些约定的类(hashMap之类的)无法正常工作。


第8条:覆盖equals时请遵守通用约定

Object的equals原意:类的实例只与它本身相同,即比较地址相同,不比较值。
在以下任意情况下不应覆盖equals方法:

  • 类的每个实例本质上都是唯一的,即比较地址不比较值(如Thread)
  • 不关心类是否提供了“逻辑相等(值相等)”的测试功能
  • 超类已覆盖了equals方法,且适用于子类(如Set实现类从AbstractSet继承equals方法,List实现类从AbstractList继承equals方法)
  • 类是私有的或是包级私有的,可以确定其equals不会被调用。

Tip:禁止调用可以在对应的方法体内抛出错误throw new AssertionError()

如果类具有其特有的“逻辑相等”概念。并且超类没有实现期待的行为,此时我们需要覆盖equals方法。这通常属于“值类(value class)”的情况。值类仅仅是一个表示值的类,如Integer或者Date。当我们调用值类的equals时,往往是为了判断其值是否相等,而不关心是否为同一个对象(可以使用==判断是否为同一实例)。

Tip: Set、List、Map等集合类多使用equals判断key是否相等,所以我们需要在使用这些类时确保我们的equals方法是符合我们的预期的。

equals方法的通用约定(JavaSE6):
x、y、z皆为非null引用值

  • 自反性(reflextive):assert x.equals(x);
  • 对称性(symmetric):if(x.equals(y)) assert y.equals(x);
  • 传递性(transitive):if(x.equals(y)&&y.equals(z)) assert x.equals(z);
  • 一致性(consistent):如果x、y未被修改,则调用x.equals(y)应一直返回true或者一直返回false
  • 非空性(Non-nullify):x.equals(null)必须返回false,assert !x.equals(null)

实现高质量equals的诀窍:

  • 使用==操作符检查“参数是否为这个对象的引用”
  • 使用instanceof操作符检查“参数是否为正确的类型”
  • 把参数转换为正确的类型
  • 对于该类的“关键域”,逐一比较(见以下Tip)
  • 回顾并进行单元测试:对称性、传递性、一致性

Tip: 逐一比较的诀窍:
1、对于不是float、double的基本类型域,可以直接使用==进行比较;
2、对于对象引用域,可以递归地调用equals方法进行比较;
3、对于float域,可以使用Float.compare方法;对于double域,可以使用Double.compare方法;
4、对于数组域,可以使用Array.equals方法
5、使用field == null ? o.field == null : field.equals(o.field)(field == o.field) || (field != null) && (field.equals(o.field))习惯写法
6、优化比较顺序:优先比较最有可能不一致的域、比较开销最低的域。不比较不属于对象逻辑状态(值)的域,或者冗余域(除非其能代表大量关键域,比较其能节省大量时间)

最后的告诫:

  • 覆盖equals时总要覆盖hashCode(见第9条);
  • 不要企图让equals过于智能;
  • 不要将equals声明中的Object对象替换为其他的类型(这样是overload而不是override),可以添加@override注解帮助规避错误。

第9条:覆盖equals时总要覆盖hashCode

在Object类中,hashCode方法是一个native方法,返回值与对象的存储地址相关,计算方法由jvm决定。

hashCode通用公约(JavaSE6):

  • 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个程序多次执行过程中,每次执行所返回的整数可以不一致;
  • 如果两个对象根据equals方法比较是相等的,那么调用者两个对象的hashCode方法必须返回同一个整数;
  • 如果两个对象根据equals方法比较是不相等的,那么调用者两个对象的hashCode方法不一定返回不同整数。给不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能

注: HashTable、HashMap、HashSet等散列集合类的散列算法实现往往依赖于hashCode。假设我们覆盖了equals方法,但没有覆盖hashCode,那么在我们理解中equals的对象,对于散列集合类而言,可能是不相等。

高质量的hashCode方法的原则:

  • 对于equals的对象,返回同一个整数;
  • 对于非equals的对象,尽可能返回不一样的整数;
  • 为不相等的对象均匀产生不相等的散列码。

编写好质量的hashCode的诀窍:

  • 把某个非零的常数值(如17)保存在一个名为result的int类型的变量中;
  • 对于对象中的每个关键域f,完成以下步骤:
    1、为该域计算int类型的散列码c(见以下Tip);
    2、计算result = 31 * result +c,合并c
  • 返回result
  • 编写单元测试,保证相等的实例能得到同一个散列值

TIp: 计算int类型的散列码c
1、如果该域是boolean类型,则计算f ? 1 : 0
2、如果该域是byte、char、short、int类型,则计算(int) f
3、如果该域是long类型,则计算(int)( f ^ (f>>>32))
4、如果该域是float类型,则计算Float.floatToTintBits(f)
5、如果该域是long类型,则计算'Double.doubleToLongBits(f)',然后按照步骤3为long计算散列值;
6、如果该域是一个对象引用,为null则返回0,否则递归调用hashCode;
7、如果该域是一个数组,调用Arrays.hashCode

在计算散列码时,只能用到覆盖的equals函数比较的关键域,否则相等(equals)的对象可以会得到不同的散列值。
初始化常数值17是任选的,不为0就可以了,目的在于增加散列值为0的关键域的影响。
31是一个比较特殊的数字,首先它是一个奇素数,如果乘数为偶数,则相当于移位,会增加冲突。再者,其乘法可以被jvm自动优化:31 * i == (i << 5 ) - i

Tip:如果一个对象是不可变的,或者其散列值计算开销比较大,可以考虑将其散列值在实例初始化时计算后缓存在对象内部。


第10条:始终要覆盖toString

在Object类中,toString会返回类名,一个@符号,和散列码的无符号十六进制表示法,这通常不是我们所希望看到的。
toString的通用约定指出,被返回的字符串应该是一个简洁的,信息丰富的,易于阅读的表达形式,并建议所有的子类都override这个方法。
提供好的toString实现可以使类用起来更加舒适,特别是在打印对象信息的时候,我们可以之间将对象作为参数直接传入print函数,打印出对象信息。
在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息
在实现toString时,还应考虑是否在文档中指定返回值的格式。若是指定格式,最好再提供一个静态工厂方法或者构造器,以便从这种表示法中转换对象;若是不指定返回值的格式,则可以保持toString方法的灵活性,以便在后期改进格式。


第11条:谨慎覆盖clone方法

Cloneable接口的目的在于表明这个对象是允许被clone的,然而它并没有成功地达到这个目的。其主要原因在于它并没有包含任何方法,而Object的clone方法是受保护的,需要反射调用,而且反射调用也不一定会成功。
Cloneable接口的作用在于表明:实现了Cloneable接口的类,应该覆盖了Object的clone方法。
clone方法的通用公约:
创建和返回该对象的一个拷贝。这个拷贝的精确含义由该对象的类决定。一般有以下含义:

  • 对于任何对象x,表达式x.clone() != x将会是true
  • 对于任何对象x,表达式x.clone().getClass() == x.getClass将会是true
  • 对于任何对象x,表达式x.clone().equals(x)将会是true

拷贝对象往往会导致创建它的类的一个新实例,但它同时要求拷贝类内部的数据结构。这个过程没有调用构造器。

覆盖clone方法,必须保证其拷贝是深度拷贝,这往往需要其超类实现了良好的clone方法。其次,对于拷贝对象内部的引用对象,我们需要实例化一个新的对象,并修改其值,而不能简单复制引用。覆盖clone方法是一件十分吃力不讨好的事。
所有实现了Cloneable接口的类都应该用一个公有的方法覆盖clone。此公有方法首先调用super.clone(),然后修正任何需要修正的域。
除非你扩展了一个实现Cloneable接口的类,否则最好提供某些其他的途径代替clone方法,或者干脆不提供这样的功能。
我们可以提供一个拷贝构造器或者拷贝工厂来替代clone方法:

// 拷贝构造器:以拷贝对象为参数
public Yum(Yum yum)
// 拷贝工厂
public static Yum newInstance(Yum yum)

更进一步,我们可以提供一个转换构造器,其参数是一个超类/接口的对象。按照惯例,所有通用集合实现都提供了一个转换构造器,如一个HashSet s,可以通过TreeSet(s)转换类型。

注意:对于一个专门为了继承而设计的类,如果你未能提供香味良好的受保护的clone方法,它的子类就不可能实现Cloneable方法。


第12条:考虑实现Comparable接口

compareTo方法是Comparable接口唯一的方法,它与Object类的通用方法有很大的相似性。compareTo方法不但允许进行简单的等同性比较,而且允许执行顺序比较。类实现了Comparable接口,就表明它的实例具有内在的排序关系。对于实现了Comparable接口的对象数组进行排序:Arrays.sort(a)
一旦类实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进项协作。事实上,java的所有公共值类都实现了这个接口。假设你正在编写一个值类,它具有非常明显的内在排序关系,那么你就应该坚决考虑实现这个接口。

compareTo的通用公约:
将这个对象与指定的对象进行比较,当对象

  • 小于指定对象时,返回负整数;
  • 等于指定对象时,返回0;
  • 大于指定对象时,返回正整数;
  • 无法与指定对象进行比较,抛出ClassCastException

与equals类似,compareTo同样有自反型传递性对称性
编写compareTo方法与编写equals方法非常相似,但也有一些区别:

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

推荐阅读更多精彩内容