EffectiveJava学习笔记(三)

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

类具有特有的逻辑相等的概念,且超类没有覆盖equals方法时应该覆盖equals方法,例如integer、String这种“值类”。

但是有一种值值类无需覆盖equals,即实例受控,每个值最多只存在一个对象的类,比如枚举类,这种类逻辑相同和对象相同是同一回事,所以Object的equals方法等她与逻辑的equals。

equals必须满足四种等价关系:自反性、对称性、传递性、一致性,并且对于非null的x,x.equals(null)永远返回false.

对称性:

CaseString类equals方法比较不区分大小写的字符串,问题在与CaseString的equals方法知道普通类型的字符串,但是String的equals方法并不知道CaseString,比较时违背了对称性。
public class EqualsDemo {

    public static void main(String[] args) {

        CaseString caseString = new CaseString("Case");
        String string = "case";
        System.out.println(caseString.equals(string));
        System.out.println(string.equals(caseString));
    }
}

final class CaseString {
    private final String s;

    public CaseString(String s) {
        this.s = Objects.requireNonNull(s);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof CaseString) {
            return s.equalsIgnoreCase(((CaseString) obj).s);
        }
        if (obj instanceof String) {
            return s.equalsIgnoreCase((String) obj);
        }
        return false;
    }
}

 解决方式:equals方法中去除与String类型比较的部分。

@Override
    public boolean equals(Object obj) {
        return obj instanceof CaseString && s.equalsIgnoreCase(((CaseString) obj).s);
    }

传递性:

  子类增加的信息会影响equals的比较结果,我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定。但是可以在抽象类的子类增加新的组件且不违反equals的规定。

一致性:

  如果两个对象相等,他们就要一直保持相等,所以不要使equals方法依赖不可靠的资源。

非空性:

  指所有的对象都不能等于null,大多数情况下一个非空对象equals null,都会返回false(返回true的情况我还没有想到),很多类的equals方法中为了避免抛出NPE,会先判读入参是否为null,其实这是没有必要的,因为equals方法在比较之前,必须是呀instanceof判断Object类型的入参是否为比较类型子类的对象,如果instanceof的第一个操作数是null,那么不管第二个操作数是什么类型,都会返回false,所以不需要显式的null检查。

ps:

1.对于非float和double类型的基本数据类型,可以使用==比较,对于float和double类型,可以使用Float.compare(x,y)和Double.compare(x,y),因为存在Float.NAN,-0.0f之类的常量,如果使用Float.equals()或Double.equals()会对基本类型进行装箱操作,降低性能.

2.不要将equals方法参数中Object对象替换成其他类型的对象,如果替换的话,它将不会重写Object.equals()而是重载。

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

在每个覆盖了equals方法的类中都要覆盖hashCode方法,否则这种类对象元素的散列集合(如HashMap、HashSet)将无法正常运行。

例:

 
 
public class EqualsDemo {

public static void main(String[] args) {

CaseString caseString = new CaseString("Case");
String string = "case";
CaseString caseString1 = new CaseString("Case");
System.out.println(caseString.equals(caseString1));
HashMap<CaseString, Integer> map = new HashMap<>();
map.put(caseString, 1);
map.put(caseString1, 2);
System.out.println(map);
CaseString caseString3 = new CaseString("test");
CaseString caseString4 = new CaseString("test");
map.put(caseString3, 3);
System.out.println(map.get(caseString4));

}
}

final class CaseString {
private final String s;

public CaseString(String s) {
this.s = Objects.requireNonNull(s);
}

@Override
public boolean equals(Object obj) {
return obj instanceof CaseString && s.equalsIgnoreCase(((CaseString) obj).s);
}
}
 

输出:

equals和HashCode比较规则:

  equals相等->hashCode一定相等

  hashCode相等->equals不一定相等

  hashCode不等->equals一定不相等

所以在比较时,会优先比较hashCode,hashCode相等后再比较equals,所以不同的对象生成不同的hash值,会提高比较的性能。

  第12条:始终要覆盖toString方法

返回值的关注的信息,易于调试。

  第13条:谨慎地覆盖clone

Cloneable接口是一个标记接口,表示实现了这个接口的类可以被克隆,object的clone方法返回该对象的逐级拷贝,否则抛出CloneNotSupportedException异常。实现cloneable接口的类是为了提供一个功能适当的公有clone方法,clone方法无需调用构造器就可以创建对象。

clone方法约定:

1. x.clone != x

2. x.clone.getClass == x.getClass

3. x.clone.equals(x) == true

【未完待续】

猜你喜欢

转载自www.cnblogs.com/youtang/p/12152849.html