重新认识String常量池

正常情况

如果按照正常情况,你面试的时候,看到这个代码,你肯定会说:结果是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()&nbsp;==&nbsp;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 &sect;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
就可以查询到这一题答案。你真的能全部答对吗?

哈哈,未必!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值