zoukankan      html  css  js  c++  java
  • Object:所有类的超类

    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 }
    View Code 

    运行结果:

    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 }
    View Code

    到这里基本说完了,还有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;
        }
    View Code

      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 }
    Employee.java
     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 }
    Manager.java

    运行结果:

    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 的定义必须一致的原则,下次再讨论。

  • 相关阅读:
    VIM配置
    VSCode配置Import@路径
    Sar
    VIM-Fold折叠
    sysctl
    java8 到 java14新增的特性
    Electron整合VUE
    使用Markfile开发GO程序
    cron 表达式
    java spi
  • 原文地址:https://www.cnblogs.com/songbeyond/p/10931807.html
Copyright © 2011-2022 走看看