47_Object类(重点)

Object类(重点)

常用的包(熟悉)

包的名称和功能

  • java.lang包 - 该包是Java语言的核心包,并且该包中的所有内容由Java虚拟机自动导入

    如:System类、String类、…

  • java.util包 - 该包是Java语言的工具包,里面提供了大量工具类以及集合类等。

    如:Scanner类、Random类、List集合、…

  • java.io包 - 该包是Java语言中的输入输出包,里面提供了大量读写文件相关的类等。

    如:FileInputStream类、FileOutputStream类、…

  • java.net包 - 该包是Java语言中的网络包,里面提供了大量网络编程相关的类等。

    如:ServerSocket类、Socket类、…

  • java.sql 包 - 该包是Java语言中的数据包,里面提供了大量操作数据库的类和接口等。

    如:DriverManager类、Connection接口、…

  • … …

  • Java程序员在编程时可以使用大量类库,因此Java编程时需要记的很多,对编程能力本身要求不是

    特别的高。

Object类的概述(重点

  1. 基本概念

    • java.lang.Object类是Java语言中类层次结构的根类(父类),也就是说任何一个类都是该类的直接或者间接子类。

    • 如果定义一个Java类时没有使用extends关键字声明其父类,则其父类为 java.lang.Object 类。

    • Object类定义了“对象”的基本行为, 被子类默认继承。

  2. 常用方法

    方法声明 													 功能介绍
    Object() 												使用无参方式构造对象
    
    boolean equals(Object obj)							用于判断调用对象是否与参数对象相等。该方法
    												  默认比较两个对象的地址是否相等,与 == 运算
    												  符的结果一致若希望比较两个对象的内容,则需
    												  要重写该方法。若该方法被重写后,则应该重写
    												  hashCode方法来保证结果的一致性。
    
    int hashCode()									   用于获取调用对象的哈希码值(内存地址的编号)
    												  若两个对象调用equals方法相等,则各自调用
    												  该方法的结果必须相同若两个调用对象equals
    												  方法不相等,则各自调用该方法的结果应该不相
    												  同。为了使得该方法与equals方法保持一致,
    												  需要重写该方法。
    
    String toString()								    用于获取调用对象的字符串形式该方法默认返回
    												   的字符串为:包名.类名@哈希码值的十六进制
    												   为了返回更有意义的数据,需要重写该方法使
    												   用print或println打印引用或字符串拼接引用
    												   都会自动调用该方法
    
    Class<?> getClass() 						用于返回调用对象执行时的Class实例,反射机制使用
    
  3. 案例题目

    • 编程实现Student类的封装,特征:学号(id)和姓名,要求提供打印所有特征的方法。

    • 编程实现StudentTest类,在main方法中使用有参方式构造两个Student类型的对象并打印特征。

  4. 题目扩展

    • 如何实现以姓名作为基准判断两个对象是否相等?以及以学号和姓名同时作为基准判断两个对象是否相等?

Object类中的 boolean equals(Object obj) 方法

  • equals方法的源码:

    public boolean equals(Object obj) {
        return (this == obj);	// this代表当前正在调用的对象
    }
    
  • equals方法用于判断调用对象是否与参数对象相等。

    该方法默认比较两个对象的地址是否相等,与 == 运算符的结果一致

    若希望比较两个对象的内容,则需要重写该方法。

    若该方法被重写后,则应该重写hashCode方法来保证结果的一致性

  • 重写equals方法

    Student类-----------------------------------------
    package com.huang.task01;
    
    /**
     * @author hhc19
     * @date 2022/1/2 20:02
     * @description
     */
    // 只要是公有的,不是私有的,也不是构造方法就意味着可以继承下来
    // Student 没有指明其父类时,相当于它默认继承了Object类,而它一旦继承了Object 类
    public class Student extends Object{ // 只要是类,最终父类都是Object类
    
        private int id; // 用于描述学号的成员变量
        private String name ; // 用于描述姓名的成员变量
    
        public Student() {
        }
    
        public Student(int id, String name) { // 构造方法中调用set方法可以进行合理值判断
            setId(id);
            setName(name);
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            if (id > 0) {
                this.id = id;
            } else {
                System.out.println("学号不合理哦!!!");
            }
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * 为了比较两个对象的内容,就是学号信息需要重写该方法
         * 重写方法的原因:父类中继承下来的版本不足以满足子类的要求
         * 重写继承自Object类的equals方法
         */
        // Student this = s1;
        // Object obj = s2; 多态,父类类型的引用指向子类类型的对象时,父类的引用不可以直接调用子类独有的方法,要想调用,得先强转
        @Override
        public boolean equals(Object obj) {
            // return this.getId() == ((Student)obj).getId();
            // 判断 obj 指向的对象是否为Student类型的对象,若是则条件成立,否则条件不成立
            if (obj instanceof Student) { // 只要做强转就需要判断一下,否则有可能出现类型转换异常
                Student ts = (Student)obj;
                return this.getId() == ts.getId();
            }
            // 否则类型不一致没有可比性,则内容一定不相等(相同)
            return false;
        }
    }
    
    
    StudentTest------------------------------------------------
    package com.huang.task01;
    
    /**
     * @author hhc19
     * @date 2022/1/2 20:08
     * @description
     */
    public class StudentTest {
    
        public static void main(String[] args) {
    
            // 1、使用有参方式构造Student类型的两个对象并判断是否相等
            // 在现实生活中,比较两个学生是否相同只需要比较学号是否相同即可
            Student s1 = new Student(1001, "zhangfei");
            // Student s2 = new Student(1002, "guanyu");
            Student s2 = new Student(1001, "guanyu");
            // Student s2 = s1; // s2 和 s1 都指向了同一个对象,地址相同了
            // 下面调用类从Object类继承下来的equals方法,该方法默认比较两个对象的地址,相当于==,可以查看源码验证
            // boolean b1 = s1.equals(s2); // 形成了多态 此处调用的是Object类中的equals方法
            // 当Student类中重写equals方法后,则调用重写以后的版本, 比较内容
            boolean b1 = s1.equals(s2); // false id不相同
            System.out.println("b1 = " + b1); // false true true
            System.out.println(s1 == s2); // false 同样也是比较地址 true
        }
    }
    
    
  • equals方法的重写优化

    • 执行流程:要理程序执行流程,我们得先找main方法,main方法中如果调用方法就是整个跳过去,执行完了再跳回来。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XSTFplm9-1641477097233)(D:\拉勾学习\JavaBasics笔记\第一阶段_JavaSE\拉钩官方文件\模块三Java核心类库(上)\01任务一 常用类的概述和使用\02_图片\01 equals方法的原理分析.png)]

    • equals方法的特性:equals方法再非null对象引用上实现等价关系(null对象调用方法会导致空指针异常):

      • 自反性 :对于任何非空的参考值xx.equals(x)应该返回true
      • 对称性 :对于任何非空引用值xyx.equals(y)应该返回true当且仅当y.equals(x)回报true
      • 传递性 :对于任何非空引用值xyz ,如果x.equals(y)回报truey.equals(z)回报true ,然后x.equals(z)应该返回true
      • 一致性 :对于任何非空引用值xy ,多次调用x.equals(y)始终返回true或始终返回false ,前提是未修改对象上的equals比较中使用的信息。
      • 对于任何非空的参考值xx.equals(null)应该返回false
    • Objectequals方法实现了对象上最具区别的可能等价关系; 也就是说,对于任何非空引用值xy ,当且仅当xy引用同一对象( x == y具有值true )时,此方法返回true

    • 请注意,通常需要在重写此方法时覆盖hashCode方法,以便维护hashCode方法的常规协定,该方法声明相等对象必须具有相等的哈希代码

  • equals方法暂时性的完整实现

    package com.huang.task01;
    
    /**
     * @author hhc19
     * @date 2022/1/2 20:02
     * @description
     */
    // 只要是公有的,不是私有的,也不是构造方法就意味着可以继承下来
    // Student 没有指明其父类时,相当于它默认继承了Object类,而它一旦继承了Object 类
    public class Student extends Object{ // 只要是类,最终父类都是Object类
    
        private int id; // 用于描述学号的成员变量
        private String name ; // 用于描述姓名的成员变量
    
        public Student() {
        }
    
        public Student(int id, String name) { // 构造方法中调用set方法可以进行合理值判断
            setId(id);
            setName(name);
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            if (id > 0) {
                this.id = id;
            } else {
                System.out.println("学号不合理哦!!!");
            }
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * 为了比较两个对象的内容,就是学号信息需要重写该方法
         * 重写方法的原因:父类中继承下来的版本不足以满足子类的要求
         * 重写继承自Object类的equals方法
         */
        // Student this = s1;
        // Object obj = s2; 多态,父类类型的引用指向子类类型的对象时,父类的引用不可以直接调用子类独有的方法,要想调用,得先强转
        @Override
        public boolean equals(Object obj) {
            // return this.getId() == ((Student)obj).getId();
            // 特殊处理2:当调用对象和参数对象指向同一个对象时,则内容一定相同
            if (this == obj) return true;
    
            // 特殊处理1:当调用对象不为空而参数对象为空时,则内容一定不相同。
            // 代码能执行到这里说明调用对象一定不为空,否则,代码执行到调用该方法的时候直接会报空指针异常
            if (null == obj) { // 由于非空性 当块中只有一条语句时,大括号可以省略
                return false;
            }
            // 判断 obj 指向的对象是否为Student类型的对象,若是则条件成立,否则条件不成立
            if (obj instanceof Student) { // 只要做强转就需要判断一下,否则有可能出现类型转换异常
                Student ts = (Student)obj;
                return this.getId() == ts.getId();
            }
            // 否则类型不一致没有可比性,则内容一定不相等(相同)
            return false;
        }
    }
    
    
    
    package com.huang.task01;
    
    /**
     * @author hhc19
     * @date 2022/1/2 20:08
     * @description
     */
    public class StudentTest {
    
        public static void main(String[] args) {
    
            // 1、使用有参方式构造Student类型的两个对象并判断是否相等
            // 在现实生活中,比较两个学生是否相同只需要比较学号是否相同即可
            Student s1 = new Student(1001, "zhangfei");
            // Student s2 = new Student(1002, "guanyu");
            Student s2 = new Student(1001, "guanyu");
            // Student s2 = s1; // s2 和 s1 都指向了同一个对象,地址相同了
            // 下面调用类从Object类继承下来的equals方法,该方法默认比较两个对象的地址,相当于==,可以查看源码验证
            // boolean b1 = s1.equals(s2); // 形成了多态 此处调用的是Object类中的equals方法
            // 当Student类中重写equals方法后,则调用重写以后的版本, 比较内容
            // boolean b1 = s1.equals(s2); // false id不相同
            // Student s3 = null;
            // boolean b1 = s1.equals(s3);
            Student s3 =s1;
            boolean b1 = s1.equals(s3);
            System.out.println("b1 = " + b1); // false true true
            System.out.println(s1 == s2); // false 同样也是比较地址 true
        }
    }
    
    

hashCode方法的重写实现

  • 返回调用对象的哈希码值(暂时理解为内存地址的编号,其他的后面讲集合的时候再说)。 支持此方法的优点是散列表,例如HashMap提供的散列表

  • hashCode的总合同(常规协定)是:

    • 只要在执行Java应用程序期间多次在同一对象上调用它hashCode方法必须始终返回相同的整数前提是不修改对象上的equals比较中使用的信息–>只要调用equals的结果没有改的话,调用hashCode方法的返回值就必须相同,也就是hashCode方法和equals方法要保持一致性。 从应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致
    • 如果两个对象根据equals(Object)方法相等(说明两个对象的内存地址相同),则对两个对象中的每个对象调用hashCode方法必须生成相同的整数结果(内存地址编号)。
    • 不是必需的:如果两个对象根据不相等equals(java.lang.Object)方法,然后调用hashCode在各两个对象的方法必须产生不同的整数结果。 但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能

    尽管合理可行,但是类Object定义的hashCode方法确实为不同的对象返回不同的整数。 (hashCode可能会或可能不会在某个时间点实现为对象的内存地址的某些功能。)

  • 用于获取调用对象的哈希码值(内存地址的编号)。

    若两个对象调用equals方法相等,则各自调用该方法的结果必须相同

    若两个调用对象equals方法不相等,则各自调用该方法的结果应该不相同

    为了使得该方法与equals方法保持一致,需要重写该方法

    // 下面调用从Object类中继承下来的hashCode方法,获取调用对象的哈希码值(暂时理解为内存地址编号即可)
    // 重写了equals方法之后,为了保证equals方法和hashCode方法的一致性,我们必须重写equals方法
    // 当Student类中重写hashCode方法后,则调用重写以后的版本
    int ia = s1.hashCode();
    int ib = s2.hashCode();
    // Java官方的规定:equals相等,hashCode就必须相等;equals不相等,hashCode就必须不相等
    // 此处虽然编译运行没有出错,但是违背了Java官方的常规协定
    System.out.println("ia = " + ia); // ia = 1627960023 重写了hashCode方法之后:1001
    System.out.println("ib = " + ib); // ib = 357863579  重写了hashCode方法之后:1001
    
    
    
    
    /**
    * 为了使得该方法的结果和equals方法的结果保持一致,从而满足Java官方的常规协定,需要重写该方法
    */
    @Override
    public int hashCode() {
        // 此处equals方法本质上是依赖于学号,学号相等,两个对象就像等,学号不相等,两个对象就不相等。
        // 我们此时要想保证equals方法和hashCode方法之间的一致性,只需要让hashCode方法的结果同样也依赖于学号而存在即可。
        // 二者都依赖于学号之后,就保持一致了
        // return getId(); // 返回当前调用对象的学号,这样就一定保持一致了。不再代表内存地址的编号了。这是耍流氓的做法
        // 上方代码虽然不再代表内存地址的编号了,但是满足了Java规定的一致性,且Java官方并没有规定hashCode的结果必须代表内存地址
        final int type = 12; // 一年有12个月,一个月最多31天
        return type*31 + getId(); // 12 和 31 是编的,随便指定的两个值,但是这两个值都是固定死的,意味着hashCode的返回结果还是依赖于学号
        // 上面代码相对来说比较完美:既能保持一致性,而且还能跟直接获取学号加以区分,何乐而不为呢?
        // hashCode方法如果重写,就暂时性的按照上面重写就ok了
    }
    

toString方法的重写实现

  • 返回对象的字符串表示形式。 通常, toString方法返回一个“文本表示”此对象的字符串。 结果应该是简洁但信息丰富的表示,便于人们阅读。 建议所有子类都覆盖此方法

  • ObjecttoString方法返回一个字符串,该字符串由对象为实例的类的名称,符号字符“ @ ”以及对象的哈希码的无符号十六进制表示形式组成。 换句话说,此方法返回一个等于值的字符串:

    getClass().getName() + '@' + Integer.toHexString(hashCode())

  • 功能:用于获取调用对象的字符串形式

    格式:该方法默认返回的字符串为:包名.类名@哈希码值的十六进制

    为了返回更有意义的数据,需要重写该方法

    使用print或println打印引用或字符串拼接引用都会自动调用该方法

    // 下面调用从Object类中继承下来的toString方法,获取调用对象的字符串形式:包名.类名@哈希码值的十六进制
    String str1 = s1.toString();
    // 重写toString方法前的版本:com.huang.task01.Student@55d 这个数据对我们来说没有任何意义
    // 当Student类中重写toString方法后,则调用重写以后的版本:Student[id = 1001, name = zhangfei]
    System.out.println("str1 = " + str1);
    // 现在重写toString方法可以改写为我们之前所写的show方法,即打印对象的所有特征。
    System.out.println(s1); // 当我们打印一个引用变量时,会自动调用toString方法:Student[id = 1001, name = zhangfei]
    String str2 = "hello " + s1; // hello Student[id = 1001, name = zhangfei]
    System.out.println("str2 = " + str2); // 字符串拼接引用变量时也会自动调用toString方法
    
    
    
     /**
       * 为了返回更有意义的字符串数据,则需要重写该方法
       */
    @Override
    public String toString() {
        return "Student[id = " + getId() + ", name = " + getName() + "]"; // 就是一个字符串的拼接
    }
    

Class<?> getClass()方法

  • 用于返回调用对象执行时的Class实例,反射机制使用

案例

  • 如何实现以姓名作为基准判断两个对象是否相等?以及以学号和姓名同时作为基准判断两个对象是否相等?

    Student类-------------------------------------------------------------
        实现以姓名为基准判断两个对象是否相等:
    
    /**
      * 为了比较两个对象的内容,就是学号信息需要重写该方法
      * 重写方法的原因:父类中继承下来的版本不足以满足子类的要求
      * 重写继承自Object类的equals方法
      */
    // Student this = s1;
    // Object obj = s2; 多态,父类类型的引用指向子类类型的对象时,父类的引用不可以直接调用子类独有的方法,要想调用,得先强转
    @Override
    public boolean equals(Object obj) {
        // return this.getId() == ((Student)obj).getId();
        // 特殊处理2:当调用对象和参数对象指向同一个对象时,则内容一定相同
        if (this == obj) return true;
    
        // 特殊处理1:当调用对象不为空而参数对象为空时,则内容一定不相同。
        // 代码能执行到这里说明调用对象一定不为空,否则,代码执行到调用该方法的时候直接会报空指针异常
        if (null == obj) { // 由于非空性 当块中只有一条语句时,大括号可以省略
            return false;
        }
        // 判断 obj 指向的对象是否为Student类型的对象,若是则条件成立,否则条件不成立
        if (obj instanceof Student) { // 只要做强转就需要判断一下,否则有可能出现类型转换异常
            Student ts = (Student)obj;
            // int 是基本数据类型  意味着它在栈区的内存空间中放的就是数据本身,使用 == 可以判断数据是否相同
            // return this.getId() == ts.getId(); 以学号为基准判断两个对象是否相等
    
            // String 是引用数据类型,意味着内存空间中放的是堆区地址,使用 == 是在判断地址是否相同
            // 也就是判断两个对象中姓名字符串的地址是否相同,不够完美,现实中不是比较地址是否相同,而是比较内容是否相同
            // return this.getName().== ts.getName();
            // String 类中的equals方法:将此字符串与指定的对象进行比较(比较的是内容)。
            return this.getName().equals(ts.getName()); // 以姓名作为基准判断两个对象是否相等
        }
        // 否则类型不一致没有可比性,则内容一定不相等(相同)
        return false;
    }
    
    /**
      * 为了使得该方法的结果和equals方法的结果保持一致,从而满足Java官方的常规协定,需要重写该方法
      */
    @Override
    public int hashCode() {
        // 此处equals方法本质上是依赖于学号,学号相等,两个对象就像等,学号不相等,两个对象就不相等。
        // 我们此时要想保证equals方法和hashCode方法之间的一致性,只需要让hashCode方法的结果同样也依赖于学号而存在即可。
        // 二者都依赖于学号之后,就保持一致了
        // return getId(); // 返回当前调用对象的学号,这样就一定保持一致了。不再代表内存地址的编号了。这是耍流氓的做法
        // 上方代码虽然不再代表内存地址的编号了,但是满足了Java规定的一致性,且Java官方并没有规定hashCode的结果必须代表内存地址
        final int type = 12; // 一年有12个月,一个月最多31天
        // return type*31 + getId(); // 12 和 31 是编的,随便指定的两个值,但是这两个值都是固定死的,意味着hashCode的返回结果还是依赖于学号
        // 上面代码相对来说比较完美:既能保持一致性,而且还能跟直接获取学号加以区分,何乐而不为呢?
        // hashCode方法如果重写,就暂时性的按照上面重写就ok了
    
        // return type*31 + getName(); // 类型不匹配
        // 此时我们的equals方法依赖于String类内部的equals方法
        // 此时要想让我们的hashCode方法和equals方法保持一致,我们的hashCode方法就需要依赖于String类内部的hashCode方法
        return type*31 + getName().hashCode();
    }
    
  • String的hashCode方法为什么选31这个质数?首先31是一个质数,不容易哈希冲突(质数哈希冲突频率低,原因去百度),其次Java对31的倍数有优化,转化为乘以32再做减法,而32可以通过左移实现,效率高。

equals方法和hashCode方法的生成

  • 在实际开发中,equals、hashCode、toString这些方法不需要我们手动写。使用快捷键 alt + insert,可以快捷自动生成 equals() and hashCode() 和 toString方法。

  • 笔试题会考到我们手写equals方法和hashCode方法和toString方法,我们要按照之前的代码手写。之后如果是笔试题中考到了我们一律手写,如果是实际开发中我们一律使用快捷键生成。

  • idea中快捷键生成的toString方法:

    @Override
    public String toString() {
        return "Student{" +
            "id=" + id +
            ", name='" + name + '\'' +
        	'}';
    }
    
  • idea快捷键生成 equals() and hashCode() 方法一:

    alt + insert 进入快捷生成面板,然后点击 equals() and hashCode(),然后选择 Template 中的 IntelliJ Default 也就是 Idea 默认
        
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
    
        Student student = (Student) o;
    
        if (id != student.id) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }
    
    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
    
  • idea快捷键生成 equals() and hashCode() 方法二:

    alt + insert 进入快捷生成面板,然后点击 equals() and hashCode(),然后选择 Template 中的 java.util.Objects.equals and hashCode(Java 7+) 也就是 从Java7的版本开始逐步更新
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id &&
            Objects.equals(name, student.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
    
  • idea按住 shift + alt 快捷键并使用 鼠标左键可以同时选中多行、多个位置或区域(可以多次选择)

上一篇:47. 全排列 II


下一篇:47 django之HttpRequest对象