`
xinklabi
  • 浏览: 1559011 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
文章分类
社区版块
存档分类
最新评论

String连接与字符串常量池

    博客分类:
  • Java
 
阅读更多

 

 

字符串常量池由String实例管理维护,每一个类的.class文件对其进行引用,所以无论在哪一个类中声明相同的字符串常量,使用==操作返回的都是true。


转自:http://topic.csdn.net/u/20090519/18/7b8cf7ef-bc06-4d26-8a2c-692eb0562231.html
作者:zangxt  

  String类是Java中很重要的一个类,在此总结一下这个类的特别之处。下面的相关资料翻译自《java语言规范》(第三版)和《java虚拟机规范》(第二版),有的直接摘引了原文。下面的代码都是用SUN jdk1.6 javac来编译。

 

1.String literal,这里将它翻译为字面常量,它由双引号包围的0个或多个字符组成,比如"abc","Hello World"等等。一个String字面常量总是引用相同的String实例,比如"abc","abc"两个常量引用的是同一个对象。

 

程序测试:

package testPackage;

class Test {

  public static void main(String[] args) {

  String hello = "Hello", lo = "lo";

  System.out.print((hello == "Hello") + " ");

  System.out.print((Other.hello == hello) + " ");

  System.out.print((other.Other.hello == hello) + " ");

  System.out.print((hello == ("Hel"+"lo")) + " ");

  System.out.print((hello == ("Hel"+lo)) + " ");

  System.out.println(hello == ("Hel"+lo).intern());

  }

}

 

class Other { static String hello = "Hello"; }

 

另一个包:

 

package other;

public class Other { static String hello = "Hello"; }

输出:

true true true true false true

结论有六点:

1) 同一个包下,同一个类中的相同的String字面常量表示对同一个String对象的引用。

2) 同一个包下,不同的类中的相同的String字面常量表示对同一个String对象的引用。

3) 不同包下,不同类中的相同String字面常量同样表示对同一个String对象的引用。

4) 通过常量表达式计算的String,计算在编译时进行,并将它作为String字面常量对待。

5) 通过连接操作得到的String(非常量表达式),连接操作是运行时进行的,会新创建对象,所以它们是不同的。

6) 显式的对一个计算得到的String调用intern操作,得到的结果是已经存在的相同内容的String字面常量。

补充说明:

1)像这样的问题,String str = "a"+"b"+"c"+"d";

运行这条语句会产生几个String对象?1个。参考上面第5条,通过常量表达式得到的String 是编译时计算的,因此执行这句话时只有"abcd"着一个String对象存在。

常量表达是的定义可以参考java语言规范。另例:

  final String str1 = "a";

  String str2 = str1+"b";

执行第二句话会有几个String对象产生?1个。因为str1是常量,所以str1+"b"也是常量表达式,在编译时计算。

  遇到这种问题时,不要说它依赖于具体的编译器或者虚拟机实现,因为这就是规范里有的。一般的说,java的编译器实现应该遵守《java语言规范》,而java虚拟机实现应该遵守《java虚拟机规范》。

 

2)不要这样使用字符串:

String str = new String("abc");

  参考文档中的说明:

String

public String(String original)

  初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。由于 String 是不可变的,所以无需使用此构造方法,除非需要 original 的显式副本。

参数:

original - 一个 String。

注意:无需使用此构造方法!!!

 

3)单独的说明第6点:

String str = new String("abc");

str = str.intern();

  当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串引用。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。

  很明显,在这个例子中"abc"引用的对象已经在字符串池中了,再调用intern返回的是已经存在池中内容为"abc"的字符换对象的引用。在上面的例子中也说明了这个问题。

2. String类的实例表示表示Unicode字符序列。String字面常量是指向String实例的引用。(字面常量是“引用”!)

3.String转换

  对于基本类型先转换为引用类型;引用类型调用toString()方法得到String,如果该引用类型为null,转换得到的字符串为"null"。

4. String链接操作“+”

  如果“+”操作的结果不是编译期常量,将会隐式创建一个新的对象。为了提高性能,具体的实现可以采用 StringBuffer,StringBuilder类对多个部分进行连接,最后再转换为String,从而避免生成再丢弃中间的String对象。为 了达到共享实例的目的,编译期常量总是“interned”的。

例子:

String a = "hello ";

String b = a+1+2+"world!";

反汇编结果:

0: ldc #2; //String hello

  2: astore_1

  3: new #3; //class java/lang/StringBuilder

  6: dup

  7: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V

  10: aload_1

  11: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  14: iconst_1

  15: invokevirtual #6; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

  18: iconst_2

  19: invokevirtual #6; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

  22: ldc #7; //String world!

  24: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

  27: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

  30: astore_2

 

实际就是

String b = new StringBuilder().append(a).append(1).append(2).append("world").toString();

这里就使用StringBuilder来避免中间临时String对象的产生而导致性能下降。

  补充例子,下面的两个例子主要是对编译时常量做一个说明:

1)

String c = "c";

String str = "a"+"b"+c;



2)

String c = "c";

String str = c+"a"+"b";

1)中,str="a"+"b"+c;编译器分析是会把"a"+"b"作为编译时常量,生成字面常量"ab",所以实际执行这句话时,链接的是"ab"和c。实际相当于执行了

String str = new StringBuilder().append("ab").append(c).toString();

2)中,String str = c+"a"+"b";

编译器分析到c为变量,后面的"a"+"b"就不会作为编译时常量来运算了。

实际运行时相当于执行

String str = new StringBuilder().append(c).append("a").append("b").toString();

5.String对象的创建:

1) 包含String字面常量的类或者接口在加载时创建表示该字面常量的String对象。以下两种情况下不会创建新String对象。

a) 一个相同的字面常量已经出现过。

b) 一个相同内容的字符串已经调用了intern操作(比如经过运算产生的字符串调用intern的情形)。

2) 非常量表达式的字符串连接操作有时会产生表示结果的String对象。

3) String字面常量来自类或接口的二进制表示中(也就是class文件中)的CONSTANT_String_info 结构。CONSTANT_String_info结构给出了构成字符串字面常量的Unicode字符序列。 

4) 为了生成字符串字面常量,java虚拟机检查 CONSTANT_String_info结构给出的字符序列:

a) 如果与CONSTANT_String_info结构中给出的字符换内容相同的串实例已经调用过String.intern,得到的字符串字面常量就来自该串的同一实例。

b) 否则,根据CONSTANT_String_info 中的字符序列创建一个新的字符串实例,然后调用intern方法。

例子:一个SCJP题目

11. public String makinStrings() {
12. String s = “Fred”;
13. s = s + “47”;
14. s = s.substring(2, 5);
15. s = s.toUpperCase();
16. return s.toString();
17. }
How many String objects will be created when this method is invoked?

答案是3个。上面已经说明,"Fred","47"是字符串字面常量,它们在在类加载时创建的。这里题目问,方法调用时(!)有多少个String对象被创建,两个字面常量自然不包括在内。3个是:"Fred47","ed4","ED4"。

6.String与基本类型的包装类比较

  相同点,它们都是不变类,使用"=="判断时可能会有类似的性质。

  在java 5之后,java增加了自动装箱和拆箱功能。因此,就有了这样的性质:

Integer i = 5;

Integer j = 5;

System.out.println(i == j);

结果:true.

  这表面上看来是和String相同点,但其实现是极为不同的。这里作为一个不同点来介绍。

  众所周知,自动装箱是这样实现的:

Integer i = 5;

相当于

Integer i = Integer.valueOf(5);//注意不是new Integer(5),这就无法满足java语言规范中的约定了,约定见本文最后

  而在Integer中,静态的创建了表示从-128~+127之间数据的Integer对象,这个范围之内的数进行装箱操作,只要返回相应的对象即可。因此

Integer i = 5;

Integer j = 5;

我们得到的是同一个对象。这是通过类库的设计来实现的。而String的共享是通过java虚拟机的直接支持来实现的,这是它们本质的不同。

  这是Integer类中的部分代码:

private static class IntegerCache {

  private IntegerCache(){}

  static final Integer cache[] = new Integer[-(-128) + 127 + 1];

  static {

  for(int i = 0; i < cache.length; i++)

  cache[i] = new Integer(i - 128);

  }

 }

public static Integer valueOf(int i) {

  final int offset = 128;

  if (i >= -128 && i <= 127) { // must cache

  return IntegerCache.cache[i + offset];

  }

  return new Integer(i);

  }

关于基本类型的装箱,Java语言规范中有如下说明:

  如果被装箱的变量p为true,false,一个处于\u0000~\u007f之间的byte/char,或一个处于-128~+127之间的int /short,令r1和r2为对p的任何两个装箱操作的结果,则r1==r2总是成立的。理想的情况下,对一个基本类型变量执行装箱操作,应该总是得到一 个相同的引用。但在实践中,在现存的技术条件下,这是不现实的。上面的规则是一个注重实效的折衷。

  最后一点,要理解java的方法调用时的传参模型:java中只有pass by value。(不明确这一点,就有乱七八糟的解释,比如典型的Java既有传值,又有传引用,String很特殊……)

//改变参数的值?

public void test(String str){

  str = "Hello";

}

//改变参数的值?

public void test(StringBuffer buffer){

  buffer = new StringBuffer("Hello");

}

//交换两个Integer?

public void swap(Integer a,Integer b){

  Integer temp = a;

  a = b;

  b = temp;

}

这三个方法全是没有意义的方法。

分享到:
评论

相关推荐

    java深入解析

    81 话题14 井然有序——运算顺序的详细挖掘 86 话题15 异曲同工——交换变量的3种方式 90 话题16 择木而栖——开关选择表达式switch的类型内幕 95 第3章 String类 103 话题17 来龙去脉——“+”是怎样连接字符串的?...

    .net性能优化宝典

    1.7.5为字符串容器声明常量,不要直接把字符封装在双引号" "里面。... 14 1.7.6 用StringBuilder代替使用字符串连接符 “+”. 14 1.7.7 避免在循环体里声明变量,... 15 1.8 Hashtable. 15 1.8.1 Hashtable机理....

    Java优化编程(第2版)

    4.2.2 字符串的length()方法与性能优化 4.2.3 tochararray()方法与性能优化 4.2.4 字符串转化为数字 4.3 系统i/o类 4.3.1 java语言中输入/输出流 4.3.2 通过系统缓冲流类提高i/o操作效率 4.3.3 通过自定制缓冲区提高...

    Java范例开发大全 (源程序)

     6.1 字符串类String 108  实例79 创建字符串类 108  实例80 如何使用charAt()方法计算重复字符 109  实例81 按字母顺序比较大小 110  实例82 首尾相连 111  实例83 字符串间的比较 112  实例84 字符集...

    java范例开发大全(pdf&源码)

    6.1 字符串类String 108 实例79 创建字符串类 108 实例80 如何使用charAt()方法计算重复字符 109 实例81 按字母顺序比较大小 110 实例82 首尾相连 111 实例83 字符串间的比较 112 实例84 字符集的解码方法 113 实例...

    java范例开发大全源代码

     6.1 字符串类String 108  实例79 创建字符串类 108  实例80 如何使用charAt()方法计算重复字符 109  实例81 按字母顺序比较大小 110  实例82 首尾相连 111  实例83 字符串间的比较 112  实例84 ...

    java范例开发大全

    6.1 字符串类String 108 实例79 创建字符串类 108 实例80 如何使用charAt()方法计算重复字符 109 实例81 按字母顺序比较大小 110 实例82 首尾相连 111 实例83 字符串间的比较 112 实例84 字符集的解码方法 113 实例...

    java面试题

    答:String是不可变的对象,每次对String类型进行改变都相当于产生了一个新的对象,StringBuffer是可变的字符序列,所以如果要经常改变某个字符串的话建议使用StringBuffer。 list、set、map问题? 答:set 不允许...

    Java范例开发大全(全书源程序)

    6.1 字符串类String 108 实例79 创建字符串类 108 实例80 如何使用charAt()方法计算重复字符 109 实例81 按字母顺序比较大小 110 实例82 首尾相连 111 实例83 字符串间的比较 112 实例84 字符集的解码方法 ...

    深入Java虚拟机(原书第2版).pdf【附光盘内容】

    6.3 特殊字符串 6.3.1 全限定名 6.3.2 简单名称 6.3.3 描述符 6.4 常量池 6.4.1 constant_utf8_info表 6.4.2 constant_integer_info表 6.4.3 constant_float_info表 6.4.4 constant_long_...

    深入java虚拟机第二版

    6.3 特殊字符串 6.3.1 全限定名 6.3.2 简单名称 6.3.3 描述符 6.4 常量池 6.4.1 CONSTANT_Utf8_info表 6.4.2 CONSTANT_Integer_info表 6.4.3 CONSTANT_Float_info表 6.4.4 CONSTANT_Long_info表 ...

    深入JAVA虚拟机(第2版)

    6.3 特殊字符串 6.3.1 全限定名 6.3.2 简单名称 6.3.3 描述符 6.4 常量池 6.4.1 CONSTANT_Utf8_info表 6.4.2 CONSTANT_Integer_info表 6.4.3 CONSTANT_Float_info表 6.4.4 CONSTANT_Long_...

    深入Java虚拟机

    6.3 特殊字符串 6.3.1 全限定名 6.3.2 简单名称 6.3.3 描述符 6.4 常量池 6.4.1 CONSTANT_Utf8_info表 6.4.2 CONSTANT_Integer_info表 6.4.3 CONSTANT_Float_info表 6.4.4 CONSTANT_Long_...

    超级有影响力霸气的Java面试题大全文档

     JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要...

    java 面试题 总结

    JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变...

    WAP 无线应用协议

    6.7.4 常量池 158 6.7.5 编译指示池 160 6.7.6 函数池 162 6.7.7 指标限度 163 6.8 WMLScript指令集 164 6.8.1 约定规则 164 6.8.2 致命的错误 164 6.8.3 优化 164 6.8.4 符号约定 165 6.8.5 指令 165 6.9 字节码...

    亮剑.NET深入体验与实战精要3

    1.3.9 字符串常见操作 21 1.3.10 几个常用的数学函数 27 1.4 .NET的面向对象之门 27 1.4.1 继承——“子承父业” 28 1.4.2 委托——“任务书” 35 1.4.3 事件——“年终分红” 42 1.4.4 反射——“解剖” 49 1.5 ...

    Java开发技术大全 电子版

    第5章数组与字符串200 5.1数组200 5.1.1一维数组的声明200 5.1.2一维数组的创建201 5.1.3一维数组的使用202 5.1.4二维数组的声明204 5.1.5二维数组的创建205 5.1.6二维数组的使用207 5.1.7for~each循环208 ...

    亮剑.NET深入体验与实战精要2

    1.3.9 字符串常见操作 21 1.3.10 几个常用的数学函数 27 1.4 .NET的面向对象之门 27 1.4.1 继承——“子承父业” 28 1.4.2 委托——“任务书” 35 1.4.3 事件——“年终分红” 42 1.4.4 反射——“解剖” 49 1.5 ...

Global site tag (gtag.js) - Google Analytics