- 关于java的String的探讨
-
问题一:
对于String类型,它自己有一个String类型的对象池(StringPool);对于通过直接赋值创建的对象,它们都放在StringPool中,并且在每次创建的时候,都回去检查StringPool中有没有这样的对象,如果有的话,就将对象的引用指向此对象,没有的话就在对象池中创建此对象;对于new方法创建的String对象,不会放在StringPool中,每次new不管堆内存中有没有此对象都会重新创建一个新的String对象,所以采用new String方式创建对象非常耗费内存,不提倡这样使用。
下面用具体的例子说明:
String a1 = "abcd"; //执行此条语句的时候,首先检查String串池中是否有对象“abcd”,由于没有所以立即在对象池中创建,并让a1指向此对象
String a2 ="abcd"; //执行此条语句的时候,仍然首先检查String串池中有没有对象'abcd',由于上一句已经在串池中创建了对象'abcd',不在创建,而是直接让a2指向串池中 //的'abcd';
String b1 = new String("abcd") ; //执行此条语句的时候,由于是采用new String()方法创建,所以直接在堆内存中创建一个新的对象(也不管堆内存中有没有都要创建)。
String b2 = new String("abcd"); //执行此条语句的时候,由于是采用new String()方法创建,所以尽管堆内存中已经存在一个具有相同内容的对象,仍然会在堆内存中重新创建一个新的对象;
//明白上面的执行过程,下面这些程序的执行结果,就在预料之中
System.out.println(a2==a1); //true a1和a2由于指向的是相同的对象,所以a1和a2里面存的是相同的地址值,这样的比较一定返回true
System.out.println(b1==b2); //false 由于b1和b2都是在堆内存中创建的新的对象,所以他们指向不同的对象,b1和b2里面也当然存的是不同的地址值,这样的比较也当然会返回false
同样的,
System.out.println(b1==a1); // 这样的比较结果当然为false,b1和a1指向的是不同的对象。
System.out.println(b2==a1); //false 同理可推出
至于String equals()方法是对两个对象内容的比较,很容易看出来的。
问题二:String使用“+”连接字符串
众所周知,String使用“+”连接在多次连接的时候是很慢的,应该使用StringBuffer(或者StringBuilder),今天发现它还有另外的问题:
String a1 ="abcd";
String a2 ="abcd";
String a3 ="ab";
a3+="cd";
System.out.println(a1==a2); //true 这个问题一已经验证
System.out.println(a3==a2); //false 这个是为什么呢?
上述a3==a2怎么会返回false,a3也是采用对象池的方式创建的,按说它被+"cd"之后应该指向对象池中的“abcd”但为何它没和a1指向同一块对象呢?要解决这个问题首先从String的对象怎么实现“+”这个操作的,参看与String相关的api:
The Java language provides special support for the string concatenation operator ( + ), and for conversion
of other objects to strings. String concatenation is implemented through the StringBuilder(or StringBuffer)
class and its append method. String conversions are implemented through the method toString,defined
by Object and inherited by all classes in Java. For additional information on string concatenation and conversion,
see Gosling, Joy, and Steele, The Java Language Specification.
解释一下它这个api,String类型的对象使用“+”进行串连接的时候,是通过StringBuffer或者是StringBuilder的append()方法来实现的,这里可以做一个大胆的猜想:也就是说在执行“+”操作时String对象会将自身构造成StringBuffer或者StringBuilder对象,然后再通过他们的append()方法来实现,之后调用StringBuffer或者StringBuilder的toString()方法返回给表达式,在看看StringBuffer或者StringBuilder的toString()方法的源码:
StringBuilder的toString()方法:
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
StringBuffer的toString()方法:
public synchronized String toString() {
return new String(value, 0, count);
}哈哈,看到这个源码,我心中无比的激动,这个问题解决了,原来StringBuffer或者StringBuilder对象在转换为String对象的时候,它又重新new 了一个String对象。难怪a3在执行完“+”操作会和a1不一样,因为a3指向的对象这个时候已经跑到堆内存当中。总结一下,String的“+”操作,首先会将自身构造成StringBuffer或者StringBuilder对象,然后调用它们的append()方法,连接完成以后,再调用StringBuffer或者StringBuilder的toString()方法将连接好的串拷贝一份来创建一个新的String对象返回给原来的表达式。
String的"+"操作是够麻烦的,要重新创建一个StringBuffer或者StringBuilder对象 ,连接完了,还要创建一个String对象来接收值,这个过程要在堆内存中new两个对象,难怪String的“+”操作在重复上千次操作的时候会速度很慢;
问题三:String str = new String("Hello") 到底创建了几个对象?
回答这个问题,参看以下jdk的api文档:
String
public String(String original)
Initializes a newly created String object so that it represents the same sequence of characters as
the argument; in other words, the newly created string is a copy of the argument string. Unless an
explicit copy of original is needed, use of this constructor is unnecessary since Strings are immutable.
Parameters:
original - a String
它的大致意思是说采用new String(String original)创建一个对象,实际上就是对original参数的一份拷贝,这样在程序执行的时候也确实创建了两个对象,因为在向构造方法传参数的时候的时候,已经创建了一个对象就是original ,只不过这个对象根据在问题一中的分析,它应该是在String串池中创建;另外一个对象由于是采用new的方法,所以会在堆内存中为其分配内存空间。
所以,String str = new String("hello"); 在执行这条语句的时候也确实创建了两个对象,一个是在String串池当中,另外一个是在堆内存当中,
根据java的垃圾回收机制,在串池的对象由于没有变量指向他,应该被标识为垃圾,它对于程序来说已经无用了,也不可能再用。所以,这条语句也确实创建了两个对象,但是一个参数对象也立即被视为垃圾,故,严格的讲上述语句说是创建两个对象不是很确切。
问题四:单独的"str"是不是对象
我认为不是:原因
第一: String a1 ="abcd";
String a2 ="ab"+"cd";
String a3 ="ab";
a3 =a3+"cd";
System.out.println(a2==a1); //true
System.out.println(a3==a1); //false
如果单独的"ab"是一个对象的话,那么利用“ab”连接“cd”与用a3连接"cd"得到的结果是一样的,但事实上是不一样的。
第二:如果单独的“str”是一个对象的话,那么下面的语句都应该被编译器所接受:
new Integer(2);
new Date();
"str"; //error: "str" 不是语句
所以,单独的“str”不应该是一个对象。