(转)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 ...
随机推荐
- NLTK学习笔记(四):自然语言处理的一些算法研究
自然语言处理中算法设计有两大部分:分而治之 和 转化 思想.一个是将大问题简化为小问题,另一个是将问题抽象化,向向已知转化.前者的例子:归并排序:后者的例子:判断相邻元素是否相同(与排序). 这次总结 ...
- (转)关于BigDecimal 转化字符串toPlainString()和toString()的区别
对于 BigDecimal b ; (b=(0.4321)^ 20)String s = b.toPlainString() ;System.out.println(s) ; 输出为:0.0 ...
- JS中函数参数值传递和引用传递
也许大家对于函数的参数都不会太在意,简单来说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样.深入研究,你会发现其实没那么简单,这个传参是要分俩种情况(其实这是个错误的说法 ...
- Tomcat7配置管理员帐号密码及权限
在使用tomcat时,若要使用管理监控功能,需要用用户名密码登录使用,而tomcat7默认是将用户是注释的,所以需要配置后使用, 配置文件为根目录下的/conf/tomcat-users.xml文件. ...
- Docker Machine 简介
Docker Machine 是什么? Docker Machine 是 Docker 官方提供的一个工具,它可以帮助我们在远程的机器上安装 Docker,或者在虚拟机 host 上直接安装虚拟机并在 ...
- javascript对象(1)
今天说面向对象,嗯,不是那个对象,是这个对象. 接下来就开始今天的内容: 什么是面向对象: 就是把数据及数据的操作方法放在一起,作为一个相互依存的整体----对象.对同类对象抽象出其共性,形成类. 类 ...
- CoolBlog开发笔记第4课:数据库模型设计
教程目录 1.1 CoolBlog开发笔记第1课:项目分析 1.2 CoolBlog开发笔记第2课:搭建开发环境 1.3 CoolBlog开发笔记第3课:创建Django应用 前言 我新书<Py ...
- 实现自己的.NET Core配置Provider之EF
<10分钟就能学会.NET Core配置>里详细介绍了.NET Core配置的用法,另外我还开源了自定义的配置Provider:EF配置Provider和Yaml配置Provider.本文 ...
- PHP数组按引用传递
<?php /**PHP数组按引用传递**/ $arr = array( array('id' => 1, 'name' => 'name1'), array('id' => ...
- 【Android Developers Training】 71. 显示翻牌动画
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...