本文共 6854 字,大约阅读时间需要 22 分钟。
做了好几天的面试题,发现 String 类的题目一直是个大头,看似简单,实则不然,牵扯到很多底层的东西。接下来我就跟着源码和题目来分析一下把
public final class String implements java.io.Serializable, Comparable, CharSequence { /** The value is used for character storage. */ private final char value[];
可以看到,String 类是被 final 修饰的,即 String 类不能被继承。其中有一个很重要的数组,char 数组,用来保存字符串的,既然是用 final 关键字来修饰的,那就代表 String 是不可变的
举以下两个方法为例,通过源代码可以看到,substring,replace 最后都是通过 new String(xxx) 来产生了一个新的 String 对象,最原始的字符串并没有改变
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen);}public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this;}
我们可以用例子验证一下:
public void stringTest3(){ String a = "nihaoshije"; String b = a.substring(0, 4); System.out.println(b); System.out.println(a);}
最终的输出为:niha nihaoshije
。可见如我们所预料的,原来的 String 的值并没有改变
我们可以通过 StringBuilder 的源码分析下
我们假设使用 append 方法,该方法返回的依然是 StringBuffer 对象本身,说明他确实是值改变了
public StringBuilder append(String str) { super.append(str); return this;}
该方法实际调用的是 StringBuilder 的父方法。该父方法,会先检测容量够不够,不够的话会进行扩容,然后调用 String 的 getChars 方法。注意,最后返回的依旧是 StringBuffer 对象
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this;}
getChars 方法具有 4 个参数
其中的 dst 对应 StringBuilder 的父类里的数组 char[] value
,该数组是专门用来存放字符的。append(String str) 里的字符串就是以字符格式一个个加到 char 数组里的
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);}
我们可以举一个例子来看一下 StringBuffer 的对象是否真正改变了
public void stringBuilderTest() { StringBuilder stringBuilder = new StringBuilder("hello"); System.out.println(stringBuilder); stringBuilder.append("world"); System.out.println(stringBuilder);}
第一输出为 hello,第二次再次输出同一个对象,变为 helloworld,结果一目了然
这里我讨论的是使用“+”对两个字符串相加的过程,看似很简单的相加,其实蕴藏着很多的密码
《Java编程思想》提到,String 对象的操作符“+”其实被赋予了特殊的含义,该操作符是 Java 中仅有的两个重载过的操作符。而通过反编译可以看到,String 对象在进行“+”操作的时候,其实是调用了 StringBuilder 对象的 append() 方法来加以构造
按照惯例,我们先来看两个字符串相加的例子
public void stringTest4(){ String a1 = "helloworld"; String a = "hello" + "world"; String b = "hello";