背景:学习辉哥总结的基础知识,从头来,直面短板。

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

要理解这个问题,首先需要搞清楚这些:

  1. 关于“==”是做什么的?
  2. a和b在内存中是什么样的?
  3. 编译时的优化方案

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

参考:面试官爱问的equals与hashCode

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和==、hashcode的区别的更多相关文章

  1. Java中equals和hashcode的区别?

    Java中equals和hashcode方法是在Object对象中的,所以每个对象都有这两个方法,大多数时候我们为了实现特定需求需要重写这两个方法 equals和hashcode方法常用在同一个类中用 ...

  2. java中equals和hashCode方法随笔二

    前几天看了篇关于java中equals和hashCode方法的解析 1.Object类中的equals方法和hashCode方法. Object类中的equals和hashCode方法简单明了,所有的 ...

  3. java中equals和hashCode方法的解析

    解析Java对象的equals()和hashCode()的使用 前言 在Java语言中,equals()和hashCode()两个函数的使用是紧密配合的,你要是自己设计其中一个,就要设计另外一个.在多 ...

  4. Java中equals,hashcode

         在Java语言中,Object对象中包含一个equals和hashCode方法,其中hashCode方法是由JVM本地代码(native code)实现的,返回值是一个有符号的32位整数,对 ...

  5. java中equals与hashCode的重写问题

    这几天有一个朋友问我在重写equals和hashCode上出现了问题,最后我帮她解决了问题,同时也整理出来分享给大家 现上Object的equals与HashCode的代码 public boolea ...

  6. Java中equals和==之间的区别

    今天在写表达式求值的时候,发现了equals和==||!=和!equals()之间是不一样的. 我就从网上搜了搜关于这方面的知识,然后在下面做一个总结: Java中有两类数据类型: 基本数据类型(Pr ...

  7. (转)java中equals和等号(==)的区别浅谈

    java中的数据类型,可分为两类:1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boolean   他们之间的比较,应用双等号(==) ...

  8. Java中equals()和hashCode()的关系以及重写equals()和hashCode()的重要性

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6580647.html  一:关系 如果两个对象相等(equal),它们的hashcode一定相同: 如果两个对 ...

  9. Java中equals,hashcode,==的区别

    ==  :比较java栈局部变量表中变量的地址或值是否相等. equals : 比较变量的地址在java堆中引用对象是否为同一个对象. hashcode : 通过对象在JVM内存中的存储地址通过特定算 ...

  10. java中equals,hashcode和==的区别

    https://www.cnblogs.com/kexianting/p/8508207.html

随机推荐

  1. Spring RPC 入门学习(1)-HelloWorld入门

    Spring搭建RPC环境 第一,下载所需要的jar包,下载地址:https://yunpan.cn/cPErQeANrSMyB (提取码:63e5),见下图: 第二,新建动态WebProject,把 ...

  2. s标签s:if和s:set实现一个表格显示为多个表格

    1.首先本来这个表格是这样的 2.这时候代码是这样的 <table cellpadding="4"> <tr> <th>指标点</th&g ...

  3. 20150421 作业5 四则运算 测试与封装 5.1 5.2(doing)

    结伴队友:王佳寧,他的博客地址:http://www.cnblogs.com/paopaotai/ 5.2 黑白盒測試 測試項目名稱 黑盒測試 測試人員 葉子鵬&王佳寧 測試編號 測試頁面 測 ...

  4. 11-Python3从入门到实战—基础之生成器和迭代器

    Python从入门到实战系列--目录 切片 Python提供切片(Slice)操作符用来获取列表.元组等数据中的部分元素:如,读取列表 list[m:n]:表示获取m-n区间的元素 list[m:n: ...

  5. Beta 冲刺 随笔合集

    团队展示: Team一二一 Beta 冲刺 凡事预则立 Beta冲刺 一 Beta冲刺 二 Beta冲刺 三 Beta冲刺 四 Beta冲刺 五 Beta冲刺 六 Beta冲刺 七 Beta总结 用户 ...

  6. 业务-----添加Service常用逻辑

    1.参数不能为空 /** * 添加人员时判断是否字段全部传值 * @param request * @return */ private Boolean checkClientByCols(Clien ...

  7. 定义类型别名(typedef,using)

    说到类型别名,无非是给类型名(如int,char,float,double,bool)取一个比较有特殊含义的名字而已 最常用的关键莫过于 typedef 吧 typedef最常见的用法是与结构体str ...

  8. HDU 2012 素数判定

    http://acm.hdu.edu.cn/showproblem.php?pid=2012 Problem Description 对于表达式n^2+n+41,当n在(x,y)范围内取整数值时(包括 ...

  9. HTML使用button的一个小坑

    https://www.w3schools.com/TAGs/att_button_type.asp Definition and Usage The type attribute specifies ...

  10. Trouble shooting(问题解决):centos 7 gnome show someting has gone wrong.

    centos 7 升级 内核 3.10,startx启动不了了.进界面也是oh,no!someting has gone wrong . 参见帖子:http://bbs.csdn.net/topics ...