问题一:
对于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解释一下它这个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 stringis 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”不应该是一个对象。