Object 类是Java中所有类的始祖,在Java中每个类都是由它扩展来的。
可以使用Object类型的变量引用任何类型的对象。
所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。
C++注释:在C++中没有所有类的根类,不过,每个指针都可以转换成void* 指针。
1、equals方法
在Object类中,equals方法用于判断两个对象是否具有相同的引用。Object类中源码如下:
public boolean equals(Object obj) { return (this == obj); }
如果两个对象具有相同的引用,它们一定是相等的。但是对于大多数类来说,这种判断没有什么意义,因为经常会需要判断两个对象的状态是否相等,状态相等就认为相等。下面给出编写一个完美equals方法的建议:
1)显示参数命名为 otherObject
2)检测 this 和 otherObject 是否引用同一个对象:if ( this == otherObject ) return true; 起到优化的作用
3)检测 otherObject 是否为 null,如果为 null,返回 false:if (otherObject == null) return false;
4)比较 this 与 otherObject 是否属于同一个类,如果 equals 的语义在每个子类中有所改变,即由子类的属性状态决定相等性,就使用 getClass 检测:if (getClass() != otherObject.getClass()) return false;如果所有的子类都拥有统一的语义,即由超类决定相等的概念,那么就使用 instanceof 检测:if (!(otherObject instanceof ClassName)) return false;
5)将 otherObject 转换成相应的类类型变量:ClassName other = (ClassName) otherObject
6)现在开始对所有需要比较的域进行比较了。使用==比较基本类型域,使用equals比较对象域。 return field1 == other.field1 && Objects.equals(field2,other.field2) && ...;如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。
提示:对于数组类型的域,可以使用Arrays.equals方法检测相应的数据元素是否相等。
顺便说一下:
在Java中,instanceof 运算符的前一个操作符是一个引用变量,该变量为空则返回 false,后一个操作数通常是一个类(可以是接口),用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是返回 true,否则返回 false 。也就是说在使用 instanceof 关键字做判断时, instanceof 操作符的左右操作数必须有继承或实现关系。
使用 Objects 类的 equals 方法 Objects.equals(field2,other.field2) 避免如果 field2 为 null 时,field2.equals(other.field2) 这种比较方法会产生异常,Objects 类在 java.util 包中,源码如下:
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
举个栗子:
1 package com.song; 2 3 import java.time.LocalDate; 4 import java.util.Objects; 5 6 public class Employee { 7 private String name; 8 private double salary; 9 private LocalDate hireDay; 10 11 public Employee() { 12 13 } 14 public Employee(String name, double salary, int year, int month, int day) { 15 this.name = name; 16 this.salary = salary; 17 hireDay = LocalDate.of(year, month, day); 18 } 19 20 //覆盖超类 Object 的 equals 方法 21 @Override 22 public boolean equals(Object otherObject) { 23 if (this == otherObject) return true; 24 if (otherObject == null) return false; 25 26 if (getClass() != otherObject.getClass()) return false; 27 // or 28 // if (!(otherObject instanceof Employee)) return false; 29 30 Employee other = (Employee) otherObject; 31 32 /** 33 * 对于这里可以直接用other.name访问私有域解释 34 * 有一个原则是:一个方法可以访问所属类的所有对象的私有数据 35 * 即Employee类的方法可以访问Employee类的任何一个对象的私有域。 36 */ 37 return Objects.equals(name, other.name) 38 && salary == other.salary 39 && Objects.equals(hireDay, other.hireDay); 40 } 41 42 public static void main(String[] args) { 43 Employee empOne, empTwo = null; 44 45 empOne = new Employee("song", 10, 2019, 5, 25); 46 System.out.println("empOne equals empTwo:" + empOne.equals(empTwo)); 47 48 empTwo = new Employee("song", 10, 2019, 5, 25); 49 System.out.println("empOne equals empTwo:" + empOne.equals(empTwo)); 50 51 System.out.println(empOne.hashCode()); 52 System.out.println(empTwo.hashCode()); 53 54 } 55 56 }
运行结果:
empOne equals empTwo:false empOne equals empTwo:true
子类中定义equals方法:
1 package com.song; 2 3 public class Manager extends Employee { 4 private double bonus; 5 6 @Override 7 public boolean equals(Object otherObject) { 8 if (!super.equals(otherObject)) 9 return false; 10 Manager other = (Manager) otherObject; 11 return bonus == other.bonus; 12 } 13 }
到这里基本说完了,还有Java语言规范要求的 equals 方法的 5 条特性,可以在API文档中 Object 类中 equals 方法查看。
2、hashCode方法
散列码是由对象导出的一个整形值。Object类中定义的hashCode方法源码如下:
public native int hashCode();
使用了native关键字,我看不懂具体方法实现,有时间看完 native关键字 这篇博客应该能明白一些,具体以后再说,暂时不常用。
String 类使用下列算法计算散列码:
int hash = 0; for (int i = 0; i < length(); i++) hash = 31 * hash + charAt(i);
由于 hashCode 方法定义在 Object 类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。 使用 Object 类的这个方法可以用 Objects 的 hashCode 方法保证 null 安全。
如果重新定义 equals 方法,就必须重新定义 hashCode 方法,以便用户可以将对象插入到散列表中(散列表在核心技术I第9章讨论)。
hashCode方法应该返回一个整形数值,并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。例如,下面是 Employee 类的 hashCode 方法。
@Override //组合多个散列值,如果存在数组类型的域,使用静态的Arrays.hashCode方法计算一个散列码,equals与hashCode的定义必须一致 public int hashCode() { return Objects.hash(name, salary, hireDay); }
涉及的源码如下:
java.util.Objects public static int hash(Object... values) { return Arrays.hashCode(values); } java.util.Arrays public static int hashCode(Object a[]) { if (a == null) return 0; int result = 1; for (Object element : a) result = 31 * result + (element == null ? 0 : element.hashCode()); return result; }
equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true,那么 x.hashCode() 就必须与 y.hashCode() 具有相同的值。例如,如果用定义的 Employee.equals 比较雇员的ID,那么 hashCode 方法就需要散列ID,而不是雇员的姓名或存储地址。
先说到这吧,后面看了散列表再理解。
3、toString方法
Object 中的一个重要方法,用于返回表示对象值的字符串。
下面是 Employee 类中的 toString 方法的实现:
@Override public String toString() { return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]"; }
下面是 Manager 类中的 toString 方法:
@Override public String toString() { return super.toString() + "[bonus=" + bonus + "]"; }
随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符 “+” 连接起来,Java 编译就会自动地调用 toString 方法,以便获得这个对象的字符串描述。
提示:在调用 x.toString() 的地方可以用 ""+x 替代。这里的 x 就是 x.toString() 。与 toString 不同的是,如果 x 是基本类型,这条语句照样能够执行。
System.out.println(x); println 方法就会直接调用 x.toString(x) ,并打印输出得到的字符串。
Object 类定义了 toString 方法,用来打印输出对象所属的类名和散列码。例如 System.out.println(System.out); 语句输出 java.io.PrintStream@154617c ,因为 PrintStream 类没有覆盖 toString 方法。Object 类的toString方法源码如下:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
警告:令人烦恼的是,数组继承了 object 类的 toString 方法,数组类型将按照旧的格式打印,生成字符串如 “[I@1a46...”(前缀 [I 表明是一个整形数组)。修正的方式是调用静态方法 Arrays.toString,多维数组调用 Arrays.deepToString 方法。
toString 方法是一种非常有用的调试工具。在标准类库中,许多类都定义了 toString方法, 以便用户能够获得一些有关对象状态的必要信息。强烈建议为自定义的每一个类增加 toString 方法。
到这里基本把 Object 类的重要方法介绍了,后续有新东西会继续添加。
程序清单:
1 package com.song; 2 3 import java.time.LocalDate; 4 import java.util.Objects; 5 6 public class Employee { 7 private String name; 8 private double salary; 9 private LocalDate hireDay; 10 11 public Employee() { 12 } 13 14 public Employee(String name, double salary, int year, int month, int day) { 15 this.name = name; 16 this.salary = salary; 17 hireDay = LocalDate.of(year, month, day); 18 } 19 20 @Override //覆盖超类 Object 的 equals 方法 21 public boolean equals(Object otherObject) { 22 if (this == otherObject) return true; 23 if (otherObject == null) return false; 24 25 if (getClass() != otherObject.getClass()) return false; 26 // or 27 // if (!(otherObject instanceof Employee)) return false; 28 29 Employee other = (Employee) otherObject; 30 31 /** 32 * 对于这里可以直接用other.name访问私有域解释 33 * 有一个原则是:一个方法可以访问所属类的所有对象的私有数据 34 * 即Employee类的方法可以访问Employee类的任何一个对象的私有域。 35 */ 36 return Objects.equals(name, other.name) 37 && salary == other.salary 38 && Objects.equals(hireDay, other.hireDay); 39 } 40 41 @Override //组合多个散列值,如果存在数组类型的域,使用静态的Arrays.hashCode方法计算一个散列码 42 public int hashCode() { 43 return Objects.hash(name, salary, hireDay); 44 } 45 46 @Override 47 public String toString() { 48 return getClass().getName() 49 + "[name=" + name 50 + ",salary=" + salary 51 + ",hireDay=" + hireDay 52 + "]"; 53 } 54 55 public static void main(String[] args) { 56 Employee empOne, empTwo = null; 57 58 empOne = new Employee("song", 10, 2019, 5, 25); 59 System.out.println("empOne equals empTwo:" + empOne.equals(empTwo)); 60 61 empTwo = new Employee("song", 10, 2019, 5, 25); 62 System.out.println("empOne equals empTwo:" + empOne.equals(empTwo)); 63 64 System.out.println(empOne.hashCode()); 65 System.out.println(empTwo.hashCode()); 66 67 System.out.println(empOne); 68 System.out.println(empOne.toString()); 69 System.out.println(empTwo.toString()); 70 71 Manager managerOne = new Manager("song",10,2019,5,25); 72 Manager managerTwo = new Manager("wang",100,2019,5,28); 73 System.out.println("empOne equals managerOne:" + empOne.equals(managerOne)); 74 System.out.println("managerOne equals managerTwo:" + managerOne.equals(managerTwo)); 75 76 Manager managerThree = new Manager("song",10,2019,5,25); 77 System.out.println("managerOne equals managerThree:" + managerOne.equals(managerThree)); 78 79 System.out.println(managerOne.hashCode()); 80 System.out.println(managerTwo.hashCode()); 81 82 System.out.println(managerOne); 83 System.out.println(managerOne.toString()); 84 System.out.println(managerTwo.toString()); 85 System.out.println(System.out); 86 } 87 88 }
1 package com.song; 2 3 public class Manager extends Employee { 4 private double bonus; 5 6 public Manager(){ 7 } 8 9 public Manager(String name, double salary, int year, int month, int day) { 10 super(name, salary, year, month, day); 11 bonus = 0; 12 } 13 14 @Override 15 public boolean equals(Object otherObject) { 16 if (!super.equals(otherObject)) 17 return false; 18 Manager other = (Manager) otherObject; 19 return bonus == other.bonus; 20 } 21 22 @Override 23 public int hashCode() { 24 return super.hashCode() + 17 * new Double(bonus).hashCode(); 25 } 26 27 @Override 28 public String toString() { 29 return super.toString() + "[bonus=" + bonus + "]"; 30 } 31 }
运行结果:
empOne equals empTwo:false empOne equals empTwo:true -1893166707 -1893166707 com.song.Employee[name=song,salary=10.0,hireDay=2019-05-25] com.song.Employee[name=song,salary=10.0,hireDay=2019-05-25] com.song.Employee[name=song,salary=10.0,hireDay=2019-05-25] empOne equals managerOne:false managerOne equals managerTwo:false managerOne equals managerThree:true -1893166707 -1683903746 com.song.Manager[name=song,salary=10.0,hireDay=2019-05-25][bonus=0.0] com.song.Manager[name=song,salary=10.0,hireDay=2019-05-25][bonus=0.0] com.song.Manager[name=wang,salary=100.0,hireDay=2019-05-28][bonus=0.0] java.io.PrintStream@154617c Process finished with exit code 0
至于结果是两个不同类的对象生成的散列码是相同的这个情况,是否违背了上面说的 equals 与 hashCode 的定义必须一致的原则,下次再讨论。