128【Java学习笔记(一百二十八)】之Object类和包装类

文章目录

    • 本文章由公号【开发小鸽】发布!欢迎关注!!!
    • 一、Object类
      • (一)定义
      • (二)类中方法
        • 1.equals(Object obj)
        • 2.hashCode()
          • (1)hashCode方法定义
          • (2)hashCode与equals方法的联系
          • (3)为什么重写equals方法就必须重写hashCode方法
          • (4)hashCode造成的内存泄露问题
          • (5)JVM在GC后hashCode值是否变化
        • 3.toString()
        • 4.finalize()
    • 二、包装类
      • (一)定义
      • (二)装箱与拆箱

本文章由公号【开发小鸽】发布!欢迎关注!!!


老规矩–妹妹镇楼:

一、Object类

(一)定义

       Object类是所有Java类的根父类,如果在任意类的声明中没有通过extends指明继承的父类,则该类的默认父类就是java.lang.Object类,下面详细地介绍Object类的内容。

(二)类中方法

       由于Object类是所有类的默认父类,则该类中的相关方法则会被子类一一继承,如果子类不重写这些方法则会默认使用这些方法。下面介绍一下Object类中的相关方法:

1.equals(Object obj)

       eqauls方法在面试中是一个高频的考点,该方法用于不同对象的比较,Object类中原始的equals方法如下所示:

public boolean equals(Object obj) {
    return (this == obj);
}

       可以看到的是,原始的equals方法中仅仅是通过“==”号来进行比较,这个符号用于比较两个值是否相等,对于基本数据类型和引用类型意义不同。对于基本数据类型,只要两个变量的值相等,则为true;对于引用类型,由于引用的是内存地址,只有引用的地址是相同的才会返回true。

       同时,需要注意的是使用“==”符号进行比较时,符号两边的数据类型必须兼容,否则会编译错误。如基础数据类型之间相互比较是可行的,但是如果将基础数据类型和对象进行比较则会出现编译错误

       因此,在定义一个类时,可以重写Object类中的equals()方法自定义代码逻辑用来比较两个对象的内容是否相等,不仅仅是比较内存地址,还需要比较对象中的内容。如String类中首先比较地址,再比较String中的字符内容是否相同,但要注意的是String是有String常量的,所谓的String常量是直接使用“ ”符号定义的,两个内容相同的String常量使用“”进行比较范返回的是true,而两个String常量使用“”进行比较则返回false;对于Date类来说,是比较两个时间是否一致。

       因此,在通常情况下重写类的equals()方法时,采用如下的步骤

  1. 先通过“==”符号比较两个对象的引用内存地址是否相同,即是否是同一个对象;
  2. 再通过instanceof检查equals()方法中传入的参数是否可以强制转换为指定的类型;
  3. 若上述传参可以转换为指定的类型,进而比较每个对象中的属性是否相等;
  4. 若存在不相等的属性则返回false,否则返回true;

2.hashCode()
(1)hashCode方法定义

       Object类中的hashCode方法用来返回对象的哈希值,多用于HashMap、HashSet等集合之中的数据查找,来提高对象查找的效率。从Object类的源码中可以看出,hashCode方法是一个native本地方法,返回一个int整数:

public native int hashCode();

       而每个对象都会有一个hashCode返回值,这个int返回值一定会产生冲突的问题,从该本地方法的代码中可以看出,有多种生成hash值的策略,返回随机数、根据对象内存地址计算、输出内存地址、产生伪随机数等等。

(2)hashCode与equals方法的联系

       由上面的内容可知,hashCode()方法用于产生所谓的对象的hash值来标识对象,但是可能会产生冲突,因此还需要equals()方法来协助继续比较两个对象才可以得出最终的结论。通常情况下,hashCode()方法能够直接判定出两个对象是否不同,若出现hash冲突的问题,则需要再次通过equals()方法判断,因此hashCode()方法和equals()方法联系非常紧密,需要遵守如下的规则

  1. 如果两个对象的equals方法的返回值相同,则hashCode方法的返回值也必须是相同的;
  2. 重写equals方法时就必须重写hashCode方法;
  3. 两个对象的hashCode结果相同,并不能代表两个对象的equals结果为true,只能够说明这两个对象处于同一个散列存储结构中;

(3)为什么重写equals方法就必须重写hashCode方法

       根据前文所述,hashCode方法只负责计算得出一个int的hash值,如果两个对象的hash值发生了冲突,则需要通过equals方法中的逻辑继续比较。如果重写了equals方法,但是没有重写hashCode方法,则该类就继承了Object类中的hashCode方法,即使重写equals方法中的逻辑判断出两个对象是相等的,由于调用的是Object类中的hashCode方法,无法确保两个对象的hash值相同,因此在进行对象比较的时候,可能由于hash值不等被直接pass了,都不需要进行equals方法的比较,这样重写equals方法就失去了意义


(4)hashCode造成的内存泄露问题

       所谓的内存泄露问题指的是,当系统以为通过某些指令删除了指定内存地址上的对象时,这些对象事实上并没有被删除,导致这些内存地址无法被回收利用。

       如HashSet的remove方法中,首先通过对象的hashCode值来比较对象并进行删除。若在该自定义类中重写了hashCode方法,并将对象的属性值参与到HashCode的计算之中,导致对象的hashCode发生变化,这样在remove对象时就无法准确找到原有的对象,导致对象删除失败内存泄露。


(5)JVM在GC后hashCode值是否变化

       根据前文所述,Object类中的hashCode结果有多种计算策略,其中包括内存地址的计算方式,而Java的GC策略中,有多种策略会在GC后移动存活对象的内存地址,那么对象的hashCode是否会在GC后发生变化呢?

       在JVM中有一个存储结构称为对象头,对象头中存储着hashCode的值,在hashCode方法未被调用时,存储的值为0,当hashCode首次计算后则会存储着hashCode的值,后续再次调用时直接从对象头中取出hashCode即可。因此,即使发生了GC,对象的内存地址改变了也不会影响hashCode的值。如在GC发生前调用过hashCode方法,则直接从对象头中取出值;若在GC发生后才调用hashCode方法,即使内存地址发生了改变,只会产生一个新的hashCode值,由于之前并没有生成后hashCode值,则不会存在hashCode的前后差异。


3.toString()

       Object类中的toString()方法,返回的是String类型,返回的内容是该类的类型和它的引用地址,如下所示:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

       在一个类中可以自定义toString()方法,来自定义 输出内容。如String类中就重写了toString()方法,返回的是String字符串的值;如Integer包装类的toString()方法,也是将int数值包装成String对象返回,如下所示:

public static String toString(int i) {
    if (i == Integer.MIN_VALUE)
        return "-2147483648";
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}

4.finalize()

       finalize()方法在该对象被垃圾回收之前调用,当我们显式地通过System.gc()进行垃圾回收时,会通知被回收的对象调用finalize()方法,因此子类中可以重写该方法,在垃圾回收前进行日志的记录。


二、包装类

(一)定义

       Java中强调万物皆对象,但Java中有八种基本数据类型,这些数据类型并非属于类,只能作为数据进行计算,没有类的特点。在很多情况下无法与其他类进行交互,因此需要将这些数据类型包装为类,才能够与其他类进行交互。

下面是八种基本数据类型和对应的包装类:

128【Java学习笔记(一百二十八)】之Object类和包装类_第1张图片

(二)装箱与拆箱

       如上文所述,所谓的包装类就是将基本数据类型包装在类中,因此也就需要将基本数据类型转换成包装类(装箱),也需要将包装类转换成基本数据类型(拆箱)

       装箱,可以通过包装类的构造方法以及静态方法valueOf():

//装箱
Integer t = new Integer(500);
Integer integer = Integer.valueOf(500);

       拆箱,可以通过parseXXX()方法或xxxValue()方法取出基本数据类型:

//拆箱
int i = t.intValue();
int i1 = Integer.parseInt("500");

       从JDK5之后,Java支持自动装箱、拆箱,因此在装箱与拆箱的写法上可以更加自由:

//自动装箱、拆箱
Integer t2 = 500;
int i2 = t2;

       基本数据类型 -> String类,可通过String.valueOf()来生成String对象,也可以直接通过 基本数据类型+ ""的方式生成String对象:

//基本数据类型 ——> String类
String s = String.valueOf(123);
String s2 = 123 + "";

       String类 -> 基本数据类型,可通过包装类的parseXXX()来获取基本数据类型:

//String类 -> 基本数据类型
int i3 = Integer.parseInt(s);

       包装类 -> String类,可通过包装类对象的toString()方法生成String对象:

//包装类 -> String类
String s1 = t2.toString();

       String类 -> 包装类,可通过该包装类的字符串形参的构造器生成,同样的包装类的valueOf静态方法也可以生成对应的包装类对象:

//String类 -> 包装类
Float aFloat = new Float("1.2F");
Integer integer1 = Integer.valueOf("1.2F");

因此,基本数据类型、包装类与String类之间可以通过装箱、拆箱不断转换,如下所示:

128【Java学习笔记(一百二十八)】之Object类和包装类_第2张图片

你可能感兴趣的:(Java学习笔记,#,JavaSE,java,学习)