试题一:
String s = new String("abc");创建了几个String对象?
涉及内容:
引用变量与对象的区别;
字符串文字"abc"是一个String对象;
文字池(pool of literal strings)和堆(heap)中的字符串对象。
1、引用变量与对象:
A aa;
这个语句声明一个类A的引用变量aa[我们常常称之为句柄],而对象一般通过new创建。所以题目中s仅仅是一个引用变量,它不是对象。
2、Java中所有的字符串文字[字符串常量]都是一个String的对象。有人[特别是C程序员]在一些场合喜欢把字符串"当作/看成"字符数组,这也没有办法,因为字符串与字符数组存在一些内在的联系。事实上,它与字符数组是两种完全不同的对象。
System.out.println("Hello".length());
char[] cc={'H','i'};
System.out.println(cc.length);
3、字符串对象的创建:
由于字符串对象的大量使用(它是一个对象,一般而言对象总是在heap分配内存),Java中为了节省内存空间和运行时间(如比较字符串时,==比equals()快),
在编译阶段就把所有的字符串文字放到一个文字池(pool of literal strings)中,而运行时文字池成为常量池的一部分。
文字池的好处,就是该池中所有相同的字符串常量被合并,只占用一个空间。
我们知道,对两个引用变量,使用==判断它们的值(引用)是否相等,即指向同一个对象:
String s1 = "abc" ;
String s2 = "abc" ;
if( s1 == s2 ) System.out.println("s1,s2 refer to the same object");
else System.out.println("trouble");
这里的输出显示s1,s2 refer to the same object,两个字符串文字保存为一个对象。就是说,上面的代码只在pool中创建了一个String对象。
即s1,s2指向了同一个对象。
s1-------
|
|-------"abc"
|
s2-------
现在看String s = new String("abc");语句:
这里"abc"本身就是pool中的一个对象,而在执行new String()时,将pool中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s持有。ok,这条语句就创建了2个String对象。
即引用s指向了两个对象。
|-------"abc"
|
s-------|
|
|-------new String()
String s1 = new String("abc") ;
String s2 = new String("abc") ;
if( s1 == s2 ){ //不会执行的语句}
这时用==判断就可知,虽然两个对象的"内容"相同(equals()判断),但两个引用变量所持有的引用不同,
上面的代码创建了几个String Object? (三个,pool中一个,heap中2个。)
|-------new String()
|
s1------|
|
|-------"abc"
|
s2--------|
|
|-----new String()
String s1 = "abc";
String s2 = new String("abc");
上面代码创建了几个对象?
两个。pool中一个,heap中一个。
试题二:
先看看下面的代码
public String makinStrings()
{
String s = "Fred";
s = s + "47";
s = s.substring(2, 5);
s = s.toUpperCase();
return s.toString();
}
问:调用makinStrings方法会创建几个String对象呢。
答案:3个
上面的方法有五条语句:现在让我们来一条一条分析一下。
1、String s = "Fred"; 结论:创建了一个String对象
这条语句相当于String s = new String("Fred");
因此,毫无疑问,第一条语句创建了一个String对象,我想没有有疑问吧?
2、s = s + "47"; 结论:未创建String对象
这条语句也许很多人认为是创建了String对象,我一开始也是这么认为的。但是为了验证我的想法。决定
用点法术恢复这条语句的本来面目。(有很多时候,编译器总是在里面搞一些小动作,javac.exe也不例外)
现在找到这个程序所生成的.class文件(假设是Test.class),找一个反编译工具,我推荐JAD,可以http://www.softpedia.com/progDownload/JAD-Download-85911.html下载
下载后,有一个jad.exe,将其路径放到环境变量path中(只限windows)。并在.class文件的当前路径执行如下的命令:jad Test
然后大喊一声“还我本来面目”
会在当前目录下生成一个Test.jad文件,打开它,文件内容如下:
public String makinStrings()
{
String s = "Fred";
s = (new StringBuilder(String.valueOf(s))).append("47").toString();
s = s.substring(2, 5);
s = s.toUpperCase();
return s.toString();
}
哈哈,其他的语句都没变,只有第二条变长了,虽然多了个new,但是建立的是StringBuilder对象。原来这是java编译器的优化处理。原则是能不建String对象就不建String对象。而是用StringBuilder对象加这些字符串连接起来,相当于一个字符串队列。这种方式尤其被使用在循环中,大家可以看看下面的代码:
String s = "";
for(int i=0; i < 10000000; i++)
s += "aa";
没有哪位老大认为这是建立了10000000个String对象吧。但不幸的是,上面的代码虽然没有建立10000000个String对象
但却建立了10000000个StringBuilder对象,那是为什么呢,自已用jad工具分析一下吧。
正确的写法应该是:
StringBuilder sb = new StringBuilder("");
for(int i=0; i < 10000000; i++)
sb.append(String.valueOf(i));
3、s = s.substring(2, 5); 结论:创建了一个String对象
也许有很多人一开始就认为这条语句是创建了一个String对象,那么恭喜你,这条语句确实创建了一个String对象
实际上就是substring方法创建了一个String对象。这也没什么复杂的,自已下一个JDK源代码,看看substring是如何实现的就可以知道了。我先说一下吧。先不用管substring是如何实现的,反正在substring方法返回时使用了一个new显式地建立了一个String对象不信自己看看源码。原码如下:
public String substring(int beginIndex, int endIndex) {
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
4、s = s.toUpperCase();
结论:创建了一个String对象
toUpperCase()、toLowCase()和substring()方法类似,在返回时也是使用了new建立了一个String对象。
public String toUpperCase(Locale locale) {
return new String(0, result.length, result);
}
5、return s.toString(); 结论:未创建String对象
toString方法返回的就是this,因此,它的返回值就是s。
public String toString() {
return this;
}
这道题还算比较简单,再给大家出一个更复杂一点的,也是关于String对象的创建的(只是改了一个原题)。
试题三:
public String makinStrings()
{
String s = "Fred";
s = s + "Iloveyou.".substring(1).toLowerCase();
s = s.substring(0);
s = s.substring(0,1).toUpperCase();
return s.toString();
}
先公布答案吧,上述代码也创建了3个String对象,哈哈!
为什么呢?
要想知道为什么,先得弄清楚substring、toLowerCase和toUpperCase什么时候创建String对象,什么时候不创建对象。
substring方法在截取的子字符串长度等于原字符串时,直接返回原字符串,并不创建新的String对象。
即substring(0),substring(o,字符串长度)将不会创建对象,直接返回自身。
public String substring(int beginIndex) {
return substring(beginIndex, count);
}
public String substring(int beginIndex, int endIndex) {
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
示例:
"unhappy".substring(2) returns "happy"
"unhappy".substring(0) returns "unhappy"
"smiles".substring(1, 5) returns "mile" substring从下标0开始,第0个为 s,第一个为m,返回的个数=5-1即返回4个字符。
toLowerCase方法在字符串中根本没有需要转换的大写字母时直接返回原字符串,如"abcd".toLowerCase()直接返回abcd,并不创建新的String对象看原代码:
public String toLowerCase() {
return toLowerCase(Locale.getDefault());
}
public String toLowerCase(Locale locale) {
if (locale == null) {
throw new NullPointerException();
}
int firstUpper;
/* Now check if there are any characters that need to be changed. */
scan: {
int c;
for (firstUpper = 0 ;firstUpper < count ;firstUpper += Character.charCount(c)) {
c = codePointAt(firstUpper);
if (c != Character.toLowerCase(c)) {
break scan;
}
}
return this;
}
char[] result = new char[count];
int resultOffset = 0;
System.arraycopy(value, offset, result, 0, firstUpper);
String lang = locale.getLanguage().intern();
boolean localeDependent = (lang == "tr" || lang == "az" || lang == "lt");
char[] lowerCharArray;
int lowerChar;
int srcChar;
int srcCount;
for (int i = firstUpper; i < count; i += srcCount) {
srcChar = codePointAt(i);
srcCount = Character.charCount(srcChar);
if (localeDependent || srcChar == '\u03A3') { // GREEK CAPITAL LETTER SIGMA
lowerChar = ConditionalSpecialCasing.toLowerCaseEx(this, i, locale);
} else {
lowerChar = Character.toLowerCase(srcChar);
}
if ((lowerChar == Character.ERROR) ||
Character.isSupplementaryCodePoint(lowerChar)) {
if (lowerChar == Character.ERROR) {
lowerCharArray =
ConditionalSpecialCasing.toLowerCaseCharArray(this, i, locale);
} else {
lowerCharArray = Character.toChars(lowerChar);
}
/* Grow/Shrink result. */
int mapLen = lowerCharArray.length;
char[] result2 = new char[result.length + mapLen - srcCount];
System.arraycopy(result, 0, result2, 0,
i + resultOffset);
for (int x=0; x<mapLen; ++x) {
result2[i+resultOffset+x] = lowerCharArray[x];
}
resultOffset += (mapLen - srcCount);
result = result2;
} else {
result[i+resultOffset] = (char)lowerChar;
}
}
return new String(0, result.length, result);
}
toUpperCase方法和toLowerCase类似。"ABCD".toUpperCase()直接返回ABCD。
知道了这个,上面的代码就非常清楚了。
public String makinStrings()
{
String s = "Fred"; // 创建一个String对象
s = s + "Iloveyou.".substring(1).toLowerCase(); // substring(1)创建一个String对象,由于toLowerCase()转换的字符串是"loveyou.",没有大写字母,因此,它不创建新的String对象
s = s.substring(0); // 由于substring(0)截获的是s本身,因此,这条语句不创建新的String对象
s = s.substring(0,1).toUpperCase(); // substring(0,1)创建了一个String对象,但由于substring(0,1)的结果是"F",为一个大写字母,因此,toUpperCase直接返回"F"本身。
return s.toString();
}
试题四、
public class TestStringBuffer {
public static void main(String[] args) {
StringBuffer a = new StringBuffer("A");
StringBuffer b = new StringBuffer("B");
operate(a,b);
System.out.println(a+","+b);
String a1 = new String("A");
String b1 = new String("B");
operateStr(a1,b1);
System.out.println(a1+","+b);
}
public static void operate(StringBuffer x,StringBuffer y){
x.append(y);
y=x;
}
public static void operateStr(String x,String y){
x+=y;
y=x;
}
}
输出结果为:
AB,B
A,B
分析:
StringBuffer只是存储字符串的缓冲区,通过x.append(y)即得到AB
但 operate(a,b);传递参数的时候只是让x也指向缓冲区,y指向另一个缓冲区,使用y=x并不能改变a,b所指向的缓冲区,所以y=x不能改变b指向的内容
所以前面输出AB,B
对String类,x+y只是创建了一个StringBuffer对象,在Buffer中加入了y的值,这样a1所指向的仍然是A,y=x一样不能改变什么,所以最后输出
结果为A,B