正常情况
如果按照正常情况,你面试的时候,看到这个代码,你肯定会说:结果是false。
如果我要他们是true,应该怎么做呢?有什么方法吗?
String str4 = new String("abc");
String str5 = new String("abc");
System.out.println((str4 == str5));//false
因为上面的代码会在堆区(heap),存入两个不同的对象。所以,直接使用双等号==的结果肯定是false的。
但是,JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化:
为字符串开辟一个字符串常量池,类似于缓存区
创建字符串常量时,首先查询字符串常量池是否存在该字符串
存在该字符串,返回引用实例;不存在,实例化该字符串并放入池中
那么,我们是否可以直接利用常量池里面的字符串呢?
看看这段代码
String str1 = "abc";
String str2 = "abc";
String str3 = "abc";
String str4 = new String("abc");
String str5 = new String("abc");
String str6 = new String("abc");
使用上面的代码画了一个简易示意图
从图中,可以看出最开始的String str1 = "abc"; ,str1直接引用常量池的实例,步骤应该是,先从常量池里面找一下,发现没有"abc",然后,新建了一个全局唯一并且不可变的"abc"字符串常量放进常量池。
“那么,我们思考一下:
”
既然知道常量池里面有一个"abc",为啥不能利用他呢?如果我做到str4 和 str5能拿到常量池里面的"abc"这不就能使用双等号==进行比较了吗?
按照常量池这个理论去推算:我们可以做个假设,如果我双等号==比较的是常量池中的"abc",这样子不就会是true了吗?
“那么,我们现在的主攻解决方向就是:
”
如何让str4 和 str5 得到常量池中唯一的"abc"呢?
如何获得常量池中的值?
我们可以使用String的intern()方法,请查看JDK源码解释:
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class <code>String</code>.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this <code>String</code> object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this <code>String</code> object is added to the
* pool and a reference to this <code>String</code> object is returned.
* <p>
* It follows that for any two strings <code>s</code> and <code>t</code>,
* <code>s.intern() == t.intern()</code> is <code>true</code>
* if and only if <code>s.equals(t)</code> is <code>true</code>.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in §3.10.5 of the
* <a href="http://java.sun.com/docs/books/jls/html/">Java Language
* Specification</a>
*
* @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方法时,如果常量池中已经含有相同内容的String实例,就返回常量池中这个相同内容的String实例
那么,我们就用intern方法测试一下:
String str4 = new String("abc");
String str5 = new String("abc");
System.out.println("(str4 == str5):"+(str4 == str5));
System.out.println("(str4.intern() == str5.intern()):"+(str4.intern() == str5.intern()));
测试结果截图:
说明了他们确实使用了常量池中的String对象,因为常量池中的"abc"是唯一的,所以,他们返回的同一个引用,所以,使用双等号==比较的结果是true。
知道之后,又能干什么呢?
相信读到这里, 你对于String的常量池有更加深的理解,而不是停留在面试题上的String str4 = new String("abc");这里创建了几个对象这种背诵式的应答。
“而是,可以用更深层次去回答面试官,用代码例子去证明有多少个对象!!!
”
面试题:下面的代码产生多少个String对象呢?(请用代码证明)
//面试题:下面的代码产生多少个String对象呢?
//请用代码证明有多少个对象
public static void main(String[] args) {
String str4 = new String("abc");
System.out.println("(str4 == \"abc\"):"+(str4 == "abc"));
System.out.println("(str4.intern() == \"abc\"):"+(str4.intern() == "abc"));
}
===============================================
测试截图:
(str4 == "abc"):false
(str4.intern() == "abc"):true
项目上的实战应用
除了可以加深理论知识的理解之外,还可以应用于实战。
相信大家都用过synchronized 同步代码块,请仔细看下面代码中的注释说明
Book book = dao.get(bookId);//假设每次查询bookId,都不使用缓存对象,每次都从DB里面直接拿出来
这样子就会就算bookId相同,对象也是不同的。
那么,如果多线程情况,不同线程都要查询同一个bookId呢?
Book book = dao.get(bookId);
//这样子就会就算bookId相同,对象也是不同的。
//那么,如果多线程情况,不同线程都要查询同一个bookId呢?
synchronized (book) {
//坑货,bookId相同的Book也进来了,你的锁又有什么意义呢?
}
因为就算是相同的bookId返回的都是两个不同的Book对象。所以,就算使用了synchronized 同步代码块也是没有意义的,因为是两个不同的对象,这样子是没有起到锁的作用,会是并行的执行结果。这样就不是我们想要的!!!
我们的目的是让相同的bookId的对象串行互斥执行代码,保证安全。而不同的bookId,是可以同时并行去执行,不影响不同的bookId的Book对象进行计算。
这个时候,常量池的String对象就很有用了,因为常量池的String对象是全局唯一的,所以,你不管你拿到多少次常量池的对象,其实,都是同一个对象。既然是同一个对象,那么,对这个唯一对象加锁,就可以做到加锁效果了。
改进代码如下:
Book book = bookService.get(bookId);
String currBookId = book.getId();
synchronized (currBookId.intern()) {//为了简洁减少代码就不进行null判断啦!
//这样子就可以做到相同bookId无法进来,又不会挡住不同bookId进行自己的操作,并行处理不同的Book了
}
最后
大家现在是不是感觉对于String常量池可以看得到,摸得着了。而不会再机械式回答面试题了,不会再不知所以然了。
其实,面试官问你String str4 = new String("abc"); 产生多少个String实例,不是真的叫你用手指去数,而是,考察你平常对基础理论的认知,考察你对问题思考的深度,而不是停留在面试题规范答案的表面。
“你试想一下,
”
如果你是面试官,应聘者用代码层面去讲解常量池,是不是比千篇一律的规范式回答会加分呢?
课后作业
看完这篇文章,你真的懂了吗?
要不,测试一下你是否理解透彻了?
//请看题:
String s1 = "123";
String s2 = new String("123");
Long number1 = 123L;
String s3 = new String("true");
Boolean boolVal = true;
System.out.println("Q1:"+(s1 == s2));
System.out.println("Q2:"+(s1 == s2.intern()));
System.out.println("Q3:"+(s1.intern() == s2));
System.out.println("Q4:"+(s1.intern() == s2.intern()));
System.out.println("Q5:"+(number1.toString() == s1));
System.out.println("Q6:"+(number1.toString().intern() == s1));
System.out.println("Q7:"+(s3 == boolVal.toString()));
System.out.println("Q8:"+(s3.intern() == boolVal.toString()));
System.out.println("Q9:"+(s3.intern() == boolVal.toString().intern()));
System.out.println("Q10:"+(s2.intern() == boolVal.toString().intern()));
想看看自己是否全部答对了?
可以在微信公众号里面回复:String123
就可以查询到这一题答案。你真的能全部答对吗?