本篇文章主要讲解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会自动拆装箱呢 ?
- 将基本数据类型放入到集合类中===>自动装箱(Integer.valueOf()方法)
- 包装类和基本数据类型大小比较时,先将包装类拆箱之后在进行比较.==>自动拆箱(xxxValue())
- 包装类的运算会被自动拆箱成基本数据类型进行运算
- 在使用三目运算符的时候,第二位,第三位操作数如果是基本类型和对象的时候其中对象就会被拆箱为基本数据类型.如果包装类对象为null,在自动拆箱的时候就会抛出NPE
- 在函数传参的时候,传参基本数据类型返回包装类==>自动装箱,传参包装类,返回基本数据类型==>自动拆箱
- 自动拆装箱会带来什么问题呢 ?
- 比较包装类型的时候不能简单的使用==,虽然-128,127范围内可以使用==,但是超出这个范围的数字还是需要equals
- 有些场景下,如果包装类对象为null,在自动拆箱的时候就会抛出NPE
- 如果一个for循环有大量的拆装箱动作,就会浪费很多资源.
-
遇到过自动拆箱引发的NPE问题么 ?
如果包装类对象为null,就会引发出NPE问题
- 比如数据库查询结果有可能查出为null,但使用基本数据类型接收就会触发自动拆箱就会NPE
- 在使用三目运算符的时候,第二位,第三位操作数如果是基本类型和对象的时候其中对象就会被拆箱为基本数据类型.如果包装类对象为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()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object
类equals()
方法。 - 类重写了
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 中声明的?