Effective Java-读书笔记 第三章-对于所有对象都通用的方法

10.覆盖equals时请遵守通用约定

  1. 覆盖equals方法看上去似乎非常简单,但是有许多覆盖方式会导致错误,并且后果非常严重。
  2. equals方法的等价关系
    1. 自反性
    2. 对称性
    3. 传递性
    4. 一致性
    5. 对于任何非null的引用值x,x.equals(null) = false
  3. 实现高质量equals方法的诀窍
    1. 使用==检查“参数是否是当前对象的引用”,如果是则返回true,这只是一种性能优化,但如果比较操作比较昂贵,就有必要进行这一步操作。
    2. 使用instanceof进行类型检查,大部分情况下是检查是否是当前类,某些情况下也可以是该类实现的某个接口。
    3. 把参数转成正确的类型
    4. 对于该类中的每个关键域,检查参数中的域是否与该对象中对应的域相匹配。
      1. 对于非float,double的基本类型域,可以直接使用==对比
      2. 对于float,double,可以使用Float.compare()和Double.compare(),不建议使用Float.equals和Double.equals,因为每次比较时都要进行自动装箱,这会导致性能下降。
      3. 对于对象引用域,可以递归地调用equals方法
  4. 下面是一些作者的告诫:
    1. 覆盖equals时总要覆盖hashcode(详见第11条)
    2. 不要企图让equals过于智能
    3. 不要将equals声明中的Object对象替换为其他的类型。问题在于,这种方式并没有覆盖原来的Object.equals,而只是重载了它。

11.覆盖equals时总要覆盖hashCode

  1. 在每个覆盖了的equals方法的类中,那必须覆盖hashCode方法

  2. 覆盖hashCode的一个常见方式

    1. 先创建出一个int类型的result变量
    2. 对每个关键域f计算其hashCode
    3. 将hashCode保存为属性,并实现”延迟初始化“
        private int hashCode;
        private String p1 = "sadas";
        private int p2 = 2;
        @Override
        public int hashCode() {
            int result = hashCode;
            if(result == 0) {
                result = 1;
                result = 31 * result + p1.hashCode();
                result = 31 * result + Integer.hashCode(p2);
                hashCode = result;
            }
            return result;
        }
    
  3. 不要试图从散列码计算中排除掉一个对象的关键域来提高性能,这有可能导致散列函数将大量实例映射到极少数的散列码上,本来以线性级时间运行的程序,将会以平方级的时间运行。

  4. 不要对hashCode的返回值做出具体的规定,因为客户端无法理所应当的依赖它,这样可以为修改提供灵活性。

12.始终要覆盖toString

  1. 提供好的toString实现可以使类用起来更加舒适,使用了这个类的系统也更易于调试。
  2. toString方法中应该返回对象中包含的所有值得关注的信息。
  3. 我们可以选择性的在文档中指定toString返回值的格式,指定格式的好处是它可以被用作一种标准的,明确的,适合人阅读的对象表示法
    1. 如果指定了格式,最好再提供一个静态工厂或者构造器,以便程序员可以轻易地在对象及其字符串之间来回转换
    2. 无论是否决定指定格式,都应该在文档中表明你的意图
  4. 为toString返回值中包含的所有信息提供一种可以通过编程访问之的途径。

13.谨慎地覆盖clone

  1. 使用clone的问题

    1. clone() 方法是浅拷贝:默认情况下,clone() 方法执行的是浅拷贝,即只复制对象的字段值。如果对象包含引用类型的字段,那么克隆的对象和原始对象将共享相同的引用,这可能导致意外的副作用。如果需要深拷贝,即复制对象及其引用的对象,需要在实现 clone() 方法时进行额外的处理。
    2. clone() 方法需要实现 Cloneable 接口:为了使用 clone() 方法,对象的类必须实现 Cloneable 接口,否则在调用 clone() 方法时会抛出 CloneNotSupportedException 异常。这种限制可能会在某些情况下引入不必要的复杂性。
    3. 引入副作用和破坏封装性:使用 clone() 方法可能会引入副作用和破坏对象的封装性。因为 clone() 方法可以直接访问对象的字段,绕过了对象的访问控制机制。
    4. 替代方法:在实践中,可以使用其他替代方法来实现对象的复制和创建副本。例如,可以使用拷贝构造函数、工厂方法或复制方法来创建对象的副本,这些方法可以更加灵活和明确地控制对象的复制过程
  2. 使用拷贝构造函数替代clone

    public class Person {
        private String name;
        private int age;
    
        // 构造函数
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        // 拷贝构造函数
        public Person(Person other) {
            this.name = other.name;
            this.age = other.age;
        }
    
        // toString 方法
        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    
        public static void main(String[] args) {
            Person person1 = new Person("Alice", 25);
            Person person2 = new Person(person1); // 使用拷贝构造函数创建副本
    
            System.out.println(person1); // 输出:Person [name=Alice, age=25]
            System.out.println(person2); // 输出:Person [name=Alice, age=25]
        }
    }
    
  3. 使用工厂方法

    public class Person {
        private String name;
        private int age;
    
        private Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public static Person createPerson(Person other) {
            return new Person(other.name, other.age);
        }
    
        // toString 方法
        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    
        public static void main(String[] args) {
            Person person1 = new Person("Alice", 25);
            Person person2 = Person.createPerson(person1); // 使用工厂方法创建副本
    
            System.out.println(person1); // 输出:Person [name=Alice, age=25]
            System.out.println(person2); // 输出:Person [name=Alice, age=25]
        }
    }
    
  4. 使用复制方法

    public class Person {
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Person copy() {
            return new Person(this.name, this.age);
        }
    
        // toString 方法
        @Override
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    
        public static void main(String[] args) {
            Person person1 = new Person("Alice", 25);
            Person person2 = person1.copy(); // 使用复制方法创建副本
    
            System.out.println(person1); // 输出:Person [name=Alice, age=25]
            System.out.println(person2); // 输出:Person [name=Alice, age=25]
        }
    }
    

14.考虑实现Comparable接口

  1. 如果在编写一个值类,它具有非常明显的内在排序关系,比如按字母排序、按数值排序、按日期排序等,那就应该坚决考虑实现Comparable接口。

  2. 使用比较器实现构造

    public class ComparableEntity implements Comparable<ComparableEntity>{
    
        private int p1;
        private double p2;
        private long p3;
        private Date date;
        private final static Comparator<ComparableEntity> COMPARATOR = Comparator
                .comparingInt((ComparableEntity c) -> c.p1)
                .thenComparingDouble((ComparableEntity c) -> c.p2)
                .thenComparingLong((ComparableEntity c) -> c.p3)
                .thenComparing((ComparableEntity c) -> c.date);
    
        @Override
        public int compareTo(ComparableEntity o) {
            return COMPARATOR.compare(this,o);
        }
    
        public ComparableEntity(int p1, double p2, long p3, Date date) {
            this.p1 = p1;
            this.p2 = p2;
            this.p3 = p3;
            this.date = date;
        }
    
        public static void main(String[] args) {
            final ComparableEntity entity = new ComparableEntity(2, 2.4, 123,new Date(System.currentTimeMillis() - 1000));
            final ComparableEntity entity2 = new ComparableEntity(2, 2.4, 122,new Date());
            System.out.println(entity.compareTo(entity2));
        }
    }
    
  3. 每当在comparaTo方法的实现中比较域值时,都应该避免使用 < 和 > 操作符,而应该使用装箱基本类型的静态compare方法,或者在Comparator接口中使用比较器构造方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值