• 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

      对于不可变的类,永远不应该提供clone方法,因为会激发不必要的克隆。

      clone方法相当于另一个构造器;必须确保原始的对象和克隆的对象不会互相伤害。如果对象包含的域中,引用了可变的对象,并且只实现简单的clone,会导致修改原始的实例会破坏克隆对象中的约束条件。

    public class CloneDemo {
    
        public static void main(String[] args) throws CloneNotSupportedException {
    
            Clo clo = new Clo();
            System.out.println(Arrays.toString(clo.getArray()));
            Clo cloCopy = clo.clone();
            System.out.println(Arrays.toString(cloCopy.getArray()));
            clo.getArray()[0] = 9;
            System.out.println(Arrays.toString(cloCopy.getArray()));
        }
    
    }
    
    class Clo implements Cloneable {
    
        private int[] array;
    
        public Clo() {
            this.array = new int[]{1, 2, 3};
        }
    
        public int[] getArray() {
            return array;
        }
    
        @Override
        protected Clo clone() throws CloneNotSupportedException {
            return (Clo) super.clone();
        }
    }

    输出:

     可以看到修改原始对象数组的值,同时影响了克隆后对象的数组的值,为了使clone方法正常工作,应递归的调用clone,clone方法修改为如下:

      @Override
        protected Clo clone() throws CloneNotSupportedException {
            Clo clo =  (Clo) super.clone();
            clo.array = array.clone();
            return clo;
        }

    输出:

       ps:要注意,如果array的类型是final的上述方法无法正常工作,因为clone方法是禁止给final域赋新值的。

       克隆负责对象的最后方法是,先调用super.clone,然后爸结果对象中的所有域设为初始状态,再调用高层方法重新赋值,同时要注意,不要在clone方法的构造过程中,调用被子类覆盖的方法,如果调用了在子类中覆盖的方法,那么在该方法所在的子类有机会修正它在克隆对象中的状态之前,该方法就会先被执行,可能会导致克隆对象和原始对象的不一致。

      线程安全的类的clone方法也必须做到同步。

      对象拷贝的更好的方法是实现一个拷贝构造器或拷贝工厂:

      public Yum(Yum yum){ ... }

      public static Yum newInstance (Yum yum){ ... }

        第14条:考虑实现Comparable接口

      对排序敏感的类可以kaol实现Compareable接口

      compareTo约定:

    • 确保所有的x,y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
    • 比较关心可传递:x.compareTo(y) > 0,y.compareTo(z) > 0  --> x.compareTo(z) > 0
    • 确保x.compareTo(y) == 0 --> sgn(x.compareTo(z)) == sgn(y.compareTo(z))
    • 强烈建议 (x.compareTo(y) == 0) == (x.equals(y)),如果遵守这一条,那么compareTo方法所施加的顺序关系就被认为与equlas一致。
  • 相关阅读:
    解决知乎pc端频繁展现视频问题
    (转)求树的直径
    (转)海量数据处理专题
    const用法小结
    codeblocks中更改gnometerminal终端调试方法
    谷歌收购摩托罗拉移动
    (转)详解sizeof
    (转)位计算的诸多算法(计算二进制1的个数)
    (转)glibc中字符串函数源码分析
    (转)typedef的用法
  • 原文地址:https://www.cnblogs.com/youtang/p/12152849.html
Copyright © 2020-2023  润新知