开始本文之前,先让我们记住一个口诀(这个口诀只针对基础的类比如String、Integer等,如果是自定义的类,需要看equal的具体实现):

equal比较其值,== 比较地址

这两天在走查代码的时候发现一个童鞋,在判断两个Integer类型的值是否相等的时候,用了==来判断,运行结果没错,因为这两个值在-128~127之间。 只是这种写法不符合规范,有隐患,我不是挑刺,但是觉得有必要给出个所以然来为啥要按照规范使用equal来比较对象值是否相等。

让我们先来看个笔试的例子:请问写出下面的代码输出结果。

  1. public class JavaTest {
  2. public static void main(String args[]){
  3. Integer a = 127;
  4. Integer b = 127;
  5. Integer c = new Integer(127);
  6. Integer d = new Integer(127);
  7. int e = 127;
  8. // 1
  9. System.out.println(a == b);
  10. // 2
  11. System.out.println(a.equals(b));
  12. // 3
  13. System.out.println(a == c);
  14. // 4
  15. System.out.println(a.equals(c));
  16. // 5
  17. System.out.println(a == e);
  18. // 6
  19. System.out.println(a.equals(e));
  20. a = 200;
  21. b = 200;
  22. // 7
  23. System.out.println(a == b);
  24. // 8
  25. System.out.println(a.equals(b));
  26. }
  27. }

答案是:

true

true

false

true

true

true

false

true

运用口诀:equal比较其值,==比较地址

第12行输出:true;因为它们的引用地址是同一个,这个地址存的值是127。

第14行输出:true;因为它们的值都是127,所以equal比较其值,结果是相等。

第16行输出:false;因为它们两个对象的引用地址不一样。

第18行输出:true;同上面的第2处。

第20行输出:true;这里会把e做装箱操作(从基本类型提升为包装对象类型),之后它们的引用地址是同一个,同第1处,后面解析java装箱、拆箱的概念。

第22行输出:true;此处一样会对e装箱提升为Integer对象,然后它们的值都是127,所以equal结果为true。

第28行输出:false;因为它们的引用地址不一样,它们不一样~,跟第1处不一样…下面会讲解。

第30行输出:true;因为它们的值都是200,同前面第2、4、6。

  • 前面讲到java自动装箱、拆箱 ,简单点说,装箱就是把基本类型升级为对象包装类型;拆箱就是把对象包装类型转换为对应的基本类型。

会自动拆箱、装箱的数据类型为:

对象类型 基本类型
Integer int (4字节)
Short short (2字节)
Float float (4字节)
Double double (8字节)
Byte byte (1字节)
Long long (8字节)
Character char (2字节)
Boolean boolean (未定)

下面让我们来看下具体的装箱、拆箱过程:我们编写一个Test类文件保存在Test.java文件中,UTF-8编码存储,内容如下:

  1. public class Test{
  2. public static void main(String[] args) {
  3. // 装箱
  4. Integer numInteger = 120;
  5. // 拆箱
  6. int num_int = numInteger;
  7. }
  8. }

我们编译一下:

javac Test.java

如果提示“GBK编码…”之类的错误,在指令加个编码选项:

javac -encoding utf-8 Test.java

然后会在当前目录生成一个Test.class文件,我们通过javap工具反汇编看下这个class的内容:

javap指令这里就不展开了,感兴趣的可以看文档:Javap官方文档

javap -c Test.class

上面的指令会直接显示反编译的内容,或者你也可以通过下面的指令来把反汇编的内容输出到文件中,比如输出当前目录的Test.bin文件里面:

javap -c Test.class>Test.bin

反汇编的内容如下:

  1. Compiled from "Test.java"
  2. public class Test {
  3. public Test();
  4. Code:
  5. 0: aload_0
  6. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  7. 4: return
  8. public static void main(java.lang.String[]);
  9. Code:
  10. 0: bipush 120
  11. 2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  12. 5: astore_1
  13. 6: aload_1
  14. 7: invokevirtual #3 // Method java/lang/Integer.intValue:()I
  15. 10: istore_2
  16. 11: return
  17. }

执行第6行代码Integer numInteger = 120;时,系统底层是执行了Integer.valueOf 来把数值120转换为一个新的Integer包装对象类型。

执行第9行代码int num_int = numInteger;的时候,系统底层实际是执行了Integer.intValue 来把包装对象numInteger的intValue值取出来赋值给int类型变量num_int:

  • 那前面的JavaTest类中第12行跟28行的结果为啥不一样?
  1. Integer a = 127;
  2. Integer b = 127;
  3. // 1、结果为true
  4. System.out.println(a == b);
  5. a = 200;
  6. b = 200;
  7. // 7、结果为false
  8. System.out.println(a == b);

感觉有点坑吧?我觉得出这个题目的考官是不是有点刁钻呀… 这个并没有啥神秘的,我们直接看源码吧。

当代码中第1处的括号运算:a == b的时候 ,根据口诀,是判断这两个包装对象的地址是否一样,根据上面的反汇编代码可以看出来,代码中的Integer a = 127;对应的操作是Integer a = Integer.valueOf (127); Integer类的源码的valueOf如下:

  1. public final class Integer extends Number implements Comparable<Integer> {
  2. // ......
  3. /**
  4. * Cache to support the object identity semantics of autoboxing for values between
  5. * -128 and 127 (inclusive) as required by JLS.
  6. *
  7. * The cache is initialized on first usage. The size of the cache
  8. * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
  9. * During VM initialization, java.lang.Integer.IntegerCache.high property
  10. * may be set and saved in the private system properties in the
  11. * sun.misc.VM class.
  12. */
  13. private static class IntegerCache {
  14. static final int low = -128;
  15. static final int high;
  16. static final Integer cache[];
  17. static {
  18. // high value may be configured by property
  19. int h = 127;
  20. String integerCacheHighPropValue =
  21. sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
  22. if (integerCacheHighPropValue != null) {
  23. try {
  24. int i = parseInt(integerCacheHighPropValue);
  25. i = Math.max(i, 127);
  26. // Maximum array size is Integer.MAX_VALUE
  27. h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
  28. } catch( NumberFormatException nfe) {
  29. // If the property cannot be parsed into an int, ignore it.
  30. }
  31. }
  32. high = h;
  33. cache = new Integer[(high - low) + 1];
  34. int j = low;
  35. for(int k = 0; k < cache.length; k++)
  36. cache[k] = new Integer(j++);
  37. // range [-128, 127] must be interned (JLS7 5.1.7)
  38. assert IntegerCache.high >= 127;
  39. }
  40. private IntegerCache() {}
  41. }
  42. /**
  43. * Returns an {@code Integer} instance representing the specified
  44. * {@code int} value. If a new {@code Integer} instance is not
  45. * required, this method should generally be used in preference to
  46. * the constructor {@link #Integer(int)}, as this method is likely
  47. * to yield significantly better space and time performance by
  48. * caching frequently requested values.
  49. *
  50. * This method will always cache values in the range -128 to 127,
  51. * inclusive, and may cache other values outside of this range.
  52. *
  53. * @param i an {@code int} value.
  54. * @return an {@code Integer} instance representing {@code i}.
  55. * @since 1.5
  56. */
  57. public static Integer valueOf(int i) {
  58. if (i >= IntegerCache.low && i <= IntegerCache.high)
  59. return IntegerCache.cache[i + (-IntegerCache.low)];
  60. return new Integer(i);
  61. }
  62. // ......
  63. }

从上面的源码可以看出来,对于-128 ~ 127之间(闭包)的int值,通过valueOf得到其对应的Integer对象,是已经预先静态生成好的存放在数组中的对象,所以只要这个int值是在这区间范围的,得到的对象都是同一个。而如果传过来的int值不在 -128 ~ 127 区间范围,直接在最后new 一个新的Integer对象返回的,这就解释了为啥两个自动装箱数值为200的对象,用==来判断其地址,是不一样的,因为两个对象都不一样了。

这里JDK应该是对于这些1字节长度的数值,总共28 = 256个,从-128~127,预先存起来,加快包装的速度、节省存储空间。那为啥不把两个字节、三个字节的都预先存起来呢?who knows?… 那为啥不把float、double的值也存起来?这两种类型的区间数是无限的好吗,看官你别逗了…

综上所述:

  • == 对于基本类型的数值,是比较它们的值是否相等;对于包装对象这种引用型的变量,比较的是它们在堆中存储的地址是否相同。
  • equals 表示这两个变量的value数值是否相同。

如果是自定义的类调用equal,这个需要具体看此类的equal有没有重写,具体如何实现的,比如:

  1. public class Test extends Object{
  2. int num = 0;
  3. public Test(int i){
  4. this.num = i;
  5. }
  6. public static void main(String[] args) {
  7. Test t1 = new Test(1);
  8. Test t2 = new Test(1);
  9. System.err.println(t1.equals(t2));
  10. System.err.println(t1 == t2);
  11. }
  12. }

上面的程序执行输出为:

false

false

第二个是false,这个很容易理解,但是第一个为啥呢,不是说好了equal是比较值么?但是这个Test自定义类并没有重写equal方法,所以调用的是Object里面的equal,我们看下它的源码:

也就是,Object的equal实现默认还是通过 == 来实现的… 明白了吧,所以我们自己来实现equal如下:

  1. public class Test extends Object{
  2. int num = 0;
  3. public Test(int i){
  4. this.num = i;
  5. }
  6. @Override
  7. public boolean equals(Object obj) {
  8. if(!(obj instanceof Test) || null == obj){
  9. return false;
  10. }
  11. return (num == ((Test) obj).num);
  12. }
  13. public static void main(String[] args) {
  14. Test t1 = new Test(1);
  15. Test t2 = new Test(1);
  16. System.err.println(t1.equals(t2));
  17. System.err.println(t1 == t2);
  18. }
  19. }

这样,就能实现equal比较的是值了,跟前面的口诀保持一致了。

【JAVA基础】一:聊聊笔试常见到的 “==、equal” 比较是否相等的内在差别的更多相关文章

  1. Java基础——面试、笔试

    网址来源: http://www.nowcoder.com/discuss/5949?type=0&order=0&pos=4&page=2 参考资料:(java方面的一些面试 ...

  2. 【Java基础】浅谈常见设计模式

    Num1:单例模式 基本概念:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 常见写法: 懒汉式 public class Singleton { /* 持有私有静态实例,防止被引用,此处赋值 ...

  3. Java基础常见英语词汇

    Java基础常见英语词汇(共70个) ['ɔbdʒekt] ['ɔ:rientid]导向的                             ['prəʊɡræmɪŋ]编程 OO: object ...

  4. Android开发面试经——3.常见Java基础笔试题

      Android开发(29)  版权声明:本文为寻梦-finddreams原创文章,请关注:http://blog.csdn.net/finddreams 关注finddreams博客:http:/ ...

  5. 聊聊java基础,int值强制类型转换成byte

    聊聊java基础,int值强制类型转换成byte 知识点:byte.short.char在表达式中会自动提升为int 之前做一个应用时,打印IP地址,因为是用4个byte存储的,所以打印的时候值范围是 ...

  6. Java英文单词Java基础常见英语词汇

    Java英文单词Java基础常见英语词汇(共70个)                                                                          ...

  7. Java基础-JAVA中常见的数据结构介绍

    Java基础-JAVA中常见的数据结构介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是数据结构 答:数据结构是指数据存储的组织方式.大致上分为线性表.栈(Stack) ...

  8. Java基础-数组常见排序方式

    Java基础-数组常见排序方式 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 数据的排序一般都是生序排序,即元素从小到大排列.常见的有两种排序方式:选择排序和冒泡排序.选择排序的特 ...

  9. Java基础语法(8)-数组中的常见排序算法

    title: Java基础语法(8)-数组中的常见排序算法 blog: CSDN data: Java学习路线及视频 1.基本概念 排序: 是计算机程序设计中的一项重要操作,其功能是指一个数据元素集合 ...

随机推荐

  1. Centos7下安装Docker

    1.首先卸载旧版: yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ ...

  2. Spring-Boot项目部署到单独tomcat运行

    前言: 本文是对学习SpringBoot过程中的笔记,拿最简单的项目进行部署,大家可以进行类比,文章最后会提供部署前和部署后的github地址,用代码做的笔记,可能会很乱,有兴趣的同学可以参考 正文: ...

  3. [译]Ocelot - Tracing

    原文 Ocelot是使用的Butterfly这个项目来实现这个的. 在ocelot要使用tracing, 首先得安装相应的包: Install-Package Ocelot.Tracing.Butte ...

  4. 清除Windows访问共享时保存的凭据记录

    场景:某些时候我们连接了某台PC或服务器的共享目录或打印机,但因为一些原因突然连接不上了(或是对方组策略发生变化,或是对方计算机用户密码变更 等等..) 又或者电脑给其它用户使用,因一些安全问题需要临 ...

  5. swoole简单demo测试

    测试代码 1.server.php: <?php $serv = new swoole_server("0.0.0.0", 9502); $serv->on('conn ...

  6. TED学习笔记

    20180621(如何做得更好) 多一些探索,询问,聆听,奋斗,成就自己,比一味的做事儿有效的多. 1.刻意让自己的生活在两个区域交替:(1)学习区Learning Zero (2)执行区Perfor ...

  7. 集合各个实现类的底层实现原理 ----- 原文地址:https://blog.csdn.net/qq_25868207/article/details/55259978

    ArrayList实现原理要点概括 参考文献: http://zhangshixi.iteye.com/blog/674856l https://www.cnblogs.com/leesf456/p/ ...

  8. thymeleaf : input/select/radio回显

    thymeleaf中不用自己去写checked="checked" selected="selected"这种代码,他自己会选. input <input ...

  9. Bootstrap3 模态框 select2搜索框无法输入

    <div class="modal fade" role="dialog" aria-hidden="true" data-backd ...

  10. hiho 1098 最小生成树二·Kruscal算法 (最小生成树)

    题目: 时间限制:10000ms 单点时限:1000ms 内存限制:256MB   描述 随着小Hi拥有城市数目的增加,在之间所使用的Prim算法已经无法继续使用了——但是幸运的是,经过计算机的分析, ...