java中‘==’和‘equals()’方法的有趣探索

这两天在看周志明的《深入理解java虚拟机》,受益颇多,根据书中的启示,对java中‘==’和‘equals()’方法的区别做了一些探索。

首先,为了更快地让进入状态,我们先来所几个判断题,例程如下,请判断各个System.out.println()输出的结果。

<pre name="code" class="java">	public static void main(String[] args) {
		Integer a =1;
		Integer b =2;
		Integer c =3;
		Integer d =3;
		Integer e =321;
		Integer f =321;
		Long g = 3l;
		System.out.println(c==d);
		System.out.println(e==f);
		System.out.println(c==(a+b));
		System.out.println(c.equals(a+b));
		System.out.println(g==(a+b));
		System.out.println(g.equals(a+b));
	}

输出结果如下:

true
false
true
true
true
false

之前在网上收到这样一些解释:

1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean

应用双等号(==),比较的是他们的值。

2.复合数据类型(类),用(==)进行比较的时候,比较的是他们在内存中的存放地址,对于Integer、String、Long....等类,由于装箱的处理,比较的是值。

3.equals在Object类中的默认实现和==一样,对于Integer、String、Long....等类对equals进行了重写,故而仍然比较的是值,而不是内存存放地址。

下面我们来用上述解释去解释输出结果:其中解释2可以解释第1、3、5行结果输出,却解释补了第2行输出;解释3可以解释第4行输出,却解释不了第6行输出。下面我们逐步去解答问题。

问题一:为什么值为3两个Integer对象使用==比较时返回true,而同为321的Integer对象在==时却为false?

既然你对于Integer对象使用==比较的是对象在内存中的地址,那么我们可以看看编译出来的字节码是如何标识一个Integer变量的,是否这两个对象的符号引用是一样的呢?

使用jdk自带的javap工具将例程的class文件做了格式输出,截取如下片段:

        10: iconst_3
        11: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        14: astore_3
        15: iconst_3
        16: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        19: astore        4

上面的这段字节码指令对应的是java代码中的‘Integer c = 3; Integer d = 3;’这两条指令,iconst_1指将常量1压入操作数栈中,invokestatic  指令调用Integer类的valueOf(int i)方法,并将常量1作为其参数,将Integer.valueOf(1)作为结果放入局部变量表的第1个Slot(内存单位)。从字节码可以看出,在运行时,jvm会两次调用Integer.valueOf()方法去初始化c、d变量,那我们去看看Integer.valueOf()方法的实现。

public static Integer valueOf(int i)
    {
        if(!$assertionsDisabled && IntegerCache.high < 127)
            throw new AssertionError();
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }

$assertionsDisabled变量字面理解应当断言不可用,加!后应当是断言可用(有待考证)。IntegerCache.high < 127,反编译内部类IntegerCache代码:

    private static class IntegerCache
    {

        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static
        {
            int i = 127;
           <span style="color:#6633ff;"> String s = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");</span>
            if(s != null)
            {
                int j = Integer.parseInt(s);
                j = Math.max(j, 127);
                i = Math.min(j, 2147483519);
            }
            high = i;
            cache = new Integer[(high - -128) + 1];
            int k = -128;
            for(int l = 0; l < cache.length; l++)
                cache[l] = new Integer(k++);

        }

        private IntegerCache()
        {
        }
    }

发现IntegerCache.high的取值依赖于‘java.lang.Integer.IntegerCache.high’jvm的参数设置,其取值区间为[127,2147483519],所以第一个if语句大部分情况会跳过,再看第二个if语句,当i在[-128,IntegerCache.high]区间时,将会使用混村cache中的值,这时候无论多少次调用Integer.valueOf(int i),只要传入的i是相同的,那么返回的对象引用就是一个,反之,则不是一个。那么可以猜想IntegerCache.high默认应当设置为127。这就解释了为什么值为3两个Integer对象使用==比较时返回true,而同为321的Integer对象在==时却为false。

试想我们将jvm的参数"java.lang.Integer.IntegerCache.high"调整到321,则321的==比较也应当返回true,所以很多面试题是缺少前提的~

结论:在jvm的默认设置下,值为[-128,127]的Integer对象如果使用valueOf()初始化(如:Integer a = 2; 或者 Integer b = Integer.valueOf("3"); )的话,则对象引用相同,==返回true,区间外的Integer对象不满足此规律;

可以通过设置jvm参数"java.lang.Integer.IntegerCache.high"来改变区间的最大值;

Short.java和Long.java的valueOf(long l)实现将区间写死为[-128,127];

Double.java和Float.java的valueOf()方法均采用new新对象的方式实现,故上述规律不适用。

问题二、为什么value相同的Long和Integer对象使用==比较返回了false?

这个比较简单,我们去看Long.java和Integer.java的equals()方法实现。

Long.java的equals方法实现:

    public boolean equals(Object obj)
    {
        if(obj instanceof Long)
            return value == ((Long)obj).longValue();
        else
            return false;
    }

Integer.java的equals方法实现:

    public boolean equals(Object obj)
    {
        if(obj instanceof Integer)
            return value == ((Integer)obj).intValue();
        else
            return false;
    }

上述代码一目了然:equals方法不支持类型转换,故而例程中的最后一行输出为false。

结论:基本类型对应的装箱类型均将equals方法重写,使得比较的是值,而非内存地址;

但是equals方法重写后仍然不支持数据类型的转换。

在这里极力推荐大家去看看jvm的一些底层实现,之后对以前的理解会更深。

  
时间: 2024-10-02 00:48:39

java中‘==’和‘equals()’方法的有趣探索的相关文章

【转】彻底弄懂Java中的equals()方法以及与&quot;==&quot;的区别

彻底弄懂Java中的equals()方法以及与"=="的区别 一.问题描述:今天在用Java实现需求的时候,发现equals()和“==”的功能傻傻分不清,导致结果产生巨大的偏差.所以,我决定花费时间把equals()和“==”的功能彻底弄懂,前事不忘后事之师嘛,分享给大家,希望对大家理解equals()和“==”的功能有所帮助. 二.分析探索解决问题的方法:1.Object 中的equals()方法: (1)通过查找API,说明如下: equalspublic boolean equ

Java中“==”与equals方法的区别

1. 用“==”比较两个变量,如果两个变量是基本类型变量,且都是数值类,则值相等就返回true 如果两个变量是引用型变量,则两个对象的地址一样,即指向同一个对象,则返回true 2.equals:String类对equals进行了重写:1)若是同一个对象,返回true: 2)若不是,则比较它们的值,值相同,返回true 重写代码: public boolean equals(Object anObject) { if (this == anObject) { return true; } if

如何重写Java中的equals方法

Java中,只有8种基本类型不是对象,例如:4种整形类型(byte, short, int,long),2种浮点类型(flout, double),boolean, char不是对象,其他的所有类型,不论是对象数组,列表等都扩展了Object类.了解学习Object中方法的设计原理和实现方式有助于更好的学习理解java语言.下面,我们首先学习一下Object中的equals方法. 判断两个对象相等时,JVM首先查找两个对象的hashCode, 如果两者hashCode不同,则返回false;如果

JAVA中复写equals方法

一般说来,所有类都应该复写Object中的equals方法.步骤大概分三步: 1) 判断两个对象地址是否一致 2)判断第二个对象是否为同一个子类实例 3)判断内容是否一致 class Person { public int age; public String name; public Person(int age, String name) { this.age = age; this.name = name; } public boolean equals(Object obj) { //第

JAVA中重写equals()方法的同时要重写hashcode()方法

object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true:注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码.如下:(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true (2)当obj1.ha

小结下java中==与equals方法

1 public class EqualsTest { 2 3 public static void main(String[] args) { 4 String s1 = "abc"; 5 String s2 = "abc"; 6 String s3 = new String("abc"); 7 String s4 = new String("abc"); 8 9 /* 10 * 首先要明确:==比较的是对象引用,即内存中的

JAVA中重写equals()方法为什么要重写hashcode()方法?

object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true:注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码.如下:(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true (2)当obj1.ha

java中的equals()方法

大家都知道,在Java中,对于对象的比较,如果用“==”比较的是对象的引用,而equals才是比较的对象的内容. 一般我们在设计一个类时,需要重写父类的equals方法,在重写这个方法时,需要按照以下几个规则设计: 1.自反性:对任意引用值X,x.equals(x)的返回值一定为true.2.对称性:对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;3.传递性:如果x.equals(y)=true, y.equals(z)=tr

java中的equals()方法重写

如何java中默认的equals方法跟实际不符的话,需要重写equals方法.例如: public class TestEquals {public static void main(String[] args) { Cat c1 = new Cat (1,2,3); Cat c2 = new Cat (1,2,3); System.out.println(c1==c2); System.out.println(c1.equals(c2));}} class Cat{ int color ;