(转)Java中equals和==的区别
背景:学习辉哥总结的基础知识,从头来,直面短板。
1 问题引入及分析
请看下面的代码清单1
@Test
public void test01() {
String a = "a" + "b" + 1;
String b = "ab1";
System.out.println(a == b);
}
上述这段代码来源自谢宇编著的书籍《Java特种兵》上册。
代码清单1中的输出是
true
这是个考察Java基本功的问题,类似的问题还有很多,如清单2:
public static String getA() { return "a"; }
@Test
public void test2(){
String a = "a";
final String c = "a";
String b = a + "b";
String d = c + "b";
String e = getA() + "b";
String compare = "ab";
System.out.println(b == compare);
System.out.println(d == compare);
System.out.println(e == compare);
}
结果:
false
true
false
要理解这个问题,首先需要搞清楚这些:
- 关于“==”是做什么的?
- a和b在内存中是什么样的?
- 编译时的优化方案
1.1 关于“==”
“==”是用于匹配内存单元上的内容,在Java语言中,当使用“==”匹配时,其实就是对比两个内存单元的内容是否一样。
如果是原始类型byte、boolean、short、char、int、long、float、double,就是直接比较它们的值。
如果是引用(Reference),比较的就是引用的值,“引用的值”可以被认为是对象的逻辑地址。
如果两个引用发生“==”操作,就是比较相应的两个对象的地址值是否一样。
换一句话说,如果两个引用所保存的对象是同一个对象,则返回true,否则返回false。如果a、b两个引用都指向null,则也是返回true。
1.2 编译时的优化
在代码清单1中,a引用是通过“+”赋值的,b引用是通过直接赋值的,那么为什么a和b两个引用会指向同一个内存单元?这就是JVM的“编译时优化”。
当编译器在编译代码 String a = "a" + "b" + 1 时,会将其编译成 String a = "ab1" ,因为都是“常量”,编译器认为这3个常量叠加会得到固定的值,无需运行时再进行计算,所以就会这样优化。
类似的还有 int i = 2 * 3 + 1,并不是在实际运行时再计算i的值,而是在编译时直接变成了i=7。
而在代码清单2中,b与compare比较时,由于compare是个常量,而b不是常量,原因是b = a + "b",a并不是一个常量,a中的值可以修改,因此编译器不会进行优化。
变量c有一个final修饰符,从定义上约束了c是不允许改变的,因此编译器会进行优化。
变量e的值来自一个方法,虽然方法内返回一个常量的引用,但是编译器并不会去看方法内部做了什么,所以编译器不会进行优化。
常量时会进行“编译时优化”
1.3 关于“equals()”
首先来看看Object中的equals()方法的实现:
public boolean equals(Object obj)
{
//调用equal的对象的地址和参数对象的地址是否相等
return (this == obj);
}
从源码中可以看出,Object类中equals()方法是使用“==”来匹配的,也就是说,如果不去重写equals()方法,那么默认的equals()操作就是对比对象的地址。
equals()方法的存在就是希望子类去重写这个方法的 ,而在String类中,就重写了equals()方法:
public boolean equals(Object anObject) {
// 如果是同一个对象
if (this == anObject) {
return true;
}
// 如果传递进来的参数是String类的实例
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = count;// 字符串长度
if (n == anotherString.count) // 如果长度相等就进行比较
{
char v1[] = value;// 取每一个位置的字符
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) // 对于每一位置逐一比较
{
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
在JDK 1.6中,String.equals()的代码逻辑大致是这样的:
1. 判定传入的对象和当前对象是否为同一个对象,如果是就直接返回true;
2. 判定传入的对象类型是否为String,若不是则返回false;
3. 判定传入的String与当前String的长度是否一致,如不一致就返回false;
4. 循环对比两个字符串的char[]数组,逐个比较字符是否一致,如果不一致就直接返回false;
5. 循环结束都没有找到不匹配的,最后返回true。
覆写equals时的相关准则
自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true, 并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false, 前提是对象上 equals 比较中所用的信息没有被修改。
非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false
2 先前的总结
java中的数据类型,可分为两类:
1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean
他们之间的比较,应用双等号(==),比较的是他们的值。
2.复合数据类型(类)
当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。
JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地 址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。 (注意这里说的,String类本身在实现equals()方法时候就覆盖了自身的equals,文章的末尾有其源码的实现)
对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。(对于普通的类还是按照这个标准的)
public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = "Monday";
if (s1 == s2) {
System.out.println("s1 == s2");
}else {
System.out.println("s1 != s2");
}
}
}
编译并运行程序,输出:
s1 == s2
说明:s1 与 s2 引用同一个 String 对象 -- "Monday"!
2.再稍微改动一下程序,会有更奇怪的发现:
@Test
public void test04(){
String s1 = "Monday";
String s2 = new String("Monday"); if (s1 == s2) {
System.out.println("s1 == s2");
}else {
System.out.println("s1 != s2");
} if (s1.equals(s2)) {
System.out.println("s1 equals s2");
} else {
System.out.println("s1 not equals s2");
}
}
输出:
s1 != s2
s1 equals s2
说明s1 和s2 分别引用的是两个不同的对象。但是由于String 类覆写了equals(),所以实际比较的是字符串的内容。
3. 字符串缓冲池(这个概念可以参考认识java中的堆和栈)
原来,程序在运行的时候会创建一个字符串缓冲池。当使用 s2 = "Monday" 这样的表达是创建字符串的时候,程序首先会在这个String缓冲池中寻找相同值的对象,在第一个程序中,s1先被放到了池中,所以在s2被创建的时候,程序找到了具有相同值的 s1,将s2引用s1所引用的对象"Monday"
第二段程序中,使用了 new 操作符,他明白的告诉程序:"我要一个新的!不要旧的!"于是一个新的"Monday"Sting对象被创建在内存中。他们的值相同,但是位置不同,一个在池中游泳一个在岸边休息。哎呀,真是资源浪费,明明是一样的非要分开做什么呢?(这就是为啥==不一致的原因,如果不是string类型重写equals的话,这个两个对象的equals结果也会不等)
4.再次更改程序:
@Test
public void test05(){
String s1 = "Monday";
String s2 = new String("Monday");
s2 = s2.intern(); //intern()方法操作过程 if (s1 == s2) {
System.out.println("s1 == s2");
}else {
System.out.println("s1 != s2");
} if (s1.equals(s2)) {
System.out.println("s1 equals s2");
} else {
System.out.println("s1 not equals s2");
}
}
s1 == s2
s1 equals s2
原 来,(java.lang.String的intern()方法"abc".intern()方法的返回值还是字符串"abc",表面上看起来好像这个方 法没什么用处。但实际上,它做了个小动作:检查字符串池里是否存在"abc"这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会 把"abc"添加到字符串池中,然后再返回它的引用。)
hashcode和equals之间的关系
(转)从一道面试题彻底搞懂hashCode与equals的作用与区别及应当注意的细节
(转)Java中equals和==的区别的更多相关文章
- java中equals和==的区别 (转)
java中equals和==的区别 值类型是存储在内存中的堆栈(以后简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中. ==操作比较的是两个变量的值是否相等,对于引 ...
- 【转】Java中equals和==的区别
[转]Java中equals和==的区别 java中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boole ...
- 关于java中equals与==的区别的小实验
java中equals与==经常容易混淆,简单一点说就是equals比较的是值是否相等,是一种方法,==比较的两个对象在JVM中的地址,是一种操作符. 做了几个小实验比较结果. 实验一: String ...
- java中.equals和==的区别?
Java中的equals是十分重要的,和= =要区别开来,孙卫琴的JAVA面向对象编程一书对这个做了阐述,现在小结其主要内容,而且要将 = =和 equals列为重要的对比概念来学习 1.声明格式 ...
- Java 中 Equals和==的区别(转)
另外一篇参考: https://blog.csdn.net/striverli/article/details/52997927 在谈论equals和==的区别前,我们先简单介绍一下JVM中内存分配的 ...
- Java中equals和==的区别?为什么重写equals方法后,一定要重写hashCode方法?
首先明确一点,equals是方法,==是操作符. 1. 如果比较的是基本数据类型: 只讨论==,因为equals是不存在的,因为java中基本数据类型不能调用method的. 2. 如果比较的是引用类 ...
- java中equals和==的区别详解
java中的数据类型,可分为两类: 1.基本数据类型. byte,short,char,int,long,float,double,boolean这八大原始数据类型他们之间的比较,使用“==”,比较的 ...
- java中equals和"=="的区别
"=="号,它比较的是一个对象在内存中的地址值, 比如2个字符串对象String s1 = new String("str");String s2 = new ...
- Java中equals和“==””的区别,String特殊
public class TestString { /* * java中的数据类型,可分为两类: * 1.基本数据类型,也称为原始数据类型.byte,short,char,int,long,float ...
随机推荐
- Python给小说做词云
闲暇时间喜欢看小说,就想着给小说做词云,展示小说的主要内容.开发语言是Python,主要用到的库有wordcloud.jieba.scipy.代码很简单,首先用jieba.cut()函数做分词,生成以 ...
- MYSQL导入数据报错|MYSQL导入超大文件报错|MYSQL导入大数据库报错:2006 - MySQL server has gone away
导SQL数据库结构+数据时,如果数据是批量插入的话会报错:2006 - MySQL server has gone away. 解决办法:找到你的mysql目录下的my.ini配置文件(如果安装目录没 ...
- 友盟分享到微信 监听不执行 监听只执行onStart,(onResult,onError,onCancel 不执行)
最近在做一个项目 有一个需求是要分享项目中的一个商品 这对于我来说简直是 so easy (项目是三个人一起写的) 正好看到之前有同事写完了 我就拿过来用吧 一顿复制粘贴 大功告成 这个是监 ...
- Thread初探
Thread初探 前言 以前大家写的都是单线程的程序,全是在main函数中调用方法,可以清楚的看到它的效率是特别低的,就像python中使用单线程取爬一个网站,可以说能让你等的吐血,因为数据量实在太大 ...
- MVP架构
一.介绍 MVP(Model View Presenter)架构是从著名的MVC(Model View Controller)架构演变而来的.对于在Android应用中开发就可以视为是MVC架构,布局 ...
- tcpdf导出pdf数据支持中文的解决方案
步骤如下:1.确保你测试tcpdf能正常输出英文内容的pdf2.测试输入中文内容后显示是?的乱码或者空白分析原因,是因为我们输入的中文,tcpdf字体库并不支持,因此乱码或者空白显示 添加一个合适的字 ...
- 使用spring mvc返回JSON,chrome可以,firefox不行的问题定位
转载http://ks.netease.com/blog?id=4024 作者:李景 场景: 前端Post请求同一个url地址,在chrome浏览器上有正常返回json,而在 ...
- 【Android Developers Training】 18. 重新创建一个Activity
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- Kotlin入门第四课:简单工厂模式
Kotlin基础知识的学习,请参考之前的文章: Kotlin入门第一课:从对比Java开始 Kotlin入门第二课:集合操作 Kotlin入门第三课:数据类型 初次尝试用Kotlin实现Android ...
- JavaScript函数部分
函数部分学习:+parseInt(): -parseInt()函数将其收到的任何输入值(通常是字符串)转换成整数类型输出,如果转换失败就返回NaN. -parseInt(“参数”,第二参数基数):没有 ...