今天给大家带来的是在面试中经常被问到的一道题:
无论在Java还是Android中,String是一个很常见的类,但是大家真的很了解吗,我这里有几个题:
1.
String str1 = "abc"; String str2 = new String("abc");
这两种创建String对象的方法有什么不同?
2.
String s = "a" + "b" + "c" + "d";
这里面一共创建了多少对象?
这两道题昨天给笔者搞得是一脸懵逼,后来一听这是一道很经典的面试题,就赶快查阅各种资料,现在已经解决了。
在解决这两个题之前,我们先来明确几个知识点,相信把这几个知识点弄完之后再回头看这两个题,就很简单了:
我们首先来开第一个:
引用在栈内存中存储,对象在堆内存中存储。
这个是我粗略画的一张图,这张图可能不是很准确,但是我只想表达一个意思,我们的栈内存,存放的是我们对象的引用和我们基本数据类型的值。而堆内存中存放的是我们的对象。就是这么简单。
为什么说String对象不可变
这里我们用一下大佬的图:
如果用代码表示上面的图,那就是:
String s = "abcd"; s = s + "el";
如果没有上面这张图,大家可能会觉得我们只是给s对象后面加了一个el,这不还是原来的s吗?不,我们说的String对象不可变就是这个意思,当我们给s再加上el时候,新的字符串abcdel被存放到了一个新的内存中。已经不是原来的内存了。所以说String对象不可变,如果变了,那就已经变成了一个新的对象。
那说到这里大家可能会返回去看我写的第二个问题:按你的说法这不就7个对象吗?abcd各占一个,每次+到一起都要重新生成对象,一共生成了7个对象。话是没错,但是我想说这道题说多解,等我们讲完了下面这两个知识点,这道题就完全解开了。
String创建对象的形式:
正如我们上面看到的,String有两种创建对象的形式:
1.字面量形式:
String str = "asd";
2.标准的new形式:
String str = new String("asd");
字符串常量池的意义:
字符串常量池,又称为字符串在字面量池。大家不要他想象的多么高深额,其实说白了他就是一块内存,它里面存放的是我们的字符串的引用。
大家可能疑问这个东西有什么意义:假如我们要创建一个字符串,"a" + "b" + "c" + ....+ 我们+了一万次,那么按照上面的说法,我们是不是为了创建一个很长的字符串,创建了很多的对象。这样不但有的字符串被重复的创建了,而且占用了很多不必要的内存,代价有点大。
我们的JVM为了减少字符串的重复创建,维护了一个特殊的内存:就是这个字符串常量池。
在这个字符串常量池中,存放着我们字符串对象的引用。下面我们根据字符串创建对象的两种形式来说明一下常量池是如何存放String对象引用的:
1.通过字面量创建String对象:
我们举个例子,String str = "abc";我们通过字面量形式创建一个值为"abc"的对象,首先我们判断常量池中是否存在一个引用,这个引用的值也是"abc",如果有这个值,那么我们就从常量池中拿到这个引用,返回给str。
如果没有,那么我们就创建这个对象,然后把str这个引用值放到常量池中。
在这里我们要明确几个点了:
2.通过new创建String对象。
这里其实只有一句话,无论常量池中是否存在相同值的引用,至少创建一个对象。因为不管别的,new这个语法就一定会创建一个新的对象,然后我们要看构造方法中的字符串,如果常量池中存在与该字符串相同值的引用,那么就不会创建新的对象,反之会创建对象之后,在常量池中存放这个引用。
这样看起来我们的常量池的意义就很明确了,减少重复创建String对象,从而减少不必要的内存消耗。
如果要说他的弊端的话,应该就是牺牲了CPU的计算时间来换取空间吧,因为查找是否有相同内容的引用是CPU耗时计算,但是与前者占用内存相比,这个还是算不了什么的吧(笔者自己观点,没有官方支持哈哈)。
intern()方法使用
最后我们来看一下这个intern()方法,这个方法其实理解为查看常量池中是否存在对应内容的引用。如果有他会返回常量池中的引用,如果没有则会将当前String的引用放入常量池。
我们举几个例子对比下就好:
public static void main(String[] args) { String str1 = "gfzy"; String str2 = str1.intern(); System.out.println(str1 == str2); }
他的结果是true。很简单,我们首先通过字面量形式创建了一个“gfzy”对象,此时常量池中没有相同内容引用,所以常量池存放str1的引用。
然后str2为str1的.intern()方法,现在常量池中存在“gfzy”这个内容的引用,所以我们返回这个引用,也就是str1,所以str1和str2指向同一个引用。
public static void main(String[] args) { String str1 = new String("gfzy"); String str2 = "gfzy"; System.out.println(str1 == str2); }
这个结果为false,首先我们看到str1是new了一个对象,所以他肯定是在堆内存中一块新的内存,而构造方法中的“gfzy”,首先在常量池中会拿到他的引用,然后返回这个引用。
str2直接拿到了常量池中的引用,所以一个是堆内存新创建的,一个是原来常量池中的引用,两者指向不是一个内存,所以是false。
而如果是这样呢:
public static void main(String[] args) { String str1 = new String("gfzy").intern(); String str2 = "gfzy"; System.out.println(str1 == str2); }
这个结果为true。在原来的基础上只是添加了一个inter方法,在new String("gfzy");时候,现在常量池中已经有了这个引用。而现在intern,我们会拿到常量池中的引用,所以str1为常量池中的引用,str2和上面一样,所以返回true。
现在这几个问题都解决完了,我们再返回头看之前的两个问题,第一个问题就很简单了,而我们刚才也说过了。
第二个是看常量池中是否已经存在了对应的引用,如果没有,那么是"a","b","ab","c","abc","d","abcd"七个对象,如果常量池中存在引用,那么只创建了一个“abcd”(这个是编译器优化后的结果,其实笔者这里也是有点懵逼,个人感觉应该是3个额,希望这个问题有读者多多留言,帮我解答一下这个问题)。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对亿速云的支持。如果你想了解更多相关内容请查看下面相关链接
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。