1. 先从一道面试题说起

请问下面的

public class Demo {
public static void main(String args[]){
String a = "a" + "b" + 1;
String b = "ab1";
System.out.println(a == b);
}
}

要了解这个问题,需要回答下面的几个问题:

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

2. 关于==

在Java语言中,“”就是对比两个内存单元的内容是否一样。

如果是原始类型byte,boolean,short,char,int,long,float,double,就是直接比较它们的值。

如果是引用,比较的就是引用的值,“引用的值”可以被认为对象的逻辑地址。如果两个引用发生“”操作,就是比较相应的两个对象的地址值是否一样。换句话说,如果两个引用所保存的对象是同一个对象,则返回true,否则返回false(如果引用指向的是null,其实这也是一个jvm赋予给它的某个指定的值)。

看下面的代码:

public class Demo {
public static void main(String args[]){
List<String> a = null;
List<String> b = null;
System.out.println(a == b);
}
}
// 输出结果
true

3. 关于“equals()”方法

“equals()”方法,首先是在Object类中被定义的,它的定义中就是使用“==”方式来匹配的。

//equals在Object类中的源码
public boolean equals(Object obj) {
return (this == obj);
}

也就是说,如果不去重写equals()方法,并且对应的类其父类中没有重写过equals()方法,那么默认的equals()操作就是对比对象的地址。

equals()方法之所以存在,是希望子类去重写这个方法,实现对比值的功能。

3. a和b在内存中是什么样的?

a和b在内存中是指向同一块内存空间的。这就得益于Java的编译时优化方案。

我们用反编译软件jd-gui看看编译后的代码是怎么样的?

import java.io.PrintStream;

public class Demo
{
public static void main(String[] args)
{
String a = "ab1";
String b = "ab1";
System.out.println(a == b);
}
}

看到这里结果应该就一目了然了。JVM会把常量叠加在编译时进行优化,因为常量叠加得到的是固定的值,无须运行时再进行计算,所以会这样优化。

看到这里别着急,JVM只会优化它可以帮你优化的部分,它并不是对所有的内容都可以优化。例如,就拿上面叠加字符串来说,如果几个字符串叠加出现了变量,即在编译时还不确定具体的值是多少,那么JVM是不会去做这样的编译时合并的。

如果上面的这段话你理解了,我们再来看一个例子:

public class Demo {
public static void main(String args[]){
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);
} private static String getA(){
return "a";
}
}
//输出结果:
false
true
false

根据我们上面的解释,判断bcompare和ecompare输出结果为false,这个比较容易理解,因为a和getA()并不是一个常量,编译时并不会对此进行优化,我们用jd-gui可靠编译后的代码:

import java.io.PrintStream;

public class Demo
{
public static void main(String[] args)
{
String a = "a";
String c = "a"; String b = a + "b";
String d = "ab";
String e = getA() + "b";
String compare = "ab"; System.out.println(b == compare);
System.out.println(d == compare);
System.out.println(e == compare);
} private static String getA()
{
return "a";
}
}

从编译后的代码,我们可以验证我们的结论,b和e并没有被JVM优化。

比较奇怪的是变量d,被JVM优化了。区别在于对叠加的变量c有一个final修饰符。从定义上强制约束了c是不允许被改变的,由于final不可变,所以编译器自然认为结果是不可变的。

4. 内存中的字符串(详细解释)

字符串对象内部是用字符数组存储的,那么看下面的例子:

String m = "hello,world";
String n = "hello,world";
String u = new String(m);
String v = new String("hello,world");

这些语句会发生什么事情?大概是这样的:

  1. 会分配一个11长度的char数组,并在常量池分配一个由这个char数组组成的字符串,然后由m去引用这个字符串。
  2. 用n去引用常量池里边的字符串,所以和m引用的是同一个对象
  3. 生成一个新的字符串,单内部的字符数组引用着m内部的字符数组。
  4. 同样会生成一个新的字符串,但内部的字符数组引用常量池里边的字符串内部的字符数组,意思是和u是同样的字符数组。

我们使用图来表示的话,情况就大概是这样的:

结论就是,m和n是同一个对象,但m,u,v都是不同的对象,但都使用了同样的字符数组,并且用equal判断的话也会返回true。

我们可以使用反射修改字符数组来验证一下效果:

public class Demo {
public static void main(String args[]) throws NoSuchFieldException, IllegalAccessException {
String m = "hello,world";
String n = "hello,world";
String u = new String(m);
String v = new String("hello,world");
Field f = m.getClass().getDeclaredField("value");
f.setAccessible(true);
char[] cs = (char[]) f.get(m);
cs[0] = 'H';
String p = "Hello,world";
System.out.println(m.equals(p));
System.out.println(n.equals(p));
System.out.println(u.equals(p));
System.out.println(v.equals(p));
}
}
//输出结果:
true
true
true
true

从上面的例子可以看到,经常说的字符串是不可变的,其实和其他final类没有什么区别,还是引用不可变的意思。虽然String类不开放value,但同样是可以通过反射进行修改。

5. 关于String中的intern方法

public class Demo {
public static void main(String args[]){
String a = "a";
String b = a + "b";
String c = "ab";
String d = new String(b);
System.out.println(b == c);
System.out.println(c == d);
System.out.println(c == d.intern());
System.out.println(b.intern() == d.intern());
}
}
//输出结果
false
false
true
true

String引用所指向的对象,它们存储在常量池中,同一个值的字符串保证全局唯一。

如何保证全局唯一呢? 当调用intern()方法时,JVM会在这个常量池中通过equals()方法查找是否存在等值的String,如果存在,则直接返回常量池中这个String对象的地址;若没有找到,则会创建等值的字符串,然后再返回这个新创建空间的地址。只要是同样的字符串,当调用intern()方法时,都会得到常量池中对应String的引用,所以两个字符串通过intern()操作后用等号是可以匹配的。

关于java中的==,equals()的更多相关文章

  1. 浅谈Java中的equals和==(转)

    浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str ...

  2. 浅谈Java中的equals和==

    浅谈Java中的equals和== 在初学Java时,可能会经常碰到下面的代码: String str1 = new String("hello"); String str2 = ...

  3. Java中的equals和hashCode方法

    本文转载自:Java中的equals和hashCode方法详解 Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要 ...

  4. Java中==、equals、hashcode的区别与重写equals以及hashcode方法实例(转)

    Java中==.equals.hashcode的区别与重写equals以及hashcode方法实例  原文地址:http://www.cnblogs.com/luankun0214/p/4421770 ...

  5. java集合(3)- Java中的equals和hashCode方法详解

    参考:http://blog.csdn.net/jiangwei0910410003/article/details/22739953 Java中的equals方法和hashCode方法是Object ...

  6. 【Java学习笔记之二十九】Java中的"equals"和"=="的用法及区别

    Java中的"equals"和"=="的用法及区别 在初学Java时,可能会经常碰到下面的代码: String str1 = new String(" ...

  7. Java中的equals和hashCode方法详解

    Java中的equals和hashCode方法详解  转自 https://www.cnblogs.com/crazylqy/category/655181.html 参考:http://blog.c ...

  8. 关于Java中的equals方法

    关于Java中的equals方法 欢迎转载,但是请填写本人的博客园原址https://www.cnblogs.com/JNovice/p/9347099.html 一.什么是equals方法 equa ...

  9. 沉淀再出发:java中的equals()辨析

    沉淀再出发:java中的equals()辨析 一.前言 关于java中的equals,我们可能非常奇怪,在Object中定义了这个函数,其他的很多类中都重载了它,导致了我们对于辨析其中的内涵有了混淆, ...

  10. 转:Java中的equals和hashCode方法详解

    转自:Java中的equals和hashCode方法详解 Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要重写这 ...

随机推荐

  1. 【一通百通】C/C++的std标准总结

    C/C++发展很多版本标准,sunsky(本人)早就想理理std标准了,让大家有个清晰的了解. 适用C(或C源代码,如C ++,Objective-C和Objective-C ++)的标准: -ans ...

  2. java 8 Hashmap深入解析 —— put get 方法源码

    每个java程序员都知道,HashMap是java中最重要的集合类之一,也是找工作面试中非常常见的考点,因为HashMap的实现本身确实蕴含了很多精妙的代码设计. 对于普通的程序员,可能仅仅能说出Ha ...

  3. 自适应滤波:奇异值分解SVD

    作者:桂. 时间:2017-04-03  19:41:26 链接:http://www.cnblogs.com/xingshansi/p/6661230.html 声明:欢迎被转载,不过记得注明出处哦 ...

  4. 刷机无法连接4g

    只显示2g,gsm only 无法修改,本人刷cm13和lineageOs都遇到过这样的情况,可能与手机有关xt1570(moto x style),特在此分享,希望有用 1.首先在设置中将sim卡网 ...

  5. C语言本身并不提供输入输出语句

    C语言本身并不提供输入输出语句,输入和输出操作是由函数来实现的.在C标准函数库中提供了一些输入输出函数,例如,printf函数和scanf函数.在使用他们时,千万不要误认为他们是C语言提供的“输入输出 ...

  6. javascript中的几种遍历方法浅析

    1. for...in 用于对数组或者对象的属性的可枚举属性进行循环操作.注意该对象来自原型链上的可枚举属性也会被循环.下面看例子 var arr = ["lee","h ...

  7. 前端必备PS技巧

    hai,how is it going?I'm MuQing.I come back.哈哈,最近在英语的路上奋战,小秀一下.又好久没写博客了,实习的生活渐行渐远了,回到学校也终于能够坐下来对很多东西进 ...

  8. php基础知识(二)---2017-04-14

    1.字符串的三种表达形式: (1)双引号 (2)单引号 (3)尖括号 $s = <<<A <div style="width:500px; height:100px; ...

  9. angular directive

    1.restrict (字符串)可选参数,指明指令在DOM里面以什么形式被声明: 取值有:E(元素),A(属性),C(类),M(注释),其中默认值为A: E(元素):<directiveName ...

  10. CF #CROC 2016 - Elimination Round D. Robot Rapping Results Report 二分+拓扑排序

    题目链接:http://codeforces.com/contest/655/problem/D 大意是给若干对偏序,问最少需要前多少对关系,可以确定所有的大小关系. 解法是二分答案,利用拓扑排序看是 ...