Java常量池与字符串的关系

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/li99yangg/article/details/87450590

package com.ly.demo1;

众所周知,Java中的String类型是几乎java中最常用的对象之一,它比char数组在实际运用中方便很多,但是我们需要了解,它在Java虚拟机中是如何储存及使用的。

首先我们来看如下的代码

import java.util.concurrent.SynchronousQueue;

public class demo02 {
 public static void main(String[] args) {
  String s1 = "我是";
  String s2 = "李";
  String s3 = s1+s2;
  String s4 = "我是 "+"李";
  String s5 = "我是李";
  String s6 = "我是李";
  String s7 = new String("我是李");
  String s8 = s3.intern();
  String s9 = s7.intern();
  /***当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串
   (用equals(Object)    方法确定),则返回池中的字符串。
   否则,将此 String 对象添加到池中,并返回此 String 对象的引用。”
  ***/
  System.out.println(s3==s4);
  System.out.println(s3==s5);
  System.out.println(s4==s5);
  System.out.println(s5==s6);
  System.out.println(s5==s7);
  System.out.println(s8==s3);
  System.out.println(s8==s5);
  System.out.println(s8==s9);
 }
}

如果不了解String在常量池与堆的存放方式,那么必然不知道上述的输出结果。先上结果,再解释原因

false  System.out.println(s3==s4);
false  System.out.println(s3==s5);
true  System.out.println(s4==s5);
true   System.out.println(s5==s6);
false  System.out.println(s5==s7);
false  System.out.println(s8==s3);
true   System.out.println(s8==s5);
true   System.out.println(s8==s9);

在字符串的组成上,他们都是 “我是李”,但是他们的地址却不同。

①首先我们来解释s3/s4/s5/s6之间的关系,这里面我们务必要提到两个概念:常量池、堆。

简单来说,它的存在就是放所有的常量,如果我们创建了两个新的变量,int x,y;为他们赋值均为1,那么x==y的结果就是true,它们的值和地址均相等。而深层次的原因就是,在我们初始化x的值为1时,常量池便创建了一个“1”在池中。当我们为y赋值为1时,常量池会先搜索整个池中有没有重复的常量,这时它发现我们之前已经有了一个“1”了,那好,它便将y的地址指向了这个1,这就是x==y的结果为true的原因。

而堆的存在,简而言之,就是存放所有被new出来的对象,在堆中的对象就不像常量池中进行重复数据检验,存放在堆的专属空间中,而不放在常量池中。假设上文中的x和y是存放在堆中,那么x==y的结果就是false。

但是现在换成字符串,却发生了很多不同。s4/s5/s6是指向同一地址的“我是李”,但是s3却没有指向该地址。s5/s6可以通过常量池的概念来理解。至于s4会与s5/s6相同,是因为编译器在编译时,为我们自动拼接了字符串,这样的话,s4和s5/s6其实是等价的。但s3不同,s3是由s1与s2相加产生。这种对象之间的加法产生的结果仍然是对象,存放在堆中,那么,它必然与s4/s5/s6的地址不同。同理,s7与s5的结果也是这个原因

②而s8中的intern()方法便是一个帮助我们检验常量池中是否存在相同数据的一个方法。当我们调用这个方法,它便会将本字符串相同的在常量池中的字符串搜索,如果存在相同的,便将本字符串的地址指向字符串。这便是s8与s9/s5地址相同的原因。

结论:字符串之间的地址之间的比较,就是堆与常量池、堆与堆之间对象的地址之间的比较。


还有其他的字符串比较的情况 如下:

  public static void main(String[] args) { String a = "hello2"; final String b = "hello"; String d = "hello"; String c = b + 2; String e = d + 2; String f ="hello"+"2"; System.out.println(a == c); System.out.println(a == e); System.out.println(a == f); }

输出结果为  true  ,false , true

首先要明确的是 引用数据类型 == 比较的是地址值,equal 没重写比较的是地址值,重写之后比较的是内容.String重写了,StringBuffer没有重写

其次:

a==c 为true是因为 b为final类型,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的  值。所以 c = b+2 等于 c = “hello”+“2” 同e

a ==e 为false是因为e = d+2 其实是创建了一个StringBuffer对象,然后用StringBuffer对象执行append方法来创建出字符串对象“ab”,然后再转换成为String。但是这个转换后的String对象,也就是上面的s3是放在堆里面的。而s4则是字符串常量,放在常量池里面。所以返回的是false。 

a ==f 为true 是因为: 常量相加的时候,其实是在编译的时候就直接相加为“hello2”,这是JVM的优化,所以运行的时候,a和f的字节码是一样的。因为在常量池中有了一个“hello2”,所以两个引用指向的是一个字符串“hello2”,所以返回结果也是true。

这段来自:https://blog.csdn.net/qq_23367963/article/details/81937325

猜你喜欢

转载自blog.csdn.net/li99yangg/article/details/87450590