java0-Object类的几个方法:equals, hashCode, toString

1,equals方法

  • Java语言规范要求equals方法具有下面的特性:
    • 1)自反性:对于任何非空引用x, x.equals(x)应该返回true。
    • 2)对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true。
    • 3)传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true, x.equals(z)也应该返回true。
    • 4)一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。
    • 5)对于任意非空引用x, x.equals(null)应该返回false。
  • 然而,就对称性来说,当参数不属于同一个类的时候需要仔细地思考一下。
    • 如果equals方法中使用instanceof进行判断,那父类对象.equals(子类对象)会返回true。
    • 但是子类对象.equals(父类对象)如果子类对象的equals方法不加注意,就会返回false或者抛出异常,从而破坏对称性。
      • 上述情形如果要也能返回true的话,会使得子类受到了束缚。这个类的equals方法必须能够用自己与任何一个父类对象进行比较,而不考虑子类拥有的那部分特有信息!
  • 可以从两个截然不同的情况看以上问题:
    • 如果子类能够拥有自己的相等概念,则对称性需求将强制采用getClass进行检测。
    • 如果由超类决定相等的概念,那么就可以使用instanceof进行检测,这样可以在不同子类的对象之间进行相等的比较(并且此时超类中的equals方法最好设置为final)。
  • 编写equals方法的建议
    • 1,显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量。应为是要覆盖Object类的equals方法,形参的类型要一致。
      @Override
      public boolean equals(Object 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比较对象域。如果所有的域都匹配,就返回true;否则返回false。
      return field1==other.field1 && Object.equals(field2, other.field2) &&
      
    • 7,如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。
      • 在子类中定义equals方法时,首先调用超类的equals。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域。
      @Override
      public boolean equals(Object otherObject){
        if(!super.equals(otherObject)){
          return false;
        }
        ClassName other = (ClassName) otherObject;
        return //比较子类中的特有的实例域
      } 
      
  • equals和==的区别
    • == 对于基本类型:比较的是值是否相同;对于引用类型:比较的是引用是否相同(地址,或者说是否是同一个对象)。
    • equals不重写就等价于 == ,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较。
  • eqauls方法和hashCode方法是这样规定的:
    • 1、相等(相同)的对象必须具有相等的哈希码(或者散列码)。
    • 2、如果两个对象的hashCode相同,它们并不一定相同。
    • 如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。
abstract class people{
    private String name;
    public people(String name){
        this.name = name;
    }
    public abstract String getDescription();
    public String getName(){
        return this.name;
    }
}

class people2 extends people{
    public people2(String name){
        super(name);
    }
    @Override
    public String getDescription(){
        return getName();
    }
    public String getName(){
        return super.getName();
    }
}

class Student extends people{
    private String university;
    public Student(String name, String university){
        super(name);
        this.university=university;
    }
    @Override
    public String getDescription(){
        return super.getName()+" "+this.university;
    }
    @Override
    public boolean equals(Object otherObject){
        System.out.println("同一个对象,比较到这里");
        if(this==otherObject){
            return true;
        }
        System.out.println("null对象,比较到这里");
        if(otherObject==null){
            return false;
        }
        /*
        if(getClass()!=otherObject.getClass()){
            return false;
        }
         */
        System.out.println("非子类对象,比较到这里");
        if(!(otherObject instanceof Student)){
            return false;
        }
        Student other = (Student) otherObject;
        System.out.println("不同对象,比较到这里");
        return this.getName().equals(other.getName()) && this.university.equals(((Student) otherObject).university);
    }
}

public class test11 {
    static Student st = new Student("张三", "北大");

    public static void main(String[] args) {
        System.out.println(st.getDescription());
        Object a = new Object();
        System.out.println("====================");
        System.out.println(st.equals(new Student("张三", "北大")));
        System.out.println("====================");
        System.out.println(st.equals(st));
        System.out.println("====================");
        System.out.println(st.equals(null));
        System.out.println("====================");
        System.out.println(st.equals(new people2("张三")));
    }
}

2,hashCode方法

  • 散列码(hash code)是由对象导出的一个整型值,散列码是没有规律的,可以认为是对象的摘要信息。
  • 由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。(String重写了hashCode方法)
  • 如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。
  • hashCode方法应该返回一个整型数值(也可以是负数),并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。
  • Equals与hashCode的定义必须一致:如果x.equals(y)返回true,那么x.hashCode( )就必须与y.hashCode( )具有相同的值。
  • 注意:
    • 1,在计算hash值时,最好使用null安全的方法Objects.hashCode。如果其参数为null,这个方法会返回0,否则返回对参数调用hashCode的结果。比如下面这个例子:
      import java.util.Objects;
      
      public class test13 {
          public static void main(String[] args) {
              String data = null;
              //System.out.println(data.hashCode());
              System.out.println(Objects.hashCode(data));
          }
      }
      
    • 2,应使用类的静态方法计算hash值,而避免创建对象。如下面这个例子:
      public class test13 {
          public static void main(String[] args) {
              double data = 3.1415;
              System.out.println(Double.valueOf(data).hashCode());
              System.out.println(Double.hashCode(data));
          }
      }
      
    • 3,需要组合多个散列值时,可以调用Objects.hash并提供多个参数。这个方法会对各个参数调用Objects.hashCode,并组合这些散列值,如下面这个例子:
      import java.util.Objects;
      
      public class test13 {
          public static void main(String[] args) {
              String data1 = null;
              double data2 = 3.1415;
              System.out.println(Objects.hashCode(data1)+Double.hashCode(data2));
              System.out.println(Objects.hash(data1, data2));
          }
      }
      

3,toString方法

  • 绝大多数(但不是全部)的toString方法都应遵循这样的格式:类的名字(通过调用getClass().getName( )获得类名的字符串),随后是一对方括号括起来的域值,这样设计的toString方法也可以供子类调用。
    • 如果超类使用了getClass().getName( ),那么子类只要调用super.toString( )就可以了,当然设计子类的程序员也应该定义自己的toString方法,并将子类域的描述添加进去。
  • 例子:

  • 随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符“+”连接起来,Java编译就会自动地调用toString方法,以便获得这个对象的字符串描述。
    • 在调用x.toString( )的地方可以用""+x替代。这条语句将一个空串与x的字符串表示相连接。这里的x就是x.toString( )。与toString不同的是,如果x是基本类型,这条语句照样能够执行。
    • println方法也会直接地调用x.toString( ),并打印输出得到的字符串。Object类定义了toString方法,用来打印输出对象所属的类名和散列码。
posted @ 2022-05-18 16:47  tensor_zhang  阅读(50)  评论(0编辑  收藏  举报