`
xinklabi
  • 浏览: 1606255 次
  • 性别: 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开发Java经验技巧共6页.p

    在Java编程语言中,字符串常量池(String Constant Pool)是一个重要的概念,它与程序的内存管理和性能优化密切相关。理解这个概念对于任何Java开发者来说都至关重要。字符串常量池是Java虚拟机(JVM)在运行时为...

    java 创建字符串类

    - 字符串常量池是JVM内存中的一个特殊区域,用于存放所有的字符串字面量。当创建一个`String`对象时,如果常量池中已经存在相同内容的字符串,那么将返回该字符串的引用,而不是创建新的对象。 4. **字符串比较**...

    String 字符串讲解

    - Java虚拟机(JVM)维护了一个字符串常量池,用于存储字符串字面量。如果两个字符串字面量相同,它们在内存中只会有一个实例。 3. **字符串连接**: - 使用`+`运算符可以合并字符串,如`str1 + str2`。这会导致...

    Java语言中字符串常量和变量的分析与比较.pdf

    `String`类通常用来表示字符串常量,而`StringBuilder`用于处理字符串变量。 1. 字符串常量(String类) - 创建方式:可以通过构造函数`new String("content")`或者直接赋值`String str = "content"`来创建。后者...

    【Java编程教程】详解Java String字符串.pdf

    当使用字符串字面量(即双引号包围的文本)创建字符串时,Java虚拟机(JVM)会首先查看字符串常量池,如果字符串已经存在,就直接返回引用;如果不存在,JVM则会在池中创建新的字符串实例。 创建字符串对象主要有两...

    Java 字符串

    在Java编程语言中,`String`类是处理文本数据的核心工具。它被广泛用于各种操作,如创建、比较、操作...了解字符串常量池、比较和操作方法,以及何时使用可变类(如`StringBuilder`)是每个Java开发者必备的基础知识。

    详解Java的String类型程序

    1. **字符串常量池与`new`关键字:** - `String a = "hehe";`: 这里直接使用双引号创建字符串,该字符串被存储在字符串常量池中。 - `String b = "he" + "he";`: 同样地,字符串字面量连接的结果也会被存放在常量...

    深入了解java中的String-字符串(csdn)————程序.pdf

    - 常量池:String对象在创建时会尝试在字符串常量池中查找是否存在相同的字符串,如果存在,就直接返回其引用,否则会在堆中创建新的对象并将其放入常量池。 2. 字符串创建: - 直接赋值创建:如`String str = ...

    Java字符池.pdf

    如果字符串常量池中已经存在一个与当前对象Unicode序列相同的字符串,那么`intern()`方法会返回常量池中的引用;否则,它会在常量池中创建一个新的字符串并返回引用。在示例3中,调用`s1.intern()`并不会改变`s1`的...

    字符串string.zip

    值得注意的是,字面量方式创建的字符串会存储在常量池中,而使用构造函数会创建堆内存中的新对象。 3. **字符串比较**:Java提供了多种比较字符串的方法,如`equals()`用于内容比较,`equalsIgnoreCase()`忽略大小...

    Java SE编程入门教程 String字符串(共27页).pptx

    - 当创建字符串时,如果字符串常量池中已经存在相同的字符串,那么Java会复用这个字符串,不会创建新对象。这就是为什么`str1 == str2`在某些情况下返回`true`的原因。 7. **字符串的其他方法**: - `charAt(int ...

    String字符串

    10. **字符串常量池** - 常量池存储了所有字面量字符串,避免了重复对象的创建,提高了性能。 了解并熟练掌握这些`String`字符串的知识点对于任何Java开发者来说都是至关重要的。在实际编程中,正确使用字符串能...

    String字符串比较1

    在Java中,字符串字面量的连接如果都是常量,如`"hel" + "lo"`,编译器会在编译时计算出结果并放入常量池,因此`hello == "hel" + "lo"`返回`true`。而`"hel" + lo`在运行时进行连接,生成一个新的字符串对象在堆中...

    java中常用字符串方法总结

    Java的字符串字面量会被放入字符串常量池中,如果两个字符串字面量内容相同,它们会指向同一个对象。例如,`str2`与`"World"`在内存中是共享的。 3. **字符串比较** `equals()`方法用于比较字符串内容是否相等,...

    javaString总结共13页.pdf.zip

    - 使用`String.intern()`方法可以将字符串放入字符串常量池,如果池中已存在相同值,则返回池中的引用。 9. **Java 11的新特性**: - `strip()`, `stripLeading()`, 和 `stripTrailing()`方法用于去除字符串两侧...

    深入了解java 中的String

    这种方式创建的字符串不会存放在字符串常量池中,而是单独存储在堆中,因此可能会导致更多的内存占用。 #### 字符串连接 可以使用 `+` 运算符或者 `concat()` 方法来连接字符串。例如: ```java String s3 = "ab" ...

    String容量大小区分

    `,该字符串会被存储在一个特殊的区域——字符串常量池(String Constant Pool)中。如果尝试再次创建一个相同内容的字符串,如`String s2 = "Hello";`,实际上s2指向的是字符串池中已存在的"Hello"。这种方式可以...

    字符串处理文档

    Java虚拟机维护了一个字符串常量池,用于存储所有的字面量字符串和由`new String()`构造的字符串。相同内容的字符串在常量池中只有一份。 13. ** intern() 方法** `intern()`方法会将字符串转换为常量池中的引用...

    Java第6章 字符串 含源代码

    字面量字符串是常量,存储在字符串常量池中,而构造函数创建的字符串则在堆内存中。 2. **不可变性** Java中的字符串是不可变的,这意味着一旦创建,就不能更改。所有改变字符串的方法,如`concat()`, `substring...

    Java基础复习笔记03我们不会注意的陷阱

    对于字符串的处理,Java虚拟机(JVM)有一个特殊的优化机制,即**字符串常量池**(String Constant Pool)。当我们在程序中使用字符串直接量时(即不使用`new`关键字创建的字符串),JVM会在字符串常量池中查找是否...

Global site tag (gtag.js) - Google Analytics