对于所有对象都通用的方法
10.覆盖equals时请遵守通用约定
- 覆盖equals方法看上去似乎非常简单,但是有许多覆盖方式会导致错误,并且后果非常严重。
- equals方法的等价关系
- 自反性
- 对称性
- 传递性
- 一致性
- 对于任何非null的引用值x,x.equals(null) = false
- 实现高质量equals方法的诀窍
- 使用==检查“参数是否是当前对象的引用”,如果是则返回true,这只是一种性能优化,但如果比较操作比较昂贵,就有必要进行这一步操作。
- 使用instanceof进行类型检查,大部分情况下是检查是否是当前类,某些情况下也可以是该类实现的某个接口。
- 把参数转成正确的类型
- 对于该类中的每个关键域,检查参数中的域是否与该对象中对应的域相匹配。
- 对于非float,double的基本类型域,可以直接使用==对比
- 对于float,double,可以使用Float.compare()和Double.compare(),不建议使用Float.equals和Double.equals,因为每次比较时都要进行自动装箱,这会导致性能下降。
- 对于对象引用域,可以递归地调用equals方法
- 下面是一些作者的告诫:
- 覆盖equals时总要覆盖hashcode(详见第11条)
- 不要企图让equals过于智能
- 不要将equals声明中的Object对象替换为其他的类型。问题在于,这种方式并没有覆盖原来的Object.equals,而只是重载了它。
11.覆盖equals时总要覆盖hashCode
-
在每个覆盖了的equals方法的类中,那必须覆盖hashCode方法
-
覆盖hashCode的一个常见方式
- 先创建出一个int类型的result变量
- 对每个关键域f计算其hashCode
- 将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; }
-
不要试图从散列码计算中排除掉一个对象的关键域来提高性能,这有可能导致散列函数将大量实例映射到极少数的散列码上,本来以线性级时间运行的程序,将会以平方级的时间运行。
-
不要对hashCode的返回值做出具体的规定,因为客户端无法理所应当的依赖它,这样可以为修改提供灵活性。
12.始终要覆盖toString
- 提供好的toString实现可以使类用起来更加舒适,使用了这个类的系统也更易于调试。
- toString方法中应该返回对象中包含的所有值得关注的信息。
- 我们可以选择性的在文档中指定toString返回值的格式,指定格式的好处是它可以被用作一种标准的,明确的,适合人阅读的对象表示法
- 如果指定了格式,最好再提供一个静态工厂或者构造器,以便程序员可以轻易地在对象及其字符串之间来回转换
- 无论是否决定指定格式,都应该在文档中表明你的意图
- 为toString返回值中包含的所有信息提供一种可以通过编程访问之的途径。
13.谨慎地覆盖clone
-
使用clone的问题
clone()
方法是浅拷贝:默认情况下,clone()
方法执行的是浅拷贝,即只复制对象的字段值。如果对象包含引用类型的字段,那么克隆的对象和原始对象将共享相同的引用,这可能导致意外的副作用。如果需要深拷贝,即复制对象及其引用的对象,需要在实现clone()
方法时进行额外的处理。clone()
方法需要实现Cloneable
接口:为了使用clone()
方法,对象的类必须实现Cloneable
接口,否则在调用clone()
方法时会抛出CloneNotSupportedException
异常。这种限制可能会在某些情况下引入不必要的复杂性。- 引入副作用和破坏封装性:使用
clone()
方法可能会引入副作用和破坏对象的封装性。因为clone()
方法可以直接访问对象的字段,绕过了对象的访问控制机制。 - 替代方法:在实践中,可以使用其他替代方法来实现对象的复制和创建副本。例如,可以使用拷贝构造函数、工厂方法或复制方法来创建对象的副本,这些方法可以更加灵活和明确地控制对象的复制过程
-
使用拷贝构造函数替代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] } }
-
使用工厂方法
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] } }
-
使用复制方法
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接口
-
如果在编写一个值类,它具有非常明显的内在排序关系,比如按字母排序、按数值排序、按日期排序等,那就应该坚决考虑实现Comparable接口。
-
使用比较器实现构造
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)); } }
-
每当在comparaTo方法的实现中比较域值时,都应该避免使用 < 和 > 操作符,而应该使用装箱基本类型的静态compare方法,或者在Comparator接口中使用比较器构造方法。