Java基础-Java常用类1(包装类 + Object类)

news2025/2/28 15:39:07

本篇文章主要讲解Java的常用类

  • 包装类
  • Object类

希望能对你的复习以及面试有帮助,有错误请指正 , 感谢.

目录

包装类

Object类

Object 类的常见方法有哪些?

对象比较(hashcode和equals方法)

== 和 equals() 的区别

hashCode() 是什么 ?  有什么用?

那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?(2个不相等的对象有可能具有相同的 hashcode吗?)(两个对象的hashCode()相同,则 equals()是否也一定为 true?)

既然Java中默认的hashcode采用的是随机算法返回一个随机数,为什么每次相同对象的hashcode都一样呢 ? 

hashcode与equals结论

hashCode生成方式可以调整吗?

怎么理解对象相等 ? 

hashcode实现原理了解么?

String对象的 hashcode() 是如何设计的 ? 为啥每次都乘以31

如果重写了Object类的hashcode,底层实现是怎么样的 ? 

为什么 JDK 还要同时提供hashcode和equals这两个方法呢?

重写hashcode的原因

重写equals的原因

只重写hashcode 或者 只重写equals方法 可以么 ?

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

谈谈你对java.lang.Object对象中hashCode和equals方法的理解。在什么场景下需要重新实现这两个方法。(hashCode()和equals()方法的重要性体现在什么地方?)

Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()?

对象拷贝(clone方法)

clone方法

如何实现对象克隆?

深拷贝与浅拷贝

Cloneable 接口实现原理?


包装类

  • Java 中的哪几种基本数据类型了解么?它们的默认值和占用空间大小知道不? 说说这八种数据类型对应的包装类型
  • 基本类型和包装类型的区别?

  • int 和 Integer 的区别

  • 为什么要有包装类型 ? 

  • 包装类型的缓存机制了解么?

  • 自动装箱与拆箱了解吗?原理是什么?

  • 在哪些场景下,Java会自动拆装箱呢 ? 
  • 自动拆装箱会带来什么问题呢 ?
  • 遇到过自动拆箱引发的NPE问题么 ?
  • String 怎么转成 Integer 的?原理?

  • Java 中的哪几种基本数据类型了解么?它们的默认值和占用空间大小知道不? 说说这八种数据类型对应的包装类型

Java一共有八种数据类型分别为 byte short int long float double char boolean.

默认值 : byte-0 ,int-0 ,short-0, long-0L, float- 0.0f, double-0.0d char- '\u0000' , boolean-false

占用空间大小 : byte -1字节,short-2字节,int-4字节,long-8字节,float-4字节,double-8字节,char-2字节boolean-不确定.

对应的包装类型 : byte - Byte,int-Integer,short-Short,long-Long,float-Float,double-Double,boolean-Boolean.

  • 基本类型和包装类型的区别? 

默认值 : 基本类型都有自己的默认值,包装类型的默认值都是null

用于泛型 : 基本类型不能用于泛型,包装类可以用于泛型

存储区域 : 如果基本类型用于成员变量那存放在堆中,用于局部变量或者方法参数就会存放在Java虚拟机栈中. 而包装类型是对象类型(引用类型) 存放在堆中

占用空间 : 基本数据类型存放的数值都是在栈中,而包装类型在栈中存储对象的引用并且指向队中的对象, 相比于基本数据类型来说,包装类型要占用更多的内存空间

变量的比较不同 : 对于==来说,如果是基本数据类型比较的是值,如果是引用数据类型(包装类型)比较的是两个变量的地址是否相等,对于equals来说,如果引用类型没有重写equals与==一样,如果重写了比较的是内容-->包装类一般都默认重写了equals,建议引用类型(对象类型)使用equals比较.基本数据类型使用==比较

  • int 和 Integer 的区别

默认值 : int默认值为0,Integer默认值为null

泛型 : int不能用于泛型,Integer是包装类型可以用于泛型

数据类型区别 : int是基本数据类型,Integer是包装类型

存储方式不同 : int类型直接存放在Java虚拟机栈中的局部变量表中.而Integer类型属于对象类型,对象的实例都是存在于堆中的.相比于对象类型,基本类型占用的空间较小

实例化方式不同 : Integer需要实例化,int不需要

变量的比较不同 : 基本类型的比较相等使用==,包装类型使用equals比较的才是内容

  • 为什么要有包装类型 ? 

因为Java是属于面向对象的编程语言,很多地方都需要使用对象而不是基本数据类型,就比如Java集合类中 不允许将基本数据类型放进去,因为集合的容器要求元素是Object类型;

所以为了让基本数据类型也有面向对象特征,就出现了包装类型.相当于对基本数据类型做了一个包装,使其具有对象的性质,并添加了属性和方法,丰富了基本类型的操作.

基本数据类型都有对应的包装类型,两者可以进行互相转化,这个过程叫做拆箱和装箱.

  • 基本类型和包装类怎么进行选择呢 ? 

 阿里巴巴Java开发手册是这么说的

这里注意的是POJO类就是简单Java对象.

所以建议POJO类(Java对象)属性必须使用包装数据类型. 推荐所有的局部变量使用基本数据类型.

原因如下 :

很多业务场景下,对象的某些属性没有赋初始值,我们就希望它是NULL,如果给出赋一个初始值就会出现问题.

假设一个场景 :

我们现在要做一个扣费系统,需要专门调一个外部接口来计算 "费率",使用公式 金额*费率 得出花费的费用,然后根据这个计算结果进行扣费.

如果扣费系统出现了异常,费率就会返回一个默认值,如果是Double类型就返回null,就会直接中断程序不在往下执行,如果是double类型则返回0.0,这样就导致得出计算结果为0后,直接扣费了,这样系统异常就无法感知了

但如果要单独对0进行判断,又会引发出问题,如果允许费率为0的情况下扣费呢 ? 这样系统就无法识别出这个0是正常情况还是异常情况. 

所以使用基本数据类型会让方案越来越复杂,使用包装类定义变量,通过异常可以阻断程序的运行,可以识别出线上问题,如果使用基本数据类型,系统无感知,可能不会报错,认为是无异常.

  • 包装类型的缓存机制了解么?

Java中大部分包装类型都实现了缓存机制

Byte,Short,Integer,Long实现了缓存机制,这四种包装类创建了在[-127,128]范围内的缓存数据.  对于Character 创建了[0,127]范围内的缓存数据. Boolean直接返回True或者false,

而Float,Double则没有缓存机制.

  • 缓存的作用是什么 ? 

缓存的作用就是提高性能和节省内存-通过使用相同的对象引用实现了缓存与重用,

会在第一次使用包装类型的时候需要额外的一定时间来初始化这个缓存,下次如果使用的数字在缓存的范围内,可以直接使用相同的对象,不用在重新创建新的包装类对象了.IntegerCache缓存内部是使用for循环从低到高的创建整数,并把这些整数存到一个整数数组里面

对于Integer类比较特殊,在Java6中可以通过Integer.IntegerCache.high来设置最大值,使得我们可以根据应用程序的实际情况来调整数值提高性能. Byte,Short,Long 缓存范围固定不能改变

  • 那为什么 选择[-128,127]之间这个数值范围呢 ? 

因为这个数值范围是最被广泛使用的.在性能和资源之间做的一个权衡.

  • 自动装箱与拆箱了解吗?原理是什么?

自动装箱 : 将基本数据类型自动转换为对应的包装类

自动拆箱 : 将包装类自动转换为对应的基本数据类型

Integer i = 10;//自动装箱
int n = i;//自动拆箱

自动装箱的原理是使用包装类的valueOf()方法(比如Integer a = Integer.valueOf(10))

自动拆箱的原理是使用包装类的xxxValue()方法(比如 int b= Integer.intValue(a))

我们如果想使用包装类型的话建议使用valueOf因为这样如果用的数字在缓存范围内是直接从缓存中取,否则new是直接创建一个对象,没有利用缓存.没有节省内存.

  • Integer相关面试题
public static void main(String[] args) {
    //对象的比较强烈建议使用equals比较
    Integer in1= new Integer(127);//堆中
    Integer in2 = new Integer(127);//堆中
    System.out.println(in1==in2);//false (都是new的->两个不同的对象->地址不同)
    System.out.println(in1.equals(in2));//true (比较的是内容)

    //超过缓存范围不能重用对象
    Integer in3= new Integer(128);//堆中
    Integer in4 = new Integer(128);//堆中
    System.out.println(in3==in4);//false (都是new的->两个不同的对象->地址不同)
    System.out.println(in3.equals(in4));//true (比较的是内容)

    Integer in5= 128;//堆中
    Integer in6 = 128;//堆中
    System.out.println(in5==in6);//false (超过缓存范围不能重用对象)
    System.out.println(in5.equals(in6));//true  (比较的是内容)


    Integer in7= 127;//常量池中
    Integer in8 = 127;//常量池中
    System.out.println(in7==in8);//true (没有超出缓存范围可以重用对象)
    System.out.println(in7.equals(in8));//true (比较的是内容)


    Integer tt2 = Integer.valueOf(99);//常量池 利用的缓存
    int tt3 = 99;
    System.out.println(tt2 == tt3);// true
}
  • 在哪些场景下,Java会自动拆装箱呢 ? 
  1. 将基本数据类型放入到集合类中===>自动装箱(Integer.valueOf()方法)
  2. 包装类和基本数据类型大小比较时,先将包装类拆箱之后在进行比较.==>自动拆箱(xxxValue())
  3. 包装类的运算会被自动拆箱成基本数据类型进行运算
  4. 在使用三目运算符的时候,第二位,第三位操作数如果是基本类型和对象的时候其中对象就会被拆箱为基本数据类型.如果包装类对象为null,在自动拆箱的时候就会抛出NPE
  5. 在函数传参的时候,传参基本数据类型返回包装类==>自动装箱,传参包装类,返回基本数据类型==>自动拆箱
  • 自动拆装箱会带来什么问题呢 ? 
  1. 比较包装类型的时候不能简单的使用==,虽然-128,127范围内可以使用==,但是超出这个范围的数字还是需要equals
  2. 有些场景下,如果包装类对象为null,在自动拆箱的时候就会抛出NPE
  3. 如果一个for循环有大量的拆装箱动作,就会浪费很多资源.
  • 遇到过自动拆箱引发的NPE问题么 ? 

如果包装类对象为null,就会引发出NPE问题

  1. 比如数据库查询结果有可能查出为null,但使用基本数据类型接收就会触发自动拆箱就会NPE
  2. 在使用三目运算符的时候,第二位,第三位操作数如果是基本类型和对象的时候其中对象就会被拆箱为基本数据类型.如果包装类对象为null,在自动拆箱的时候就会抛出NPE
  • String 怎么转成 Integer 的?原理?

String 转成 Integer,主要有两个方法:

  • Integer.parseInt(String s)
  • Integer.valueOf(String s)

不管哪一种,最终还是会调用 Integer 类内中的parseInt(String s, int radix)方法.

public static int parseInt(String s, int radix)
                throws NumberFormatException
    {

        int result = 0;
        //是否是负数
        boolean negative = false;
        //char字符数组下标和长度
        int i = 0, len = s.length();
        ……
        int digit;
        //判断字符长度是否大于0,否则抛出异常
        if (len > 0) {
            ……
            while (i < len) {
                // Accumulating negatively avoids surprises near MAX_VALUE
                //返回指定基数中字符表示的数值。(此处是十进制数值)
                digit = Character.digit(s.charAt(i++),radix);
                //进制位乘以数值
                result *= radix;
                result -= digit;
            }
        }
        //根据上面得到的是否负数,返回相应的值
        return negative ? result : -result;
    }

使用一个数字累减,然后最后取反.

Object类

object类是所有类的父类

Object 类的常见方法有哪些?

以下所有方法,所有对象都会有

对象比较

  • public native int hashCode() ;

 用于返回对象的哈希码,主要作用就是配合哈希表使用,比如 Java 中的 HashMap.

  • public boolean equals(Object obj);

用于比较 2 个对象是否相等,String 类对该方法进行了重写 ,比较的是字符串的值是否相等.

对象拷贝

  • protected native Object clone() throws CloneNotSupportedException : 用于创建并返回当前对象的一份拷贝.

使用clone方法不实现cloneable接口就会抛出异常CloneNotSupportedException

并且 对于任意一个对象

  • x.clone() == x 为false
  • x.clone().getClass() == x.getClass() 为true

对象转字符串:

  • public String toString();

返回对象字符串的表示形式.

多线程相关:

  • public final void wait() throws InterruptedException;

没有超时时间的等待方法,获取锁才能调用该方法,当条件不满足去锁对象内部的等待队列去等待-->底层调用的是wait(0). 当其他线程调用notify/notifyAll来唤醒

  • public final native void wait(long timeout) throws InterruptedException;

native 方法并且不能重写.

带有超时时间的等待方法,获取锁才能调用该方法,当条件不满足去锁对象内部的等待队列去等待-->底层调用的是wait(0). 当其他线程调用notify/notifyAll来唤醒或者超过超时时间就会被唤醒

  • public final void wait(long timeout, int nanos) throws InterruptedException;

多了 nanos 参数,这个参数表示额外时间. 所以超时的时间还需要加上 nanos 毫秒.获取锁才能调用该方法,当条件不满足去锁对象内部的等待队列去等待-->底层调用的是wait(0). 当其他线程调用notify/notifyAll来唤醒或者超过超时时间就会被唤醒

  • public final native void notify();

native方法 并且不能重写.唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念).如果有多个线程在等待只会任意唤醒一个.

  • public final native void notifyAll();

native方法并且不能重写.跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程.

反射:

  • public final native Class<?> getClass();

用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。

垃圾回收:

  • protected void finalize() throws Throwable;

当垃圾回收器决定回收某个对象的时候,就会运行该对象的finalize方法.

对象比较(hashcode和equals方法)

== 和 equals() 的区别

== 用于判断相等:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() : 它的作⽤用于判断两个对象是否相等。但是这个“相等”一般也分两种情况:

  • 类没有重写 equals()方法:通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法。
  • 类重写了 equals()方法:一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

equals方法是在Object类中的,只能用于比较两个对象是否相等.

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

对于Java中的String类就重写了equals方法,所以在比较字符串的时候比较的是两个字符串的内容,在使用equals方法的时候要避免空指针异常.

所以良好的编码习惯是

  • 使用常量.equals(变量) , 将不会为null的对象放在前面,将可能为null的对象放在后面
  • 如果两个字符串都有可能为null,建议使用Objects.equals(s1,s2)
public boolean equals(Object obj) {
    if (obj instanceof Long) {
        return value == ((Long)obj).longValue();
    }
    return false;
}

equals方法不能比较基本数据类型,当比较包装类的时候,首先会先比较类型,再去比较内容

//String类
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

equals默认是先会比较两个对象的内存地址,在去比较两个对象的类型,在使用equals比较两个对象的内容是否相等.

hashCode() 是什么 ?  有什么用?

用于返回对象的哈希码,主要作用就是配合哈希表使用,比如 Java 中的 HashMap用于确定在哈希表中的索引位置

对于hashmap而言,是不允许元素重复的,当我们添加一个元素的时候,需要查询这个元素是否已经存在了,如果存在直接覆盖,

如果只使用equals查询速度太慢了,对于hashmap就使用hashcode + equals进行比较

当添加一个元素的时候

先计算数组下标,然后与每一个元素进行比较的时候先进行hashcode比较,如果该下标的链表中没有相同的,直接存进去,如果hashcode相同,在使用equals进行比较,如果equals不同,直接存进去-->发生哈希冲突,否则,会覆盖掉原先的值.

通常来说,String 会用来作为 HashMap 的键进行哈希运算,因此我们再来看一下 String 的 hashCode() 方法

String中的hash算法 :

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

HashMap中的hash算法 : 

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 那为什么两个对象有相同的 hashCode 值,它们也不一定是相等的?(2个不相等的对象有可能具有相同的 hashcode吗?)(两个对象的hashCode()相同,则 equals()是否也一定为 true?)

要想解决这个问题我们得知道hashcode是怎么计算的.

static inline intptr_t get_next_hash(Thread* current, oop obj) {
  intptr_t value = 0;
  if (hashCode == 0) {
    // 这种形式使用全局的 Park-Miller 随机数生成器。
    // 在 MP 系统上,我们将对全局变量进行大量的读写访问,因此该机制会引发大量的一致性通信。
    value = os::random();
  } else if (hashCode == 1) {
    // 这种变体在 STW(Stop The World)操作之间具有稳定(幂等)的特性。
    // 在一些 1-0 同步方案中,这可能很有用。
    intptr_t addr_bits = cast_from_oop<intptr_t>(obj) >> 3;
    value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;
  } else if (hashCode == 2) {
    value = 1;            // 用于敏感性测试
  } else if (hashCode == 3) {
    value = ++GVars.hc_sequence;
  } else if (hashCode == 4) {
    value = cast_from_oop<intptr_t>(obj);
  } else {
    // Marsaglia 的异或移位方案,具有线程特定的状态
    // 这可能是最好的整体实现 -- 我们可能会在未来的版本中将其设为默认实现。
    unsigned t = current->_hashStateX;
    t ^= (t << 11);
    current->_hashStateX = current->_hashStateY;
    current->_hashStateY = current->_hashStateZ;
    current->_hashStateZ = current->_hashStateW;
    unsigned v = current->_hashStateW;
    v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));
    current->_hashStateW = v;
    value = v;
  }

  value &= markWord::hash_mask;
  if (value == 0) value = 0xBAD;
  assert(value != markWord::no_hash, "invariant");
  return value;
}

对于上面的hashcode方法代码是C++来实现的也就是native方法

这里总结一下

  • hashcode==0 : 随机数
  • hashcode==1 地址 + 随机数
  • hashcode==2 固定值为1
  • hashcode==3 递增数列
  • hashcode==4 地址
  • hashcode==5 其他随机数

Java使用的就是hashcode==5,使用一个随机算法计算出来的随机数进行返回. 所以即使是不同的对象计算出的hashcode也有可能相同.

即 :

  • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等.
  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞).
  • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等.

既然Java中默认的hashcode采用的是随机算法返回一个随机数,为什么每次相同对象的hashcode都一样呢 ? 

因为当一个对象第一次调用hashcode的时候会放入对象头中的markword中,下次在调用对象的hashcode的时候,如果markword中已经保存了,那么直接使用即可.

hashcode与equals结论

综上所述得出结论 :

  • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等.
  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞).
  • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等.

hashCode生成方式可以调整吗?

可以使用虚拟机参数来调整hashcode的生成方式

-XX:+UnlockExperimentalVMOptions -XX:hashCode=?

怎么理解对象相等 ? 

两个对象相等的严格定义是 : 对象的内容相同(equals的结果) ,并且哈希值也要相等(hashCode的结果).

hashcode实现原理了解么?

底层native方法的实现 : 

static inline intptr_t get_next_hash(Thread* current, oop obj) {
  intptr_t value = 0;
  if (hashCode == 0) {
    // 这种形式使用全局的 Park-Miller 随机数生成器。
    // 在 MP 系统上,我们将对全局变量进行大量的读写访问,因此该机制会引发大量的一致性通信。
    value = os::random();
  } else if (hashCode == 1) {
    // 这种变体在 STW(Stop The World)操作之间具有稳定(幂等)的特性。
    // 在一些 1-0 同步方案中,这可能很有用。
    intptr_t addr_bits = cast_from_oop<intptr_t>(obj) >> 3;
    value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;
  } else if (hashCode == 2) {
    value = 1;            // 用于敏感性测试
  } else if (hashCode == 3) {
    value = ++GVars.hc_sequence;
  } else if (hashCode == 4) {
    value = cast_from_oop<intptr_t>(obj);
  } else {
    // Marsaglia 的异或移位方案,具有线程特定的状态
    // 这可能是最好的整体实现 -- 我们可能会在未来的版本中将其设为默认实现。
    unsigned t = current->_hashStateX;
    t ^= (t << 11);
    current->_hashStateX = current->_hashStateY;
    current->_hashStateY = current->_hashStateZ;
    current->_hashStateZ = current->_hashStateW;
    unsigned v = current->_hashStateW;
    v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));
    current->_hashStateW = v;
    value = v;
  }

  value &= markWord::hash_mask;
  if (value == 0) value = 0xBAD;
  assert(value != markWord::no_hash, "invariant");
  return value;
}

对于上面的hashcode方法代码是C++来实现的也就是native方法

这里总结一下

  • hashcode==0 : 随机数
  • hashcode==1 地址 + 随机数
  • hashcode==2 固定值为1
  • hashcode==3 递增数列
  • hashcode==4 地址
  • hashcode==5 其他随机数

Java使用的就是hashcode==5,使用一个随机算法计算出来的随机数进行返回. 所以即使是不同的对象计算出的hashcode也有可能相同.

String对象的 hashcode() 是如何设计的 ? 为啥每次都乘以31

  • HashCode的设计目标就是设计出较为均匀的散列效果,使得每一个字符串的hashcode都不相同,足够独特->理想情况下.

对于String的HashCode.

由于字符串中的每一个字符都可以根据查ASCII码表来得到一个数字为Si,那么i的范围就是0~n-1

那么可以通过散列公式为 s0 * 31^n-1 + s1* 31^n-2 + ....... si* 31^n-1-i + Sn-1 * 31^0,这样每一个数字乘以31的个数都不相同,这样就能够保证每一个字符串的hashcode足够独特.与其他字符串的hashcode不相同.

那为什么每次都要乘以31呢 ?

原因是31带入公式有较好的散列效果, 另外31可以被优化为移位运算 比如 31*h ===> (h<<5 - h),提高效率

如果重写了Object类的hashcode,底层实现是怎么样的 ? 

@Override
public int hashCode() {
    return Objects.hash(id);
}
            ||
            \/
public static int hash(Object... values) {
     return Arrays.hashCode(values);
}
            ||
            \/
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;
}

重写之后会让Objects类调用hash方法生成的hashcode值返回

Objects类的hash方法又是调用Arrays.hashCode方法.

Arrays.hashCode方法.把属性放进一个Object数组,每次与31相乘并加上对象的hashcode累加而成为最终的hashcode.

为什么 JDK 还要同时提供hashcode和equals这两个方法呢?

我举一个hashse添加对象的例子

当往hashset中添加元素的时候,会先使用hashcode计算出元素的下标位置,然后在与该下标位置的对象进行比较hashcode,如果hashcode相同,则发生了哈希冲突,会在使用equals进行比较,如果不同会继续比较其他对象,知道该链表中的对象全部比较完成都不相同,则会加入到链表的尾部.

可以看出,Java的容器使用hashcode定位到具体下标位置,先比较对象的hashcode,如果对象的hashcode相同再去比较equals,这样就大大减少了equals的比较次数.

重写hashcode的原因

重写hashcode方法的主要目的是保证相同对象映射到同一个下标上,确保对象在散列表等数据结构中能够被正确地存储和检索。

如果没有重写hashcode方法,默认情况下Object类中的hashCode()方法会根据对象进行计算返回的是一个随机数, 这会导致相同对象的hashcode不同,映射不在同一个下标中 , 会出现重复元素

重写equals的原因

如果没有重写equals那么默认比较的是对象的内存地址(equals只能比较对象) ,就算是找到对应的映射下标 , 即使是内容相同的对象,也会认为是不同的(因为比较的是对象的内存地址)

重写了equals才比较的是内容.

只重写hashcode 或者 只重写equals方法 可以么 ?

  • 如果只重写了hashcode方法,当哈希冲突发生的时候,即使两个对象相等,也不会判断两个对象为重复(默认的equals比较的是地址),进而就会导致数组中存储一大堆重复的对象.
  • 如果只重写了equals方法,那两个相等的的对象,存放的下标不一定相等,还是会造成重复元素的问题

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

如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。导致存放的下标不一定相等,会造成重复元素的问题

  • 对于HashSet :

这样对于HashSet就失去了去重的功能.

重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题。

  • 对于 HashMap 

HashMap里面本来有这个key,但是你告诉我没有,导致了put操作成功,get时取出来的不是key对象的value.

这样就与Java的思想不符合.

因为:hashmap只能有唯一的key, hashSet只能有唯一的对象

综上 : 

  • 如果只重写了hashcode方法,当哈希冲突发生的时候,即使两个对象相等,也不会判断两个对象为重复(默认的equals比较的是地址),进而就会导致数组中存储一大堆重复的对象.
  • 如果只重写了equals方法,那两个相等的的对象,存放的下标不一定相等,还是会造成重复元素的问题

必须两个都要重写

谈谈你对java.lang.Object对象中hashCode和equals方法的理解。在什么场景下需要重新实现这两个方法。(hashCode()和equals()方法的重要性体现在什么地方?)

hashcode和equals都是Object的方法,也就是所有对象都有这两个方法.

Object类的hashcode方法在Java中默认会返回一个随机数也就是哈希码. 主要的作用就是确定对象在哈希表中的位置(Java中的,HashMap,HashSet等).

Object类的equals方法默认比较的是两个对象的地址是否相同.如果重写了比较的是内容

使用场景

比如我们在使用容器,hashMap,HashSet的时候,存放对象的时候都需要先根据hashcode计算出一个索引值,然后把对象放进下标中,在我们计算下标的时候很有可能出现哈希冲突问题,因为不同的对象也有可能hashcode相同,hashMap中采用的是链地址法. 因为HashMap,HashSet中的key是唯一的,为了防止key重复,hashcode相同的对象还要使用equals进行比较.如果与下标这个链表的元素都不同才能放入到链表中. 否则就会新值覆盖旧值.

如果存放的是自定义对象,就需要重写hashcode和equals方法.因为默认都是Object类的hashcode和equals方法.就会导致相同的对象计算出的hashcode和equals不一样,导致发生重复元素的问题以及其他不可预知的问题.所以我们必须同时重写这两个方法以防止出现错误.

Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()?

当往hashset中添加元素的时候,会先使用hashcode计算出元素的下标位置,然后在与该下标位置的对象进行比较hashcode,如果hashcode相同,则发生了哈希冲突,会在使用equals进行比较,如果不同会继续比较其他对象,知道该链表中的对象全部比较完成都不相同,则会加入到链表的尾部.

对象拷贝(clone方法)

clone方法

  • protected native Object clone() throws CloneNotSupportedException : 用于创建并返回当前对象的一份拷贝.

使用clone方法不实现cloneable接口就会抛出异常CloneNotSupportedException

并且 对于任意一个对象

  • x.clone() == x 为false
  • x.clone().getClass() == x.getClass() 为true

如何实现对象克隆?

深拷贝与浅拷贝

Cloneable 接口实现原理?

每个对象都可上锁,这是在 Object类而不是 Thread 类中声明,为什么呢?

为什么等待和通知是在 Object 类而不是 Thread 中声明的?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/556637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据分享】中国首套10米分辨率的建筑高度数据(tif格式)

建筑是城市最重要的构成要素&#xff0c;高密度高层数的建筑是城市区别于乡村的显著特征&#xff01;建筑数据也是我们在各项研究中都会使用到的数据&#xff01;之前我们分享过2020年全国90个城市市域范围的建筑体块数据&#xff08;可查看之前的文章获悉详情&#xff09;。 …

Ubuntu2004设置共享开发环境

我们都知道Linux操作系统是一个多用户的操作系统&#xff0c;由于大家在实际工作中很少接触到多用户环境&#xff0c;特别是在目前电脑硬件成本不断降低的情况下几乎每个从事IT行业的人员都有一台甚至多台个人PC&#xff0c;因此大家对多用户的理解并不深刻。 ChatGPT引燃了人…

【数据结构】堆堆堆堆堆!

目录 前言 树 树的概念 树的相关概念​编辑 树的表示 二叉树的概念 特殊的二叉树 ​ 二叉树的存储结构 堆 堆的建立(本篇以小堆为例&#xff0c;大堆实现方法一样&#xff09; 堆的结构定义 堆的初始化 堆的插入 堆的基础算法——向上调整算法 插入注意事项 堆的判…

Openai+Coursera: ChatGPT Prompt Engineering(三)

想和大家分享一下最近学习的Coursera和openai联合打造ChatGPT Prompt Engineering在线课程.以下是我写的关于该课程的前两篇博客&#xff1a; ChatGPT Prompt Engineering(一) ChatGPT Prompt Engineering(二) 今天我们来学习第三部分内容&#xff1a;推断(Inferring) 推断…

Android:IPC(进程间通信)机制

Android&#xff1a;IPC&#xff08;进程间通信&#xff09;机制 进程和线程 我们先来了解一些关于线程和进程基本的概念。 按照操作系统中的描述&#xff0c;线程是CPU调度的最小单元&#xff0c;同时线程是一种有限的系统资源。而进程一般指一个执行单元&#xff0c;在PC和…

(学习日记)AD学习 #2

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

npm ERR! code E404 在vscode安装插件时报错的解决方案

答主在配置commit代码提交规范时【即如下代码】遇到了以下问题 npm i cz-customizable6.3.0 --save-dev 出现了 npm ERR! code E404 npm ERR! 404 Not Found - GET https://registry.npmjs.org/vue%2fvue-loader-v15 - Not found npm ERR! 404 ……等报错情况 解决方案1 检查n…

SVN 导出改动差异文件

文章目录 SVN 导出改动差异文件应用场景/背景介绍具体操作方法 SVN 导出改动差异文件 应用场景/背景介绍 当然下面的两个场景介绍可能用分支管理都会有不错的效果&#xff0c;或者更优&#xff0c;只是记录一下思路&#xff0c;用什么还是看大家个人爱好啦 在开发过程中偶尔会…

nexus私服仓库maven-metadata.xml缺失导致的构建失败或者下载504

环境&#xff1a;maven项目&#xff0c;使用Nexus私服&#xff0c;jenkins实现代码的编译和打包。 问题分析思路&#xff1a;某周末前&#xff0c;jenkins上的编译打包任务一直正常工作&#xff0c;但周末后突然所有项目都编译失败&#xff0c;报错很一致都是Could not find a…

【牛客刷题专栏】0x30:JZ38 字符串的排列(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录 前言问…

局域网与城域网 - ARP 地址解析协议

文章目录 1 概述2 ARP 地址解析协议2.1 工作过程2.2 报文格式2.3 ARP 命令 3 扩展3.1 网工软考真题 1 概述 #mermaid-svg-CQnNvTP8xFoJsztk {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-CQnNvTP8xFoJsztk .error-…

plt、fig、axes、axis的含义

plt import matplotlib.pyplot as plt figure,axes与axis 如果将Matplotlib绘图和我们平常画画相类比&#xff0c;可以把Figure想象成一张纸&#xff08;一般被称之为画布&#xff09;&#xff0c;Axes代表的则是纸中的一片区域&#xff08;当然可以有多个区域&#xff0c;这…

剑指 Offer - 字符串合辑

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340; 算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 题解目录 一、&#x1f331;[剑指 Offer 05. 替换空格](https://leetcode.cn/problems/t…

[笔记]C++并发编程实战 《二》线程管理

文章目录 前言第2章 线程管理2.1 线程管理的基础2.1.1 启动线程2.1.2 等待线程完成2.1.3 特殊情况下的等待2.1.4 后台运行线程2.2 向线程函数传递参数 前言 第2章 线程管理 本章主要内容 启动新线程等待线程与分离线程线程唯一标识符 好的&#xff01;看来你已经决定使用多…

使用压缩包安装jdk多版本并能领过切换

使用压缩包安装jdk多版本并能领过切换 1.下载2.解压包到指定位置3.使用pdate-alternatives 进行版本切换管理3.1. jdk173.2. jdk1.8 3.切换版本4.解决JAVA_HOME环境变量识别的问题 1.下载 官网的下载地址&#xff1a; 下载地址&#xff1a; jdk17: jdk1.8在当前页面的下面: …

基于差分进化算法的微电网调度研究(Matlab代码实现)​

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

MAC突然打不开Notion,你遇到过这个问题吗?

目录 解决办法 为什么Notion会突然打不开呢&#xff1f; Notion是一款适合记录/规划的应用&#xff0c;而且页面简洁&#xff0c;模板强大&#xff0c;深得大家喜爱。我也经常在Notion上制定计划、记录学习笔记等。不过&#xff0c;今天突然打不开了&#xff0c;网页版、本地…

基于SpringBoot的生鲜管理系统的设计与实现

背景 困扰交易市场的许多问题当中,生鲜交易管理一定是交易市场不敢忽视的一块。但是管理好生鲜交易又面临很多麻烦需要解决,例如有几个方面:第一,生鲜市场往往人数都比较多,如何保证能够管理到每一个商家,如何在工作琐碎,记录繁多的情况下将生鲜交易的当前情况反应给领导相关部…

【大数据】Hadoop高可用集群搭建

知识目录 一、写在前面&#x1f495;二、Zookeeper安装✨三、Hadoop配置✨四、Hadoop HA自动模式✨五、HA脚本分享✨七、结语&#x1f495; 一、写在前面&#x1f495; 大家好&#xff01;这篇文章是我在搭建Hdfs的HA(高可用)时写下的详细笔记与感想&#xff0c;希望能帮助到大…

分布式调度XXL-JOB

分布式调度XXL-JOB 1.概述 1.1什么是任务调度 比如: 某电商平台需要每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发放一批优惠券某银行系统需要在信用卡到期还款日的前三天进行短信提醒某财务系统需要在每天凌晨0:10分结算前一天的财务数据&#xff0c;统计汇总 以…