Java源码赏析(三)初识 String 类
由于String类比较复杂,现在采用多篇幅来讲述
这一期主要从String使用的关键字,实现的接口,属性以及覆盖的方法入手。省略了大部分的字符串操作,比如split()、trim()、replace()、contains()、matches()等。
在《Java源码赏析(五)再识 String 类》中,会增加对String类中的字符串操作进行详细描述。
今后,还会在《Java源码赏析(六)Java String 三顾》中介绍StringBuffer、StringBuilder两个类。
/** * 精简的String结构,便于我们初步的理解, 省略了大部分构造方法和字符串操作 * 使用了final关键字,说明此类是最终类,无法继承 * 实现了序列化接口,排序接口,CharSequence接口 */ public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; private int hash; // Default to 0 private static final long serialVersionUID = -6849794470754667710L; /** 指明需要实例化的字段 */ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; /** 省略CaseInsensitiveComparator()的实现,主要是用于按ASCII码的排序规则进行排序 */ public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); /** 实现Comparable<String>,可用于集合类的排序功能 */ public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; } /** 覆盖hashCode(),可以用于switch以及Map,Set的查询 */ public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; } /** 覆盖父类equal(),实现了值比较 */ public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } /** 覆盖父类toString() */ public String toString() { return this; } /** * 实现CharSequence接口 * 共有length(), charAt(int index), subSequence(int beginIndex, int endIndex) 等 * 在实现subSequence()方法时使用了String中substring()方法 */ /** 获取字符串长度 */ public int length() { return value.length; } /** 获取index位置的字符 */ public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; } /** 获取字符串子串 */ public CharSequence subSequence(int beginIndex, int endIndex) { return this.substring(beginIndex, endIndex); } /** 从索引位置beginIndex到endIndex处获取子串 */ 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 native String intern();
阅读一个类的源码,首先要定性。
1. 它是一个final的类,说明不可继承,
2. value为final,说明值创建后不可更改。
3. 所有看起来会修改String的方法都返回了一个新的String对象。
这种字符串的不可变性的好处在于简单(不用考虑并发问题),安全(不用考虑子类、值被修改等问题),高效(由于不可变可以设计常量池的处理)等。
大概了解了String的属性和方法,我们回顾源码赏析一、二的内容,发现前两篇内容无一不体现在这个类中。希望大家可以结合前两篇来看待这个经典的类。
一、Object中的重写方法 equals()、 hashCode()、 toString()
二、常见的接口Serializable, Comparable的实现
除了上面两点之外,还实现了CharSequence这个字符串的通用接口。StringBuffer、StringBuilder也实现了此接口。
还有需要注意的一点是,字符串拥有 “操作符重载” ( +, +=)。我们可以方便的使用这个操作符进行一些处理。
比如 连接字符串
String a = "hello"; String b = ", world"; //输出 hello, world // System.out.println(a + b);
注, “+” 最后不好放在for循环之中(第一种写法创建了100个对象),而第二种只创建了1个对象
String s = new String(); for(int i=0; i<100; i++) { s = s + i; } //等效于 StringBuilder s1 = new StringBuilder(); for(int i=0; i<100; i++) { StringBuilder one = new StringBuilder(i); s1.append(one).toString(); } //高效写法 StringBuilder s2 = new StringBuilder(); for(int i=0; i<100; i++) { s2 = s2.append(i); }
同理,频繁使用的String也可以直接写出 String s3 = "a";,不需要new出来 String s4 = new String("a");,这样能让这个值存在于常量池里(可以当成存在缓存)。