String这个类相等 or 不相等,你真的了解?
相信看完这篇文章你会对String类有个更深的理解,当然大佬你可以当做没看到;
正文
很多的小伙伴应该都遇到过被String这个类支配的恐惧,也经常会有面试题涉及到这个类,下面我们就来掰一掰这个类。
在掰这个类之前我们先来介绍下常量池,常量池它属于JVM中的方法区,分为Class常量池(静态常量池)、运行时常量池和字符串常量池(全局字符串池,它被所有的类共享);
在JDK1.7以后,运行时常量池物理地址已经移到堆内存中,在JDK1.8以后,字符串常量池也被存放在堆内存中,但不管他们存在哪里,在逻辑上都还是属于方法区(JVM对内存的逻辑划分)。
对于常量池的具体细节介绍,可以在这篇文章中查看:
https://blog.csdn.net/xiao______xin/article/details/81985654
那为什么要先介绍常量池(主要是字符串常量池)呢?
不知道大家注意到没有,String在实现代码中是用final修饰的,而且变量char数组也被final修饰了,我们知道final修饰的类不可被继承,而char[] 被final + private 修饰,代表着String类不可被更改,这就是String的不可变性。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
String的不可变性这样做有什么好处呢?
- 保证String对象的安全性。
- 保证hash属性值不会频繁变更,确保了唯一性。
- 就是实现了**字符串常量池。**字符串常量池的实现方式一般有两种,一种是通过字符串常量的方式创建,如String str=“abc”;另一种就是字符串通过new的方式创建,如 String str = new String(“abc”);它实现了字符串共享(所有类可见),可以节省内存空间。
在介绍了这么多以后,我们先来一道String类的开胃菜:
示例1:
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false
这相信大家都知道,在执行s1这段代码时,会先去判断字符串常量池有没有“abc”的引用,如果有,则直接将使用这个引用,如果没有,则先在字符串常量池中新建一个“abc”,在将这个引用返回;s2一样的道理,s3是由于new了一个新的对象,对象地址是指向堆中的地址,但这个对象的“abc”还是指向字符串常量池中的“abc”。
下面我们先简单介绍一个String类中的本地方法intern(),查看这个类的描述如下:
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
主要的意思是: 在调用intern这个方法时,如果字符引用在字符串常量池中已经存在,则直接返回,如果没有,此对象将被添加到字符串常量池中以及对这个对象的引用被返回。
下面加上这个方法比较下:
示例2:
String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
String str4 = new String("abc");
System.out.println(str1 == str2);
System.out.println(str2 == str3);
System.out.println(str1 == str3);
System.out.println(str2.intern() == str4.intern());
根据上面介绍的知识,这也应该不难,应该是false,false,true,true;因为str3调用intern()方法,在字符串常量池中找到了“abc”的引用,即str1, 而str2.intern() == str4.intern()为true,在上述英文解释中就有说,他们比较的是值,类似于equals方法。
It follows that for any two strings {@code s} and {@code t},
{@code s.intern() == t.intern()} is {@code true}
if and only if {@code s.equals(t)} is {@code true}.
接下来再来个进阶版:
示例3:
String str7 = new String("fg") + new String("h");
String str8 = str7.intern();
String str9 = "fgh";
System.out.println(str7 == str8);
System.out.println(str7 == str9);
System.out.println(str8 == str9);
这个你们知道答案吗?绝对让你意想不到,他们都是true,why???
在解释这个现象之前,再让你们做一个题后再来解释这个问题,认真看下,其实我只是把顺序调换一下,但为了便于区分我换了个字符串和变量,即:
示例4:
String s7 = "def";
String s8 = new String("de") + new String("f");
String s9 = s8.intern();
System.out.println(s7 == s8);
System.out.println(s7 == s9);
System.out.println(s8 == s9);
这个答案又是什么呢?是false,true,false,这会估计小伙伴一脸问号了???
接下来对两个例子解释下:首先示例4很好理解,s7在字符串常量池中创建“def”的引用,运行到s9直接返回这个引用,所以只有s7==s9,而示例三又怎么理解呢?也很简单,其实上面已经有提示了,在调用intern这个方法时,如果不存在,会将这个对象的引用加入到字符串常量池中的同时对这个对象的引用被返回。故在执行了String str8=str7.instrn();这行代码后,字符串常量池中已经存在“fgh”的引用了,并且会将str7的引用指向这个字符串常量池中的“fgh”,所以三个字符串str7,str8和str9均指向同一个字符串常量池的引用。
看到这可能有些脑洞大开的小伙伴会想到与下面这个程序对比一下:
示例5:
String string1 = new String("jkl");
String string2 = string1.intern();
String string3 = "jkl";
System.out.println(string1 == string2); //flase
System.out.println(string1 == string3); //false
System.out.println(string2 == string3); //true
这答案很简单,上面已经标注了,但我要说的是它与示例三有何区别呢?
其实区别就在一行代码,String string1 = new String(“jkl”);这行代码不仅仅是声明这个字符串,同时还会将这个字符串的引用添加到字符串常量池中,而调用intern()方法执行的是找的路径,直接返回,不会对对象引用返回。,说白了就是intern这个方法在字符串常量池找的到和找不到字符串的引用的区别。
如果你还没有明白intern方法找不找的到的两种处理方式,可以把下面这两行代码加到示例3下面,在分析查看下结果也许你就明白了。
String str6 = new String("fg") + new String("h");
System.out.println(str6 == str9); //false
最后再来检验下成果吧,来个最终版,我相信如果你理解了上面的内容,做这个问题应该不大:
String string5 = new String("张") +new String("三");
String string6 = new String("张") +new String("三");
String string7 = string5.intern();
String string8 = string6.intern();
String string9 = "张三";
System.out.println(string5 == string6); //false
System.out.println(string5 == string7); //true
System.out.println(string5 == string8); //true
System.out.println(string5 == string9); //true
System.out.println(string6 == string7); //false
System.out.println(string6 == string8); //false
System.out.println(string6 == string9); //false
System.out.println(string7 == string8); //true
System.out.println(string7 == string9); //true
System.out.println(string8 == string9); //true
需要注意的是:以上测试均在JDK1.8环境上测试,不同版本的JDK在结果上可能略有差别,同时在这里我没有结合class的反编译来看,如果想要了解的更深的话可以结合着一起,也许你会理解的更加深刻。
至此相信你对String这个类会有进一步的理解,以上,如有问题,欢迎指正。