- 浏览: 118024 次
- 性别:
- 来自: 北京
最新评论
-
supertangcugu:
senninha 写道然鹅。。jdk1.8已经把这货移到了he ...
小例子:如何说明String常量池的位置 -
senninha:
然鹅。。jdk1.8已经把这货移到了heap区?只有把-Xmx ...
小例子:如何说明String常量池的位置 -
onada1108:
还有你是基于jdk那个版本
小例子:如何说明String常量池的位置 -
onada1108:
我想问下,你这个方法跑啦多长时间
小例子:如何说明String常量池的位置 -
hu_xuefeng:
hsbljyy 写道看来真的有很多谬误啊!当变量在-128~1 ...
从“关于Java堆与栈的思考”一帖看错误信息的传播
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(Stringoriginal)
初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。由于 String 是不可变的,所以无需使用此构造方法,除非需要 original 的显式副本。
参数:
original - 一个 String。
注意:无需使用此构造方法!!!
3)单独的说明第6点:
String str = new String("abc"); //这是很白痴的创建String对象的方式,实际没有人这么写程序
str = str.intern();
当调用 intern 方法时,如果池已经包含一个等于此 String
对象的字符串(用 equals(Object)
方法确定),则返回池中的字符串引用。否则,将此 String
对象添加到池中,并返回此 String
对象的引用。
很明显,在这个例子中"abc"引用的对象已经在字符串池中了,再调用intern返回的是已经存在池中内容为"abc"的字符换对象的引用。在上面的例子中也说明了这个问题。
2. String类的实例表示表示Unicode字符序列。String字面常量是指向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总是成立的。理想的情况下,对一个基本类型变量执行装箱操作,应该总是得到一个相同的引用。但在实践中,在现存的技术条件下,这是不现实的。上面的规则是一个注重实效的折衷。
7.String和基类类型相同的一个地方
String类型和基本类型的常量都会进行常量折叠(或者叫常量传播)处理。
比如:
final int a = 5;
int b = a+1;//编译时就计算出b
final String str = "Hello";
String str2 = str + " World"; //编译时就计算出str2
尤其是下面这种情况要注意:
public class A {
public static final String str = "Hello";
}
public class B{
public void test(){
String s = A.str;
// 处理s
……
}
}
编译之后,B中并不存在类A的信息,String s = A.str;已经直接处理为s = "Hello";所以,如果对A中str的内容进行了修改,B也必须重新编译。
最后一点,要理解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;
}
ps:关于java的传参方式的说法,参考James Gosling等人的《Java程序设计语言》等,里面有说明,为什么Java中只有pass by value一种传参方式,以及什么是pass by reference。
发表评论
-
接口类型引用为何能调用Object类的方法
2009-09-20 19:15 3443首先看这段代码 ... -
无聊:详解java的构造方法
2009-09-20 00:47 5632关于java的构造方法有几个简单的问题: 1. ... -
小例子:如何说明String常量池的位置
2009-09-20 00:44 14430以前批过《关于Java堆与栈的思考》这个帖子,具体内容 ... -
无聊:为啥可以使用空引用调用static方法或者访问static field
2009-09-10 00:52 2009先看代码: public class ... -
无聊:谁说private方法自动是final的?兼评《Java编程思想》中的几个问题
2009-09-03 10:07 7184首先声明,本文无任何实际价值,只是讨论一些 ... -
从“关于Java堆与栈的思考”一帖看错误信息的传播
2009-08-05 05:03 10014我对转贴的信息一直有敌意,原因如下:首先,除了 ... -
谜题解析
2009-07-30 12:35 2844题目接http://zangxt.iteye.com/blog ... -
几个谜题,深入的了解java
2009-07-29 18:10 5996在2009年的JavaOne大会上,Joshua Bloch和 ... -
java中匿名内部类的构造方法调用
2008-07-28 19:14 12055与人讨论匿名内部类的构造方法问题,自己 ... -
static方法与覆盖
2008-08-01 17:50 1331The Java Programming Langu ... -
局部内部类 与 final变量
2008-09-12 23:39 1247局部内部类可以访问定义该类的作用字段中的所有变量,包 ... -
java 1.6 表达式计算方法
2008-10-11 12:42 1097import javax.script.Script ... -
看一下enum的原理
2008-10-29 13:22 2504java5增加了enum,其实对enum的处理很简 ... -
一个静态初始化的问题
2008-10-31 21:59 1176问题代码: public class Test1{ ... -
读Java Puzzlers笔记: 表达式问题
2008-11-02 11:27 1162一.奇数性判断一个数是否为奇数,方法: publ ... -
i++ ,++i,i=i++的问题
2008-11-05 20:16 1229很经典的无聊问题之一。 int i=0;i=i+ ... -
instanceof的使用
2008-11-06 20:47 1404总结:Java Puzzlers一书中的谜题50 看 ... -
Java的好书
2008-11-14 21:59 2273学Java也算有一段时间了,记录一下看过或者还需要 ... -
HashMap小测试
2008-11-25 18:48 1142测试HashMap代码: ... -
jvm对boolean类型的处理
2008-11-25 19:16 2051JVM对boolean类型的支持比较有意思,java虚拟 ...
相关推荐
本文档将对`String`类型的一些关键知识点进行总结,包括字符串的格式化、特殊运算操作、进制转换以及与之相关的其他实用技巧。 #### 二、字符串格式化 1. **小数保留指定位数** - 示例代码:`double sum = r * r *...
### 知识点一:Java中的String与char类型转换 #### 概述 在Java编程语言中,`String` 类型和 `char` 类型是两种基本的数据类型。`String` 类型用于表示一系列字符的集合,而 `char` 类型则代表单个字符。有时我们...
知识点: 1. XML 文件的基本结构:XML 文件是一种标记语言,使用标签来标记数据。每个 XML 文件都有一个根元素,根元素可以包含多个子元素。 2. DOM 解析方式:DOM 解析方式将 XML 文件解析成一个树形结构,然后...
总结一下,当我们处理JavaScript中的数值和字符串时,有以下关键知识点: 1. 使用`toString()`方法将Number类型转换为String类型。 2. 使用`replace()`方法结合正则表达式去除字符串中的特定字符,如逗号。 3. 使用...
Java 中 String, StringBuffer 与 StringBuilder 三种字符串类型的区别是很多开发者经常混淆或不了解的知识点。今天,我们将深入探讨这三种字符串类型的区别和用法。 首先,让我们从 String 类型开始。String 类型...
本文将详细探讨这两个关键字的区别以及C#中字符串处理的相关知识点。 首先,`string`是C#中的一个预定义类型,它是`System.String`类的别名。这意味着当你声明一个`string`变量时,实际上是在创建一个`String`对象...
JavaScript 数据类型知识点总结及基础类型的使用方法 JavaScript 中的数据类型是指在编程语言中对常用的各种数据类型进行明确的划分,以便让计算机正确的识别和处理不同的数据类型。在 JavaScript 中,常用的数据...
**知识点详述:** 在Java编程语言中,`String`类是一个极其重要的类,用于处理文本数据。由于其不可变性(即一旦创建后不能改变),`String`对象在内存中是高效的,尤其是在需要比较字符串或频繁操作字符串但不希望...
本篇将重点讨论如何利用`std::string`进行数据类型转换,并分析为什么在某些情况下使用`std::string`比传统的`char *`更加高效和安全。 ### 使用 `std::string` 进行类型转换 #### 1. 为何选择 `std::string` `...
根据给定的文件信息,以下将详细解析与“C# String 查找”相关的知识点: ### C#中的String查找 在C#中,字符串是不可变的对象类型,这意味着一旦创建了一个字符串对象,就不能改变其内容。因此,在进行字符串操作...
综上,我们可以总结出以下几个关键知识点: 1. String到int的转换:在Java中,将String转换成int类型是一种常见的操作,通常使用`Integer.parseInt(String s)`或者`Integer.valueOf(String s)`方法来实现。但如果...
### Date与String互相转换知识点详解 #### 一、引言 在Java开发过程中,日期(`Date`)与字符串(`String`)之间的相互转换是非常常见的需求。这不仅涉及到数据类型的转换,还涉及到时间格式的处理。本文将详细...
根据给定的文件信息,我们将深入探讨如何实现一个自定义的`String`类,并重点讲解其构造函数、析构函数、拷贝构造函数以及赋值操作符。在C++编程语言中,这些成员函数对于正确地管理动态分配的内存至关重要。 ### ...
总结来说,通过以上知识点,我们可以了解到如何从头开始设计和实现一个简单的C++字符串类,模拟标准库中的string行为。这不仅有助于理解标准string的工作原理,还能加深对C++面向对象编程的理解,特别是关于封装、...
总结起来,这篇博客可能涵盖了以下知识点: 1. MyBatis中`<foreach>`标签的使用,包括如何遍历集合,构建动态SQL。 2. MyBatis源码解析,如何处理List类型的参数和返回值。 3. 字符串操作技巧,如动态SQL构建,条件...
### JAVA String 类函数总结 #### 一、字符串创建与初始化 在 Java 中,`String` 类是最常用的字符串处理工具之一。它可以用来表示一系列字符,并且提供了丰富...这些知识点对于日常开发非常重要,希望对你有所帮助。
这里还有一个重要的知识点需要指出,那就是在JavaScript中,数值调用toString()方法默认转换为十进制字符串,而如果你想转换为其他进制的字符串表示形式,需要在toString()方法中指定进制基数,如toString(2)表示...
这里的重点在于确保转换的准确性和兼容性,因为`java.sql.Date`与`java.util.Date`是不同的,前者主要用于与数据库交互。 #### 二、解决方案 为了实现这个需求,我们可以编写一个方法`stringToSqlDate`,该方法...