疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

深入理解String类


 

   

  1、String str = "eee" 和String str = new String("eee")的区别

  先看一小段代码,

  复制代码

  1 public static void main(String[] args) {

  2 String str1 = "eee";

  3 String str2 = "eee";

  4 String str3 = new String("eee");

  5 System.out.println("str1 == str2 is " + (str1 == str2));

  6 System.out.println("str1 == str3 is " + (str1 == str3));

  7 System.out.println("str1.equals(str2) is " + str1.equals(str2));

  8 System.out.println("str1.equals(str3) is " + str1.equals(str3));

  9 }

  复制代码

  运行结果为:

  str1 == str2 is true

  str1 == str3 is false

  str1.equals(str2) is true

  str1.equals(str3) is true

  2、从JVM角度分析

  《深入理解Java虚拟机》一书指出,JVM运行时数据区如下:

  所有线程共享区域包括:

  方法区:用于存储已被虚拟机加载的类信息、常亮、静态变量、即时编译器编译后的代码等数据,以及运行时常量池。

  Java堆:在虚拟机启动时创建,存放对象实例,几乎所有的对象实例都在这里分配内存。

  线程私有区域包括:

  虚拟机栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

  本地方法栈:与虚拟机栈类似, 区别主要是本地方法栈为Native方法服务。

  程序计数器:一块较小的内存空间,当作当前线程所执行字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能需要依赖这个计数器来完成。

  String是一个不可变对象,可以认为是特殊的常量,因此存在方法区的运行时常量池中,可以被共享使用,以提高效率。

  从JVM角度分析以上代码:

  1 String str1 = "eee"; //1、在运行时常量池中创建新的对象"eee",如果常量池中不存在的话;2、栈中创建对象的引用str1

  2 String str2 = "eee"; //由于运行时常量池中已经存在该对象,直接在栈中创建对象的引用str2即可。

  3 String str3 = new String("eee"); //1、通过new指令,在堆中创建新的对象,2、在栈中创建对象的引用str3。

  对象之间通过==来比较,比较的是对象的引用。因此也就不难理解为什么str1 == str2, 而str != str3了。

  而equals方法比较的是什么呢?如果类没有重写Object类中equals方法时,比较的也就是对象的引用;如果重写了equals方法,那么就要看重写的方法了。

  3、从代码角度分析

  在jdk1.8中查看String类的源码,

  复制代码

  1 public final class String

  2 implements java.io.Serializable, Comparable, CharSequence {

  3 private final char value[];

  4 private int hash; // Default to 0

  5

  6 public String(String original) {

  7 this.value = original.value;

  8 this.hash = original.hash;

  9 }

  10

  11 /** 实际比较的是value[]是否相等 */

  12 public boolean equals(Object anObject) {

  13 if (this == anObject) {

  14 return true;

  15 }

  16 if (anObject instanceof String) {

  17 String anotherString = (String)anObject;

  18 int n = value.length;

  19 if (n == anotherString.value.length) {

  20 char v1[] = value;

  21 char v2[] = anotherString.value;

  22 int i = 0;

  23 while (n-- != 0) {

  24 if (v1[i] != v2[i])

  25 return false;

  26 i++;

  27 }

  28 return true;

  29 }

  30 }

  31 return false;

  32 }

  33 }

  复制代码

  根据源代码可以看出,String类的equals方法比较的实际是value[]是否相等。根据构造函数以及之前的JVM内存模型,可以分析出str1,str2,str3在内存中关系如下:

  可以很容易的理解,str1.equals(str3)为true。

  4、不建议String对象作为锁去同步

  直接看一个例子,

  复制代码

  1 public class StringAsSynchronized {

  2 public static class Service {

  3 public void print(String stringParam) {

  4 try {

  5 synchronized (stringParam) {

  6 while (true) {

  7 System.out.print(Thread.currentThread().getName());

  8 Thread.sleep(1000);

  9 }

  10 }

  11 } catch (InterruptedException e) {

  12 e.printStackTrace();

  13 }

  14 }

  15 }

  16

  17 public static class ThreadA extends Thread {

  18 private Service service;

  19 private String stringA = "synchronized";

  20

  21 public ThreadA(Service service) {

  22 this.service = service;

  23 }

  24

  25 @Override

  26 public void run() {

  27 service.print(stringA);

  28 }

  29 }

  30

  31 public static class ThreadB extends Thread {

  32 private Service service;

  33 private String stringB = "synchronized";

  34

  35 public ThreadB(Service service) {

  36 this.service = service;

  37 }

  38

  39 @Override

  40 public void run() {

  41 service.print(stringB);

  42 }

  43 }

  44

  45 public static void main(String[] args) {

  46 Service service = new Service();

  47 ThreadA a = new ThreadA(service);

  48 a.setName("A");

  49 ThreadB b = new ThreadB(service);

  50 b.setName("B");

  51 a.start();

  52 b.start();

  53 }

  54 }

  复制代码

  运行结果为:AAAAAAAAA。。。。

  原因为ThreadA类以及ThreadB类中的成员变量stringA以及stringB指向的是同一个对象。

  改正方法为

  1、第33行修改为private String stringB = new String("synchronized");

  2、更好的做法是不使用String对象用来同步锁。