面试最常问的问题
1、equals 比拟的什么?
2、有没有重写过 equals?
3、有没有重写过 hashCode?
4、什么状况下须要重写 equals()和 hashCode()?
1) equals 源码
指标:如果不做任何解决(可能绝大大大多数场景的对象都是这样的),jvm 对同一个对象的判断逻辑是怎么的
咱们先读一下 Object 里的源码:
/**
* Indicates whether some other object is "equal to" this one.
* <p>
* The {@code equals} method implements an equivalence relation
* on non-null object references:
* <ul>
* <li>It is <i>reflexive</i>: for any non-null reference value
* {@code x}, {@code x.equals(x)} should return
* {@code true}.
* <li>It is <i>symmetric</i>: for any non-null reference values
* {@code x} and {@code y}, {@code x.equals(y)}
* should return {@code true} if and only if
* {@code y.equals(x)} returns {@code true}.
* <li>It is <i>transitive</i>: for any non-null reference values
* {@code x}, {@code y}, and {@code z}, if
* {@code x.equals(y)} returns {@code true} and
* {@code y.equals(z)} returns {@code true}, then
* {@code x.equals(z)} should return {@code true}.
* <li>It is <i>consistent</i>: for any non-null reference values
* {@code x} and {@code y}, multiple invocations of
* {@code x.equals(y)} consistently return {@code true}
* or consistently return {@code false}, provided no
* information used in {@code equals} comparisons on the
* objects is modified.
* <li>For any non-null reference value {@code x},
* {@code x.equals(null)} should return {@code false}.
* </ul>
* <p>
* 该办法用于辨认两个对象之间的相似性
* 也就是说,对于一个非 null 值,x 和 y,当且仅当它们指向同一个对象时才会返回 true
* 话中有话,和 == 没啥两样。* The {@code equals} method for class {@code Object} implements
* the most discriminating possible equivalence relation on objects;
* that is, for any non-null reference values {@code x} and
* {@code y}, this method returns {@code true} if and only
* if {@code x} and {@code y} refer to the same object
* ({@code x == y} has the value {@code true}).
* <p>
* Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
*
* @param obj the reference object with which to compare.
* @return {@code true} if this object is the same as the obj
* argument; {@code false} otherwise.
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {return (this == obj);
}
猜测:如果咱们不做任何操作,equals 将继承 object 的办法,那么它和 == 也没啥区别!
上面一起做个面试题,验证一下这个猜测:
package com.eq;
import java.io.InputStream;
public class DefaultEq {
String name;
public DefaultEq(String name){this.name = name;}
public static void main(String[] args) {DefaultEq eq1 = new DefaultEq("张三");
DefaultEq eq2 = new DefaultEq("张三");
DefaultEq eq3 = eq1;
// 尽管俩对象里面看起来一样,eq 和 == 都不行
// 因为咱们没有改写 equals,它应用默认 object 的,也就是内存地址
System.out.println(eq1.equals(eq2));
System.out.println(eq1 == eq2);
System.out.println("----");
// 1 和 3 是同一个援用
System.out.println(eq1.equals(eq3));
System.out.println(eq1 == eq3);
System.out.println("===");
// 以上是对象,再来看根本类型
int i1 = 1;
Integer i2 = 1;
Integer i = new Integer(1);
Integer j = new Integer(1);
Integer k = new Integer(2);
// 只有是根本类型,不论值还是包装成对象,都是间接比拟大小
System.out.println(i.equals(i1)); // 比拟的是值
System.out.println(i==i1); // 拆箱 ,
// 封装对象 i 被拆箱,变为值比拟,1== 1 成立
// 相当于 System.out.println(1==1);
System.out.println(i.equals(j)); //
System.out.println(i==j); // 比拟的是地址,这是俩对象
System.out.println(i2 == i); // i2 在常量池里,i 在堆里,地址不一样
System.out.println(i.equals(k)); // 1 和 2,不解释
}
}
论断:
-
“==”比拟的是什么?
用于根本数据(8 种)类型(或包装类型)互相比拟,比拟二者的值是否相等。
用于援用数据(类、接口、数组)类型互相比拟,比拟二者地址是否相等。
-
equals 比拟的什么?
默认状况下,所有对象继承 Object,而 Object 的 equals 比拟的就是内存地址
所以默认状况下,这俩没啥区别
2) 内存地址生成与比拟
tips:既然没区别,那咱们看一下,内存地址到底是个啥玩意
指标:内存地址是如何来的?
Main.java
public static void main(String[] args) {User user1=new User("张三");
User user2=new User("张三");
}
1、加载过程(回顾)
从 java 文件到 jvm:
tips:加载到办法区
这个阶段只是 User 类的信息进入办法区,还没有为两个 user 来分配内存
2、分配内存空间
在 main 线程执行阶段,指针碰撞(间断内存空间时),或者闲暇列表(不间断空间)形式开拓一块堆内存
每次 new 一个,开拓一块,所以两个 new 之间必定不是雷同地址,哪怕你 new 的都是同一个类型的 class。
那么它如何来保障内存地址不反复的呢?(cas 画图)
3、指向
在栈中创立两个局部变量 user1,user2,指向堆里的内存
归根到底,下面的 == 比拟的是两个对象的堆内存地址,也就是栈中局部变量表里存储的值。
public boolean equals(Object obj) {return (this == obj);// 本类比拟的是内存地址(援用)
}
3) 默认 equals 的问题
需要(or 指标):user1 和 user2,如果 name 一样咱们就认为是同一个人;如何解决?
tips:
面试最常问的问题
1、equals 比拟的什么?
2、有没有重写过 equals?
3、有没有重写过 hashCode?
4、什么状况下须要重写 equals()和 hashCode()?
1、先拿 User 下手,看看它的默认行为(com.eq.EqualsObjTest)
public static void main(String[] args) {
// 需要::user1 和 user2,在现实生活中是一个人;如何断定是一个人(相等)User user1 = new User("张三");
User user2 = new User("张三");
System.out.println("是否同一个人:"+user1.equals(user2));
System.out.println("内存地址相等:"+String.valueOf(user1 == user2));// 内存地址
System.out.println("user1 的 hashCode 为 >>>>" + user1.hashCode());
System.out.println("user2 的 hashCode 为 >>>>" + user2.hashCode());
}
输入如下
论断:
很显然,默认的 User 继承了 Object 的办法,而 object,依据下面的源码剖析咱们晓得,equals 就是内存地址。
而你两次 new User,不论 name 怎么统一,内存调配,必定不是同一个地址!
怎么破?
2、同样的场景,咱们把用户名从 User 换成单纯的字符串试试(com.eq.EqualsStrTest)
public static void main(String[] args) {
String str1 = "张三";// 常量池
String str2 = new String("张三");// 堆中
String str3 = new String("张三");// 堆中
System.out.println("是否同一人:"+str1.equals(str2));// 这个中央为什么相等呢,重写
System.out.println("是否同一人:"+str2.equals(str3));// 这个中央为什么相等呢,重写
// 如果相等,hashcode 必须相等,重写
System.out.println("str1 的 hashCode 为 >>" + str1.hashCode());
System.out.println("str2 的 hashCode 为 >>" + str2.hashCode());
}
}
输入如下
达到了咱们的逾期,雷同的 name,被断定为同一个人,为什么呢?往下看!
String 的源码剖析
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
// 如果内存地址相等,那必须 equal
if (this == anObject) {return true;}
if (anObject instanceof String) {
// 如果对象是 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) {
// 从前往后,任意一个字符不匹配,间接返回 false
if (v1[i] != v2[i])
return false;
i++;
}
// 全副匹配完结,返回 true
return true;
}
}
return false;
}
论断:
String 类型改写了 equals 办法,没有应用 Object 的默认实现
它不论你是不是同一个内存地址,只有俩字符串里的字符都匹配上,那么 equals 就认为它是 true
3、据此,咱们参照 String,来重写 User 的 equals 和 hashCode(com.eq.User2)
@Override
public boolean equals(Object o) {
// 留神这些额定的判断类操作
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
// 比拟值
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
// 返回值的 hashCode
return name != null ? name.hashCode() : 0;}
换成 User2 再来跑试试(参考 com.eq.EqualsObjTest2)
目标达到!
4)hashCode 与 equals
为什么说 hashCode 和 equals 是一对搭档?他俩到底啥关系须要绑定到一块?
看代码谈话:(com.eq.Contains)
package com.eq;
import java.util.HashSet;
import java.util.Set;
public class Contains {public static void main(String[] args) {User user1 = new User("张三");
User user2 = new User("张三");
Set set = new HashSet();
set.add(user1);
System.out.println(set.contains(user2));
User2 user3 = new User2("张三");
User2 user4 = new User2("张三");
Set set2 = new HashSet();
set2.add(user3);
System.out.println(set2.contains(user4));
}
}
论断:
hashCode 是给 java 汇合类的一些动作提供撑持,来判断俩对象“是否是同一个”的规范
equals 是给你编码时判断用的,所以,这俩必须保持一致的逻辑。
5)总结
1、非凡业务需要须要重写,比方下面的
2、例如 map,key 放自定义对象也须要重写
3、重写 equals 后必须要重写 hashCode,要放弃逻辑上的统一!
1.2.5 对于双等(扩大)
equals 被重写后,双等还留着干啥用?
1)String 的特殊性
tips:面试常问的问题
intern 是做什么的?
先来看一段代码:(com.eq.Intern)
public class Intern {public static void main(String[] args) {
String str1 = "张三";// 常量池
String str2 = new String("张三");// 堆中
//intern;内存地址是否相等(面试常问)
System.out.println("str1 与 str2 是否相等 >>" +(str1==str2)); // false
System.out.println("str1 与 str2 是否相等 >>" +(str1==str2.intern())); // true
}
}
版本申明:(JDK1.8)
new String 是在堆上创立字符串对象。
当调用 intern() 办法时,
JVM 会将字符串增加 (堆援用指向常量池) 到常量池中留神:
1、1.8 版本只是将 hello word 在堆中的援用指向常量池,之前的版本是把 hello word 复制到常量池
2、堆(字符串常量值)办法区(运行时常量池)不要搞反了
2)valueOf 里的机密
对于双等号地址问题,除了 String.intern(),在根底类型里,如 Integer,Long 等同样有一个办法:valueOf 须要留神
咱们先来看一个小例子:猜一猜后果?
package com.eq;
public class Valueof {public static void main(String[] args) {System.out.println( Integer.valueOf(127) == Integer.valueOf(127));
System.out.println(Integer.valueOf(128) == Integer.valueOf(128));
}
}
奇怪的后果……
源码剖析(以 Integer 为例子):
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
*!在 -128 到 127 之间会被 cache,同一个地址下,超出后返回 new 对象!*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
本文由传智教育博学谷 – 狂野架构师教研团队公布,转载请注明出处!
如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源