深入Java中的字符串(equals && ==)

预备知识

equals、==

        首先明确一点,==比较的是引用,equals比较的是内容,在类库没有定义equals方法重写的情况下,自然继承的是Object类的equals方法,上源码:

 public boolean equals(Object obj) {
        return (this == obj);
    }

        那么对于String类来说,官方已封装了复写之后的equals方法,比较的是具体内容,判断:如果是引用相同,也就是堆内的地址相同,那么就是同一个对象,直接返回true,否则就遍历字符串中的每一个字符进行比较:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

       其次,字符串还有个常量池,也就是说如果先定义了一个"ABC"字符串,再定义一个相同字符串的时候,会首先去常量池里面找之前有没有定义,如果有,则直接指向常量池的同一地址。

常量池介绍

       JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化

  • 为字符串开辟一个字符串常量池,类似于缓存区

  • 创建字符串常量时,首先坚持字符串常量池是否存在该字符串

  • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中

       有了以上的铺垫,后面几个问题就好解释了:

案例1

public class StringTests {
    public static void main(String[] args) {
        String str1 = "ABC";
        String str2 = "ABC";
        String str3 = new String("ABC");
        System.out.println(str1 == str2);// true
        System.out.println(str2 == str3);// flase
        System.out.println(str1.equals(str2)); // true
        System.out.println(str2.equals(str3)); // true
    }
}

       str1与str2都指向的是常量池中同一个字符串的地址,所以==为true,而str3是new了一个新的对象,会在堆中新建一个内存地址,所以比较str3和str3的地址时则为flase,equals就简单了,比较的均为字符串内容,故均为true。
在这里插入图片描述

案例2

public class StringTests {
    public static void main(String[] args) {
        String str1 = "ABC";
        String str2 = "ABC";
        String str3 = new String("ABC");
        String str3_1=str3.intern();
        System.out.println(str1 == str2);// true
        System.out.println(str2 == str3);// flase
        System.out.println(str3_1 == str1);// true
        System.out.println(str3_1 == str3);// flase
    }
}

       如果我们新定义一个str3_1变量,让他取str3的intern()方法,此方法是这样定义的:

这是一个native的方法,书上是这样描述它的作用的:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回常量池池中这个字符串的String对象;否则,将此String对象包含的字符添加到常量池中,并返回此String对象的引用。

所以str3_1拿到的是常量池中的”ABC“地址,和str1、str2的地址肯定是相同的,

案例3

public class StringTests {
    public static void main(String[] args) {
        String str1 = "ABC";
        String str2 = "ABC";
        str1 +="D";
        String str5 = "ABCD";// 肯定在常量池中生成一个
        System.out.println(str5 == str1);//flase
    }
}

        这里如果给str1拼接了一个D字符串,这里通过javap命令查看字节码文件,会发现+=操作在String源码中调用的其实是apped方法,生成的是一个新的对象堆地址,str5指向的常量池的地址与str1指向的堆对象地址不一,如果我在str1拼接之后加上str1 = str1.intern(); 这时候由于str1指向的是常量池中的ABCD,==就为true了。

public class StringTests {
    public static void main(String[] args) {
        String str1 = "ABC";
        String str2 = "ABC";
        str1 +="D";
        str1 = str1.intern();
        String str5 = "ABCD";// 肯定在常量池中生成一个
        System.out.println(str5 == str1);//true
    }
}

        这里intern调用之后,如果常量池中没有”ABCD“,则会在常量池生成,如果有则直接指向它。所以此处的str1指向的是常量池中的”ABCD“,后面定义的str5在构造时则会直接指向常量池中的”ABCD“

如果这里把str1 += ”D“修改一下成常量与常量的拼接:

public class StringTests {
    public static void main(String[] args) {
        str1 ="ABC" +"D";
        String str5 = "ABCD";// 肯定在常量池中生成一个
        System.out.println(str5 == str1);//true
    }
}

        如果两个常量进行拼接,编译器会自动优化成"ABCD",也就是存到了常量池中去了,str5指向的也是常量池中的值。

总结

原则:

(1)常量+常量:结果是常量池

(2)常量与变量 或 变量与变量:结果是堆

(3)拼接后调用intern方法:结果在常量池

new 出来的是在堆空间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值