目录:
- 一、比较
- 自定义类型的比较
- 系统类型的比较
- 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、结论:
了解了上面的概念,就好得出结论了,先看图:
String内存赋值示意图第一步定义
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方法,而直接使用==就可以了。
