面试的时候,经常被问到,总结一下,如有不正确的地方,多多指教。
1 、关于“==”
关于“!=”,看完下面的内容,大家应该可以自己脑补;“==”可以进行下面三种类型的比较;
- 数值类型==
可以在基本类型之间进行比较,比如整型类型,int,long;浮点类型float,double;
(1)基本类型之间可以相互比较,如果对应的值相等,则返回true,否则,返回false;
(2)基本类型与对应的包装类型之间也可以进行比较,比较的时候,包装类型要进行拆箱操作;
(3) 布尔类型与数值类型(整型、浮点型)是不能直接用“==”比较的。
示例代码:
public static void main(String []args){
int i = 9;
float f = 9f;
long l = 9l;
double d = 9d;
Long L = new Long(9l);
double D = new Double(9d);
double D1 = new Double(9.1d);
System.out.println("int == float : " +(i==f));
System.out.println("int == long : " +(i==l));
System.out.println("int == double : " +(i==d));
System.out.println("Long == Double : " +(L==D));
System.out.println("Long != Double : " +(L==D1));
}
运行结果:
int == float : true
int == long : true
int == double : true
Long == Double : true
Long != Double : false
- 布尔类型==
(1) 两个操作数操作数类型为boolean,或者是其包装类型Boolean;
(2) 如果其中一个操作数是类型Boolean,则进行拆箱转换。
(3) 如果两个操作数都为true或者false,结果为true,否则,结果为false;
示例代码:
public static void main(String []args){
boolean t = true;
Boolean t1 = true;
boolean f = false;
Boolean f1 = false;
System.out.println("boolean t == Boolean t1 : " +(t==t1));
System.out.println("boolean t == Boolean f : " +(t==f));
System.out.println("boolean f == Boolean f1 : " +(f==f1));
System.out.println("Boolean t1 == Boolean f1 : " +(t1==f1));
}
运行结果:
boolean t == Boolean t1 : true
boolean t == Boolean f : false
boolean f == Boolean f1 : true
Boolean t1 == Boolean f1 : false
- 引用类型
运算符的操作数是引用类型或者是null类型,引用类型可以是对象或者数组;
(1)在比较时,如果两个操作数都是null或者都引用同一个对象或数组,则结果为true,否则,结果为false;
(2)虽然“==”可以用于比较类型的引用String,但是这样的相等性测试是确定两个操作数是否引用相同的String 对象;因此,可能会出现两个new的String对象字符串序列相同,但是“==”结果却是false的情况;
(3)两个字符串的内容s和t是否相等可以通过s.equals(t)方法进行计较。
示例代码:
public static void main(String []args){
Object o1 = new Object();
Object o2 = o1; //o2与o1引用同一对象
Object o3 = new Object();
Object o4 = null;
Object o5 = null;
System.out.println("o1 == o2 : " +(o1==o2));
System.out.println("o1 == o3 : " +(o1==o3));
System.out.println("o1 == o4 : " +(o1==o4));
System.out.println("o4 == o5 : " +(o4==o5));
}
运行结果:
o1 == o2 : true
o1 == o3 : false
o1 == o4 : false
o4 == o5 : true
2 、关于“equals”
boolean equals(Object obj) 方法来自java.lang.Object类,代码如下:
public boolean equals(Object obj) {
return (this == obj);
}
是不是感觉有点眼熟,实际上,如果某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,是在比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象。
我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回false;
由于 Java类,String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法,因此,是比较对象的内容是否相等;自定义类的话就需要自己覆盖equals()方法来判断两个对象的内容是否相等。
示例代码:
package com.example.demo;
import com.sun.tools.doclets.formats.html.SourceToHTMLConverter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
public class EqualsTest {
public static void main(String []args){
Person p1 = new Person("金毛狮王",100);
Person p2 = new Person("金毛狮王",100);
System.out.println("未覆盖equals: "+p1.equals(p2));
Person1 p3 = new Person1("金毛狮王",100);
Person1 p4 = new Person1("金毛狮王",100);
System.out.println("覆盖equals: "+p3.equals(p4));
}
}
class Person{
String name;
int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Person1{
String name;
int age;
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//覆盖之后,如果两个对象的内容相同,或者是属于同一个对象的引用,都返回true,否则,返回false
@Override
public boolean equals(Object obj) {
if(obj == null)
return false;
//如果是同一个对象返回true
if(this == obj)
return true;
//判断是否类型相同
if(this.getClass()!=obj.getClass())
return false;
Person1 p = (Person1)obj;
return this.name == p.name && this.age == p.age;
}
}
运行结果:
未覆盖equals: false
覆盖equals: true
equals方法在非空(null)对象引用上有如下的等价关系:
- 自反性: 对于任何非空引用值x , x.equals(x)应该返回true;
- 对称性:对于任何非空引用值x和y,如果x.equals(y) 返回true,那么 y.equals(x)也一定返回true;
- 传递性:对于任何非空引用值x,y,z,如果x.equals(y)返回true,y.equals(z)返回true, 那么x.equals(z)也一定返回true;
- 一致性:对于任何非空引用值 x和y,在多次调用 x.equals(y)始终返回true 或始终返回false,除非equals的比较对象被修改(这里多说一句,所谓的“修改”就是说你在覆盖equals方法时,进行对象内容比较,用到的对象中的某个成员的值发生了改变)。
- 对于任何非空引用值x, x.equals(null)应返回false。
3 、关于“hashCode”
public int hashCode()也是来自java.lang.Object类,它返回对象的哈希码(也叫散列码,一个整型数字);
哈希码是用来确定该对象在哈希表中的索引位置,在Java集合中,HashMap,HashTable,HashSet等都属于哈希(散列)表;因此,如果当前对象不会在散列表中被用到(就是不会作为HashSet的元素,或者HashMap的Key),那么完全可以忽略hashCode;可能你现在在想我平时用HashMap的时候也没有考虑hashCode的问题啊,不也用的好好的;因为,一般情况下,都用String或者Java已有类型作为Key,已有类型在默认继承Object的时候已经覆盖了hashCode方法,因此在使用HashSet或者HashMap的时候,不会出现重复元素;看几个例子就明白了:
下面分别用HashSet和HashMap做测试,要做的就是保证HashSet里面元素不同,HashMap的key也不相同;
先看HashSet,
示例代码:
package com.example.demo;
import java.util.HashSet;
import java.util.Iterator;
public class HashTest {
public static void main(String[] args) {
HashSet<MyString> stringHashSet = new HashSet<>();
HashSet<MyString1> myStringHashSet = new HashSet<>();
stringHashSet.add(new MyString("hello"));
stringHashSet.add(new MyString("hello"));
stringHashSet.add(new MyString("hello1"));
Iterator it = stringHashSet.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("===========");
myStringHashSet.add(new MyString1("hello"));
myStringHashSet.add(new MyString1("hello"));
myStringHashSet.add(new MyString1("hello1"));
Iterator<MyString1> it1 = myStringHashSet.iterator();
while(it1.hasNext()){
System.out.println(it1.next());
}
}
}
class MyString{
String value;
public MyString(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
class MyString1{
String value;
public MyString1(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.toUpperCase().hashCode();
}
@Override
public boolean equals(Object obj) {
MyString1 s = (MyString1)obj;
return this.value ==s.value;
}
}
- 运行结果
hello
hello
hello1
===========
hello1
hello
可以看出在没有覆盖hashcode()方法时,HashSet中的元素可能会出现重复的情况,这就违背了Set集合的设计初衷,而覆盖了hashcode()方法,HashSet中的元素就不重复了;
原因就是一般情况下,不同的对象会产生不同的hashcode,即使对象的内容相同;按照hashcode的约定规则:如果两个对象equals()相等,那么应该有相同的hashcode,因此,覆盖后的hashcode()要符合这一规则。
hashcode()的生成有如下的约定规则:
- 在Java应用程序执行期间,多次在同一对象上调用它时,hashCode()必须始终返回相同的整数,除非当前对象的内容被修改;从应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。
- 如果两个对象equals()后相等,它们在使用hashcode()方法后,生成的hashcode(散列码)应该相同(这就解释了如果要覆盖equals()方法的话,就要覆盖hashcode()方法)。
- 如果两个对象equals()后不相等,它们在使用hashCode()方法后,产生的散列码可以相同,也可以不同;但是,要明白:为不同的对象生成不同的hashcode(散列码)能够改善散列表的性能。
HashMap的例子不贴了,大家可以自己试一下,只要保证key满足上述特性就可以了;
关于散列表的内容,数据结构与算法(要找经典教材看)中有详细介绍,不太提倡看博客,有时候大神的思路不一定你能跟的上,书上的内容更加全面一些,更通俗易懂。
参考资料
https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.21
https://docs.oracle.com/javase/10/docs/api/java/lang/Object.html#equals(java.lang.Object)
https://www.cnblogs.com/skywang12345/p/3324958.html
https://www.jianshu.com/p/1acdfac2b4e4?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
http://www.cnblogs.com/skywang12345/p/3311899.html