zoukankan      html  css  js  c++  java
  • 关于java中的==,equals()

    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是同样的字符数组。

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

    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()操作后用等号是可以匹配的。



    ================================================================
    PS:如果你觉得文章对你有所帮助,别忘了推荐或者分享,因为有你的支持,才是我续写下篇的动力和源泉!
  • 作者: Greta Wang
    出处: http://www.cnblogs.com/greta/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
================================================================
查看全文
  • 相关阅读:
    spring多数据源配置
    spring+myBatis 配置多数据源,切换数据源
    Maven项目引入log4j的详细配置
    基于Https协议返回Jason字符串
    Http协议入门、响应与请求行、HttpServletRequest对象的使用、请求参数获取和编码问题
    java http post/get 服务端和客户端实现json传输
    java实现一个简单的Web服务器
    设计模式系列
    Nginx系列
    Linux系列
  • 原文地址:https://www.cnblogs.com/greta/p/5902612.html
  • Copyright © 2011-2022 走看看