关于java中的==,equals()

1. 先从一道面试题提及

请问下面的java

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类中被定义的,它的定义中就是使用“==”方式来匹配的。jvm

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

也就是说,若是不去重写equals()方法,而且对应的类其父类中没有重写过equals()方法,那么默认的equals()操做就是对比对象的地址。优化

equals()方法之因此存在,是但愿子类去重写这个方法,实现对比值的功能。ui

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

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

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

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

根据咱们上面的解释,判断b==compare和e==compare输出结果为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是一样的字符数组。

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

image

结论就是,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()操做后用等号是能够匹配的。

相关文章
相关标签/搜索