effective java读书笔记——对于所有对象都通用的方法

Java中的所有类都继承自Object类,Object类中有许多通用的方法,这一章要讨论的是:对于Object类中的通用方法,我们的类要不要继承,以及继承时需要注意的事项。

第1条:equals(),覆盖时请遵守通用约定

首先看一下不需要覆盖的情况:

1.类的每个实例本质上是唯一的。(比如Static的,单例的等等),这样不需要特意覆盖equals方法,用Object类的equals()方法就足够了

2.不关心类是否实现了“逻辑相等”的测试功能。我们用equals的目的就是判断两个对象是否是“逻辑相等”的,比如String类的值是否相等。如果这个类并不需要这个功能,那么我们自然没必要覆盖equals()方法。

3.超类已经覆盖了equals()方法,且从超类继承过来的行为对子类也是合适的。

4.有一种“值类”不需要覆盖,即实例受控类,确保“每个值之多只存在一个对象”,枚举类型就是这种类,对于这样的类,逻辑相等与对象相等是一回事,所以不需要覆盖equals()方法

那么什么时候需要覆盖呢?

如果类有自己特有的“逻辑相等”的概念,而且父类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals()方法了。这通常属于“值类”。值类通常是一个只表示值的类,如Integer,程序员在用equals比较时仅仅希望知道它们的值是否相等,而关心它们是否指向同一个对象。

equals方法需要实现“等价关系”,包含四个方面:

1.自反性。x.equals(x)必须始终返回true.

2.对称性。如果x.equals(y)=true,那么y.equals(x)也必须为true.

3.传递性。如果有x.equals(y)= true,y.equals(z) = true,那么也必有x.quals(z) = true.

4.一致性。只要值没被修改,那么多次调用x.equals(y)的值应该始终相等。

下面举一些违反了这4条的例子。

违反自反性:这种一般很少,因为这一条仅仅要求对象等于自身。如果违反了的话,假设你把一个对象加到一个集合里,然后查询,该集合的contains方法会告诉你集合中不存在这个元素。

违反对称性:考虑下面的类,它实现了一个区分大小写的字符串,但比较时不考虑大小写。

public final class CaseInsensitiveString{
    private final String s;
    ...
    public boolean equals(Object o){
        if( o instanceof CaseInsentiveString){
            return s.equalsIgnoreCase( ((CaseInsentiveString) o).s);
        if( o instanceof String)
        //考虑了与普通String类的比较
            return s.equalsIgnoreCase((String) o);
        return false;
    }
}    

看上去考虑的很好,比较时,考虑了传入的对象是本类和String类的情况。但是,假设我们有两个对象:
CaseInsensitiveString cis = new CaseIntensitiveString("Polish");

String s = "polish";

那么,显然cis.equals(s)=true。但是问题来了,s.equals(cis) = false。这就违反了对称性。

解决方法是,如果A类的equals方法中引入了对B类对象的比较,那么B类反过来也一定要引入对A类的比较。

违反传递性:违反这条一般是因为继承时子类添加了新的值对象。子类添加的信息会影响equals的比较结果。

比如我们有一个简单的Point类:

public class Point{
    private final int x;
    private final int y;
    public Point(int x,int y){
        this.x = x;
        this.y = y;
    }
    public boolean equals(Object o){
        if(!(o instanceof Point))
            return false;
        Point p = (Point) o;
        return this.x == p.x && this.y == p.y;
    }
}

假设你想扩展这个类,为每个点加一个颜色信息:

public class ColorPoint extends Point{
    private final Color color;
    public ColorPoint(int x,int y ,Color c){
        super(x,y);
        color = c;
    }
}

equals方法是怎样呢?有两种考虑:1.只和有色点比较,要x,y,color都相等才返回true,对于不是有色点的对象,返回结果始终是false
2.有色点也能够和普通点比较,如果和普通点比较,就比较x,y的值。

看第一种方法的实现:

public boolean equals(Object o){
    if(!(o instance of ColorPoint)){
        return false;
    return super.equalso) && ((ColorPoint) o).color == color;
    }
}

如果有一个有色点对象cp(1,2,red)和一个普通点对象p(1,2),那么p.equals(cp) = true,cp.equals(p) = false。违反了对称性。

第二种方法:

public boolean equals(Object o){
    if(!(o instanceof Point))
        return false;
    if(!(o instanceof ColorPoint))
//如果对象不是colorPoint类的话,就用这个对象的equals方法来判断
        return  o.equals(this);
    return super.equalso) && ((ColorPoint) o ).color = color;
}

这种方法实现了对称性,但是牺牲了传递性。考虑有色点cp1(1,2,red),cp2(1,2,blue)和普通点p(1,2)
有cp1.equals(p) = true ,p.equals(cp2) ,但是cp1.equals(cp2)。违反了传递性。

我们无法在扩展可实例化的类的同时,既增加新的值组件,又保留equals约定。

怎么解决呢?

复合优先于继承。

我们不再继承Point类,而是新建一个ColorPoint类,在其中加入一个Point类的引用。

今天先写到这,明天继续

时间: 2025-01-07 20:49:36

effective java读书笔记——对于所有对象都通用的方法的相关文章

Effective Java2读书笔记-对于所有对象都通用的方法(一)

第8条:覆盖equals时请遵守通用约定 ①约定的内容 自反性.对于任何非null的引用值x.x.equals(x)必须返回true. 对称性.对于任何非null的引用值x和y.当且仅当y.equals(x)返回true时,x.equals(y)必须返回true. 传递性.对于任何非null的引用值x.y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,则x.equals(z)也必须是true. 一致性.对于任何非null的引用值x和y,只要equals的比

Effective Java 学习笔记之所有对象都通用的方法

一.覆盖equals时请遵守通用约定 1.满足下列任何一个条件时,不需要覆盖equals方法 a.类的每个实例本质上都是唯一的.此时就是Object中equals方法所表达的含义. b.不关心类是否提供了“逻辑相等”的测试功能 c.超类中覆盖的equals方法,使用于子类,不需要自己再覆盖了. d.类是私有或者包级私有,并确定equals方法永远不会被调用,可以不覆盖.或者覆盖equals方法,内容为抛出异常. 2.高质量equals方法的诀窍: a.使用==操作符检测“参数是否为这个对象的引用

Effective Java2读书笔记-对于所有对象都通用的方法(二)

第10条:始终要覆盖toString 这一条没什么好讲的,就是说默认的toString方法打印出来的是类名[email protected]+十六进制哈希码的值.我们应该覆盖它,使它能够展示出一些更为详细清晰的信息,这个看实际情况吧. 第11条:谨慎地覆盖clone 有时候会出现这样的场景,你需要备份一些数据,对其一进行修改时,另外一个不受影响.这样,直接Foo a = new Foo(); Foo b = a; 是不可行的,b引用和a指向的是同一个对象. 这一条讲了很多,最后的总结是,Clon

Effective Java2读书笔记-对于所有对象都通用的方法(三)

第12条:考虑实现Comparable接口 这一条非常简单.就是说,如果类实现了Comparable接口,覆盖comparaTo方法. 就可以使用Arrays.sort(a)对数组a进行排序. 它与equals方法有点类似,但是,因为Comparable接口是参数化的,而且comparable方法时静态的类型,因此不必进行类型检查,也不需要对它的参数进行类型转换.返回负值代表小,正值代表大,0代表相等.

Effective Java读书笔记(3对于所有对象都通用的方法)

3.1 覆盖equals时请遵守通用约定 什么时候应该覆盖Object.equals()方法呢? 如果类具有自己特有的"逻辑相等"概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法. Object.equals()方法具有自反性.对称性.传递性.一致性和与null比较返回false的特点. 实现高质量equals方法的诀窍: (1)使用==操作符检查"参数是否为这个对象的引用".如果是,则返回true,这

Effective java 第三章对于所有对象都通用的方法(一) 读书笔记

对于所有对象都通用的方法 覆盖equals时请遵守通用约定 类的每个实例本质上都是唯一的. 不关心类是否提供了逻辑相等的测试功能 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的. 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用. throw new AssertionError() 一般覆盖Object.equals都是值类 但有一种值类不需要覆盖equals方法,即实例受控,确保每个值至多只存在一个对象的类.如枚举 覆盖equals方法,通用约定. 自

[Effective Java 读书笔记] 第三章 对所有对象都通用的方法 第八 ---- ?条

这一章主要讲解Object类中的方法, Object类是所有类的父类,所以它的方法也称得上是所有对象都通用的方法 第八条 覆盖equals时需要遵守的约定 Object中的equals实现,就是直接对对象进行相等的比较: public boolean equals(Object obj) { return (this == obj); } 那么什么时候需要覆盖equals呢? 当你的类有自己的逻辑相等,而不是对象相等时,应该自己实现equals,比如Date和Interger,他们的相等比较不仅

Effective Java 读书笔记(2创建和销毁对象)

第一章是引言,所以这里不做笔记,总结一下书中第一章的主要内容是向我们解释了这本书所做的事情:指导Java程序员如何编写出清晰.正确.可用.健壮.灵活和可维护的程序. 2.1考虑用静态工厂方法代替构造器 静态工厂方法与构造器相比有四大优势: (1)静态工厂方法有名称,具有适当名称的静态工厂方法易于使用.易于阅读: (2)不必每次在调用它们的时候都创建一个新的对象: (3)可以返回原返回类型的任何子类型的对象: (4)在创建参数化类型实例的时候,它们使代码变得更加简洁. 同时静态工厂方法也有两大缺点

Effective Java读书笔记(4 类和接口)

4.1 使类和成员的可访问性最小化 要区别设计良好的模块和设计不好的模块,最重要的因素在于,这个模块对于外部的其他模块而言,是否隐藏其内部数据和其他实现细节.设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰的隔离开来,然后模块之间只通过API进行通信,一个模块不需要知道其他模块内部的工作情况,这个概念被称为信息隐藏或封装,是软件设计的基本原则之一. 4.2 在公有类中使用访问方法而非公有域 坚持面向对象程序设计思想:如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改