使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。
day030 Object超类(hashCode方法、toString方法)
1.hashCode方法
散列码(hashcode)是由对象导出的一个整型值。散列码是没有规律的。如果x和y是两个不同的对象,x.hashCode()与y.hashCode()基本上不会相同。下面的表列出了几个通过调用String类的hashCode方法得到的散列码。
String类使用下列算法计算散列码:
int hash = 0;
for (int i = 0; i < length();i++)
hash = 31 * hash + charAt(i);
由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。来看下面这个例子。
String s = "Ok";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode() + " " + sb.hashCode());
String t = new String("Ok");
StringBuilder tb = new StringBuilder(t);
System.out.println(t.hashCode() + ""+ tb.hashCode());
请注意,字符串s与t拥有相同的散列码,这是因为字符串的散列码是由内容导出的。而字符串缓冲sb与tb却有着不同的散列码,这是因为在StringBuffer类中没有定义hashCode方法,它的散列码是由Object类的默认hashCode方法导出的对象存储地址。
如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插人到散列表中。
hashCode方法应该返回一个整型数值(也可以是负数),并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。
例如,下面是Employee类的hashCode方法。
public class Employee
{
public int hashCode()
{
return 7 * name.hashCode0 + 11* new Double(salary).hashCode0 + 13 * hireDay.hashCode();
}
...
}
不过,还可以做得更好。首先,最好使用null安全的方法Objects.hashCode。如果其参数为null,这个方法会返回0,否则返回对参数调用hashCode的结果。
另外,使用静态方法Double.hashCode来避免创建Double对象:
public int hashCode()
{
return 7 * Objects.hashCode(name) + 11* Double.hashCode(salary) + 13 * Objects.hashCode(hireDay);
}
还有更好的做法,需要组合多个散列值时,可以调用ObjeCtS.hash并提供多个参数。这个方法会对各个参数调用Objects.hashCode,并组合这些散列值。这样Employee.hashCode方法可以简单地写为:
public inthashCode()
{
return Objects,hash(name, salary, hireDay);
}
Equals与hashCode的定义必须一致:如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。例如,如果用定义的Employee.equals比较雇员的ID,那么hashCode方法就需要散列ID,而不是雇员的姓名或存储地址。
2.toString方法
在Object中还有一个重要的方法,就是toString方法,它用于返回表示对象值的字符串。下面是一个典型的例子。Point类的toString方法将返回下面这样的字符串:
java.awt.Point[x=10,y=20]
绝大多数(但不是全部)的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值。下面是Employee类中的toString方法的实现:
public String toString()
{
return"Employee[name="+name+",salary:"+salary+",hireDay="+hireDay+"]";
}
实际上,还可以设计得更好一些。最好通过调用getClaSS().getName()获得类名的字符串,而不要将类名硬加到toString方法中。
public String toString()
{
return getClass().getName()+"[name=" + name +",salary=" + salary + ",hireDay=" + hireDay + "]";
}
toString方法也可以供子类调用。
当然,设计子类的程序员也应该定义自己的toString方法,并将子类域的描述添加进去。如果超类使用了getClass().getName(),那么子类只要调用super.toString()就可以了。例如,下面是Manager类中的toString方法:
public class Manager extends Employee
{
...
public String toString()
{
return super.toString() + "[bonus=" + bonus +"]";
}
}
现在,Manager对象将打印输出如下所示的内容:
Manager[name=...,salary=...,hireDay=...][bonus=...]
随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符“+”连接起来,Java编译就会自动地调用toString方法,以便获得这个对象的字符串描述。例如,
Point p = new Point(10, 20);
String message= "The current position is " +p;
//automatically invokes p.toString()
在调用x.toString()的地方可以用""+x替代。这条语句将一个空串与x的字符串表示相连接。这里的x就是x.toString()。与toString不同的是,如果x是基本类型,这条语句照样能够执行。
toString方法是一种非常有用的调试工具。在标准类库中,许多类都定义了toString方法,以便用户能够获得一些有关对象状态的必要信息。像下面这样显示调试信息非常有益:
System.out.println("Currentposition="+position);
下面的程序实现了Employee类和Manager类equals、hashCode和toString方法。
//package equals;
/**
*This program demostraters the equals method.
*@author zzehao
*/
public class EqualsTest
{
public static void main(String[] args)
{
Employee alice1 = new Employee("Alice Adams",75000,1987,12,15);
Employee alice2 = alice1;
Employee alice3 = new Employee("Alice Adams",75000,1987,12,15);
Employee bob = new Employee("Bob Brandson",50000,1989,10,1);
System.out.println("alice1 == alice2: " +(alice1 == alice2));
System.out.println("alice1 == alice3: " +(alice1 == alice3));
System.out.println("alice1.equals(alice3): " +alice1.equals(alice3));
System.out.println("alice1.equals(bob): " +alice1.equals(bob));
System.out.println("bob.toString(): "+bob);
Manager carl = new Manager("Carl Cracker",80000,1987,12,15);
Manager boss = new Manager("Carl Cracker",80000,1987,12,15);
boss.setBonus(5000);
System.out.println("boss.toString(): "+boss);
System.out.println("carl.equals(boss): "+carl.equals(boss));
System.out.println("alice1.hashCode(): "+alice1.hashCode());
System.out.println("alice3.hashCode(): "+alice3.hashCode());
System.out.println("bob.hashCode(): "+bob.hashCode());
System.out.println("carl.hashCode(): "+carl.hashCode());
}
}
//package equals;
/**
*@author zzehao
*/
import java.time.*;
import java.util.Objects;
public class Employee
{
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String name,double salary,int year,int month,int day)
{
this.name = name;
this.salary = salary;
hireDay = LocalDate.of(year,month,day);
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public LocalDate getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public boolean equals(Object otherObject)
{
//a quik test to see if the objects are identical
if(this == otherObject)
return true;
//must return false if the explicit parameter is null
if(otherObject == null)
return false;
//if the classes don't match,they can't be equal
if(getClass() != otherObject.getClass())
return false;
//now we know otherObject is a non-null Employee
Employee other = (Employee)otherObject;
//test whether the fields have identical values
return Objects.equals(name,other.name)&&salary == other.salary&&Objects.equals(hireDay,other.hireDay);
}
public int hashCode()
{
return Objects.hash(name,salary,hireDay);
}
public String toString()
{
return getClass().getName()+"[name=" + name +",salary=" + salary + ",hireDay=" + hireDay + "]";
}
}
//package equals;
/**
*@author zzehao
*/
public class Manager extends Employee
{
private double bonus;
public Manager(String name,double salary,int year,int month,int day)
{
super(name,salary,year,month,day);
bonus = 0;
}
public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public void setBonus(double b)
{
this.bonus = bonus;
}
public boolean equals(Object otherObject)
{
if(!super.equals(otherObject))
return false;
Manager other = (Manager)otherObject;
//supper.equals checked that this and other belong to the same class
return bonus == other.bonus;
}
public int hashCode()
{
return super.hashCode() + 17 * new Double(bonus).hashCode();
}
public String toString()
{
return super.toString() + "[bonus=" + bonus +"]";
}
}
运行的结果: