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

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. E. Binary Numbers AND Sum

    链接 [http://codeforces.com/contest/1066/problem/E] 题意 给你长度分别为n,m的二进制串,当b>0时,对a,b,&运算,然后b右移一位,把 ...

  2. 第十次Scrum meeting

    第十次Scrum  meeting 任务及完成度: 成员 1.2 1.3 陈谋 任务1040:完成stackoverflow的数据处理后的json处理(100%) 任务1114-2:完成对pdf.pp ...

  3. 2-Twenty Fourth Scrum Meeting-20151230

    前言 因为服务器关闭,我们的开发项目也遭遇停滞一个星期.与网站开发负责人员协商之后,29号开放服务器.我们的项目也能够继续下去.比规定的开发时间(截止为2015/12/29)推迟. 事项安排 1.开发 ...

  4. QQ通信机制(转)

    下面有4个基本的问答: 问题一:为什么只要可以连上互联网的计算机都可以用QQ相互建立通信,而不需要固定IP?也就是这个QQ用户端是怎样找到另一个QQ用户的,而用户在每次使用时他可能用的是不同的计算机, ...

  5. 软件工程(GZSD2015) 第二次作业进度

    贵州师范大学软件工程第二次作业 徐 镇 王铭霞 张 英 涂江枫 张 燕 安 坤 周 娟 杨明颢 杨家堂 罗文豪 娄秀盛 周 娟 李盼 岳庆 张颖 李丽思 邓婷 唐洁 郑倩 尚清丽 陈小丽 毛茸 宋光能 ...

  6. docker网络调试过程

    #1: 添加永久网桥 vi /etc/sysconfig/network-scripts/ifcfg-br0 DEVICE=br0 TYPE=Bridge BOOTROTO=static IPADDR ...

  7. solt插槽的使用。

    在组件内template中使用 <slot name='header'></slot> 在页面内 直接添加标签 如 <hs><h1 slot='header' ...

  8. PGSQL 获取数据库大小以及表达小等的SQL

    SELECT d.datname AS Name, pg_catalog.pg_get_userbyid(d.datdba) AS Owner, CASE WHEN pg_catalog.has_da ...

  9. 使用Hexo搭建Github静态博客

    1. 环境环境 1.1 安装Git 默认配置就好 1.2 安装node.js 下载:http://nodejs.org/download/ 安装时直接保持默认配置即可. 2. 配置Github 1.1 ...

  10. jquery 語法

    基本形式: $(selector).action() 文檔加載函數: $(document).Ready{ function(){ //將所有的函數寫到文檔加載函數里,可以防止頁面未加載完全,就執行j ...