目录:
- 一、比较
- 自定义类型的比较
- 系统类型的比较
- String类的比较
- 包装类的比较
- 二、结论
-
==
的比较 -
equals
的比较 - 说明
-
结论:
先说结论:
-
==
:
基础类型变量:比较两个变量的 值 是否相等
引用类型变量:比较的是两个变量在 堆中对象的存储地址 是否相等,即栈中的内容 是否相等。
---> 对象地址的比较 -
equals
:
比较的是两个变量是否是对同一个对象的引用,即 堆中的内容 是否相等
---> 对象内容的比较 - 说明:
String
类对equals
方法进行了重写,在比较String
时,需要使用equals
来比较是否相等。
包装类中也对equals
方法进行了重写,比较时最好也使用此方法。
一、比较:
- 说明:由于大部分类中都重写了
equals
方法,所以下面的从两方面来说:系统类的比较和自定义类的比较。
1、自定义类型的比较:
- 先看例子:
public class Person { String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } /* @Override public boolean equals(Object obj) { if (!(obj instanceof Person)) { return false; } return this.name.equals(((Person) obj).name); } */ } public static void main(String[] args) { Person p1 = new Person("Tom"); Person p2 = new Person("Tom"); //false System.out.println("p1==p2? ==>" + (p1==p2)); //equals方法重写之前:false //equals方法重写之后:true System.out.println("p1.equals(p2)? ==>" + (p1.equals(p2))); }
例子很简单,但凡懂Java/Android的人知道,用==
比较这两个Person
绝对是不等的,因为是new
的两个对象嘛,好比你有两个女盆友:前任对象和现任对象,那绝对不是一个人!
而在重写equals
方法之前,两个对象也是不等的,起始从Person
类的父类Object
中可以看到,equals
方法中用的就是==
的比较。
public boolean equals(Object obj) {
return (this == obj);
}
但当我重写了equals
方法之后,就不一样了,两个new
出来的对象是相等的!(难道我的前任女友和现任女友是一个人?姓名都不一样嘛!)
这就是系统中的很多类都重写equals
的原因,一般的,如果两个对象中的内容完全一样,就认为这两者是同一个对象,所以我在这里,认为两个名字相同的Person
对象就是同一个。(当然,实际开发中根据情况而定)
2、系统类型的比较:
- 说明:主要是针对
String
和包装类(如Integer
等)来说。
2.1、String类的比较
-
先看例子:
public static void main(String[] args) { String a = "hello"; String b = "hello"; String c = new String("hello"); String d = c.intern(); //a==b?==>true System.out.println("a==b?==>" + (a==b)); //a.equals(b)?==>true System.out.println("a.equals(b)?==>" + (a.equals(b))); //a==c?==>false System.out.println("a==c?==>" + (a==c)); //a.equals(c)?==>true System.out.println("a.equals(c)?==>" + (a.equals(c))); //d==c?==>false System.out.println("d==c?==>" + (d==c)); //d.equals(c)?==>true System.out.println("d.equals(c)?==>" + (d.equals(c))); //d==a?==>true System.out.println("d==a?==>" + (d==a)); //d.equals(a)?==>true System.out.println("d.equals(a)?==>" + (d.equals(a))); }
-
1、分析:
-
a
和b
指向同一个字符串,是相等的,这个母庸质疑。 -
a
和c
用==
比较是不等的,这是因为c
是通过new
出来的,所以会新申请一个内存地址,是一个新对象,所以其栈内存放的地址是和a
(a
的对象在常量池红) 不同的。 - 再看最后两组比较,你会发现用
==
比较,c
是和d
不等的,而a
竟然和d
相等,这个就涉及到intern
方法的返回值含义了。insert
方法是用来 返回字符串对象的规范化表现形式 的,具体在下面解释。
-
-
2、概念:
在得出结论之前,先说一下内存中的堆和栈,以及常量池。- 栈(Stack):存放 基本类型的数据 和 对象的引用。对象本身并不存放在栈中。
- 堆(Heap):存放由
new
创建的 对象和数组。 - 常量池(Constant Pool):JVM必须为每个被装载的类型维护一个常量池。
常量池就是该类型所用到常量的一个有序集合,包括直接常量(基本类型、String)和对其他类型、字段和方法的符号引用。
-
3、结论:
了解了上面的概念,就好得出结论了,先看图:
第一步定义
a
的时候,会在常量池中放入字符串"hello"
。定义
b
的时候,由于常量池中已经有了字符串常量"hello"
,就直接指向了它。当创建字符串对象
c
的时候,会先去常量池中检查是否有字符串"hello"
,发现有了,就直接new
一个对象。
这里注意了,为什么说要先去找常量池中是否有创建的字符串呢?如果c
在a
之前定义,则会多一步操作,就是发现常量池中没有创建的字符串,会先在常量池中创建一个字符串,然后在new
对象。这应该是为了共享。通过示意图和上面的结论可以得出:
使用==
比较,比较两个值是否相等。
而由于String类重写了equals
方法,则不管是否是两个对象,只要其中的内容是相等的,就是相等的。
4、面试题:
String a = new String("hello");
创建了几个对象?
对于这个问题,就是我上面所分析的,创建几个对象,就要看其中的字符串(hello
)是否存在于常量池中(准确的说是StringPool这个池子),如果常量池中没有这个字符串,则会先在常量池中创建一个字符串常量对象,然后在堆内存中new
对象,那就是两个对象。否则会直接在堆内存中new
对象,那就是一个对象。
2.2、包装类的比较
- 先看例子:
例子1:
例子2:public static void main(String[] args) { Integer a = 127; Integer b = 127; Integer c = new Integer(127); int d = c.intValue(); int e = 127; //a==b?==>true System.out.println("a==b?==>" + (a==b)); //a.equals(b)?==>true System.out.println("a.equals(b)?==>" + (a.equals(b))); //a==c?==>false System.out.println("a==c?==>" + (a==c)); //a.equals(c)?==>true System.out.println("a.equals(c)?==>" + (a.equals(c))); //d==c?==>true System.out.println("d==c?==>" + (d==c)); //d==a?==>true System.out.println("d==a?==>" + (d==a)); //e==c?==>true System.out.println("e==c?==>" + (e==c)); }
public static void main(String[] args) { Integer a = 128; Integer b = 128; Integer c = new Integer(128); int d = c.intValue(); int e = 128; //a==b?==>false <----看这里 System.out.println("a==b?==>" + (a==b)); //a.equals(b)?==>true System.out.println("a.equals(b)?==>" + (a.equals(b))); //a==c?==>false System.out.println("a==c?==>" + (a==c)); //a.equals(c)?==>true System.out.println("a.equals(c)?==>" + (a.equals(c))); //d==c?==>true System.out.println("d==c?==>" + (d==c)); //d==a?==>true System.out.println("d==a?==>" + (d==a)); //e==c?==>true System.out.println("e==c?==>" + (e==c)); }`
这里都是用int
的包装类Integer
来说明的,其他的几种包装类都一样。
-
1、分析:
先说比较容易理解的,就是用
==
比较a
和c
不相等,因为是两个对象,所以内存地址是不等的。用
equals
比较的,都是相等的,这个是由于包装类Integer等都重写了Object
的equals
方法,比较的是其中的值是否相等。从两个例子可以看出,
a==b
的结果不同,是不是觉得很奇怪呢?这也是由于共享数据的原因,Integer共享的范围是-128 ~ 127
。这个可以看一下Integer的源码中的valueOf
方法。再看两个例子中的最后三个比较,
int
和Integer
的比较,只要是值相等,就都是相等的。这里涉及到一个自动拆箱的过程。
-
2、概念:
-
共享数据:java为了优化内存,提高性能,就单开了一片内存池(pool),也就是说,在这里共享了那些固定不变的数据(我个人理解),如数字和字符串等等,这也是一种对象。
重点说
Integer
和int
,在内存池中定义他们的范围是-128 ~ 127
,这里的数是共享的,其实共享的是地址,就是变量指向的地址。(题外话:变量其实都是指向的地址,地址才是代表一块内存的空间的。)java为了提高效率,初始化了-128--127
之间的整数对象,所以,如果写Integer a =100;
的话,是在内存池中创建了一个变量为a的对象,再写b=100
,就共享了100这个数据,其实是指向了相同地址。但是如果超过了这个范围的话,这数据就不是共享的了,指向的不是相同地址。所以就相等了。 -
自动封箱和自动拆箱:
先看一个小栗子:public static void main(String [] args) { Integer x = 4; x = x + 2; System.out.println("x" + x); }
这里面就涉及到了自动拆箱和自动封箱的过程:
1、自动封箱:其中Integer x = 4;
就是自动封箱的过程,即Integer x = new Integer(4);
2、自动拆箱:其中x+2
就是进行了自动拆箱的过程,将x
变为int
类型,再和2进行加法运算,在拆箱的时候,要先进行x.intValue()
的判断,是否x
为null
,不可为null
,否则编译失败。
3、自动封箱:再将求的和进行封装,赋值给x
-
-
3、结论:
- 使用
equals
比较的包装类,比较的是其中的 值 是否相等。 - 使用
==
比较的包装类,如果在共享范围内-128~127
,则比较的是两个 值 是否相等。否则比较的是对象的 存储地址 是否相等。
- 使用
二、结论
1、== 的比较:
- 基础类型变量:比较两个变量的 值 是否相等
- 引用类型变量:比较的是两个变量在 堆中对象的存储地址 是否相等,即 栈中的内容 是否相等。---> 对象地址的比较
2、equals的比较:
- 比较的是两个变量是否是对同一个对象的引用,即 堆中的内容 是否相等。
---> 对象内容的比较
3、说明:
- String类对equals 方法进行了重写,在比较String时,需要使用equals来比较是否相等。
- 包装类中也对equals 方法进行了重写,比较时最好也使用此方法。
- 对象的比较,基本都是用
equals
来比较是否相等。 - 在自定义的类中,要依据情况来决定是否需要重写
equals
方法。
- 如果无法判断两个对象(比如a和b)是否某一个为
null
时,可以通过Objects.equals(a, b)
来进行比较,其内部是对a
进行null
值判断,然后才比较的a.equals(b)
。 - 在Java规范中说明,在比较两个枚举类型的值时,永远不需要调用
equals
方法,而直接使用==
就可以了。