“==”
首先要讲下Java中的数据类型,分为俩类
基本数据类型(或者叫值类型、也叫原始数据类型),比如byte、int、double、boolean等,他们之间的比较就是使用“==”,这时候对比的是他们的值是否相等
int a = 10;
int b = 10;
System.out.print("a==b? " + (a == b));
//输出结果 a==b? true
引用数据类型,比如数组、类、接口等,这些数据直接使用“==”的话,进行比对的是其引用地址是否相同(这里对于String的比较后面再分析)
Integer aObj = new Integer(10);
Integer bObj = new Integer(10);
System.out.print("aObj==bObj? "+(aObj == bObj));
//输出结果 aObj==bObj? false
下面我们对“==”比较的一些情况进行分析,大家都看到了,我们用到了Integer,有必要跟大家说一下这个Integer是什么,这就要涉及到Java中的另一个概念了,包装类,每中基本数据类型都会对应有一个包装类,为了让基本数据类型也能满足万事万物皆对象嘛,其实包装类的出现对我们的很多操作都方便了。说到它那就得说它的2个概念,封箱和拆箱,简单解释就是,将基本数据类型封箱成对应的包装类,拆箱就是将包装类转换成对应的基本类型。
说了这么个概念,那到底对我们理解“==”有什么用呢?下面看下基本类型与包装类之间的相互比较的结果:
Integer aObj = new Integer(10);
Integer bObj = 100;
Integer cObj = 100;
int a = 10;
int b = 100;
System.out.println("aObj==a? " + (aObj == a)); //true
System.out.println("bObj==cObj? " + (bObj == cObj)); //true
System.out.println("bObj==b? " + (bObj == b)); //true 与第一个含义相同
对于上面的结果分析:
第一个 aObj == a 为true,那是因为基本数据类型与对应的包装类对象进行“==”比较的时候,包装类型会进行自动拆箱操作,拆箱成基本数据类型,所以最终比较的还是他们的值。
第二个 bObj == cObj 为true,这其中的原由要牵扯到Java编译,Java在编译的时候会对包装类型赋值基本数据转换成Integer.valueOf(基本数据); 也就是说 Integer bObj = 100;在class文件里面的含义就变成了 Integer bObj = Integer.valueOf(100);大家可以点进去看下valueOf的源码:
通过源码可知,对于-128到127之间的数,Java会进行缓存,Integer bObj = 100;时,会将100进行缓存,下次再写Integer cObj = 100;时,就会直接从缓存中取,就不会new了。所以这2个对象的引用地址都是指向第一次创建的那个100的对象地址。
总结:“==”对于基本数据类型进行比较的就是他们的值是否相等,而对于引用类型进行比较的就是对象在内存地址的值,而我们知道Java中对象的创建是存放在堆中的,而对象存放的地址是存储在栈中,所以这个比较也就是对比栈中的值是否相等。
“equals”
这个方法是Object上帝类中的,我们直接去看看它长什么样子
我们可以看出,这个和上面讲的 “==” 其实是一个意思,所以当我们没有对equals进行重写的时候,也就是使用的是Object类中的equals方法的时候,其含义与“==”对比一样。
那么问题来了,既然是一样,为何还要多一个存在呢???其实这不是多,因为我们知道单纯的“==”对比是不能满足我们的对比需求的,所以我们一般都会对equals进行重写。
而提到equals,Java规范中对其有很多的规定,但是有一点我们必须注意的是,重写equals的时候必须要重写hashCode方法,给出的理由是 为了维持hashCode的一种约定,相同的对象必须要有相同的hashCode值,但是我们先分析下hashCode这个方法的实际作用我们再来讲equals和hashCode之间的关联,因为这里要分2种情况,并不是Java规范中那么硬性规定他们必须关联,看了他的作用之后你也就知道他们什么时候该一起重写,什么时候可以不管hashCode。
那么我们这时候就得说下什么是hashCode方法的作用了,hashCode方法的作用是获取哈希码,也叫做散列码;实际上就是一个int整数,该哈希码的作用就是确定对象在哈希表中的索引位置,这里出现的散列表概念我们就不展开了,一会也说不明白,大家只要知道大概知道Java中的散列表类主要是指集合,比如HashMap、HashTable、HashSet等,而哈希码只有在这些类中才会起作用,其他情况下就无用了,下面稍微概括一下哈希码在散列表中是如何起作用的:
我们都知道,散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了哈希码! 散列表的本质是通过数组实现的。当我们要获取散列表中的某个“值”时,实际上是要获取数组中的某个位置的元素。而数组的位置,就是通过“键”来获取的;更进一步说,数组的位置,是通过“键”对应的哈希码计算得到的。这个如果去详细了解集合的时候会讲到的。
接下来有了上面的理解,那么我们就可以分2部讲equals重写了
第一种 不会创建“类对应的散列表”
这里所说的“不会创建类对应的散列表”是说:我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!这种情况下,equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用,所以,不用重写hashCode()。
第二种 会创建“类对应的散列表”
这就跟上面的相反了,类会用在集合里面,这时候该类的“hashCode() 和 equals() ”是有关系的:
1)、如果两个对象相等,那么它们的hashCode()值一定相同。
这里的相等是指,通过equals()比较两个对象时返回true。
2)、如果两个对象hashCode()相等,它们并不一定相等。
因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。
此外,在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。
例如,创建Person类的HashSet集合,必须同时覆盖Person类的equals() 和 hashCode()方法。 如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。
总结:equals方法的使用,默认使用Object的equals方法,等价于使用“==”进行对比,而重写的话根据自己的逻辑去判断是否相等,注意后面涉及到的hashCode,虽然第一种情况下equals与hashCode方法没有关联,但是我们还是尽可能的去一起重写他们,因为你也没法说你写的这个类肯定不会用map去存储。
写得不好的地方多多海涵,有错误的地方欢迎留言区交流指正!
借鉴文章:https://www.cnblogs.com/skywang12345/p/3324958.html
编程不仅是一门技术!
编程更加是一门艺术!