参考文章:
内容简介
String类的相关介绍在之前那篇里简单说了,这篇文章主要是介绍一下String类实现接口时重写的方法和String类不给我们用的那些方法到底隐藏着什么样的秘密
重写方法
Serializable接口
这个接口不用重写方法
Comparable接口
Comparable接口下只有compareTo()一个方法需要重写,作用是在这个接口进行排序时会通过compareTo()方法得知当前字符串是不是比传入的字符串大,什么是大请看方法解析.
compareTo()方法
public int compareTo(String anotherString) {
byte v1[] = value;
byte v2[] = anotherString.value;
if (coder() == anotherString.coder()) {
return isLatin1() ? StringLatin1.compareTo(v1, v2)
: StringUTF16.compareTo(v1, v2);
}
return isLatin1() ? StringLatin1.compareToUTF16(v1, v2)
: StringUTF16.compareToLatin1(v1, v2);
}
这个方法的作用是比较传入字符串和当前字符串哪个更大,那何为更大呢,都知道,char类型其实可以当做一个数字,而字符串其实本质上就是一个字符数组,
所以这个方法将将两者的字符数组取出,从第一个字符开始比较,如果相同则比较下一个字符,如果不同则将两个字符进行比较并输出当前字符串是不是比传入字符串的这个字符大(是返回正数).
如果较短的字符串和较长的字符串前面那段一模一样则比较当前字符串是不是比传入字符串长(是返回正数).如果完全一样的话返回0.
有的字符串进行了压缩,有的没有,所以根据isLatin1判断是否进行了压缩,从而使用对应的排序方法进行排序,但原理都是这个原理.
CharSequence接口
这个接口包含 length() , charAt(int index) , subSequence(int start, int end) 三个抽象方法.
String在实现这个接口的时候还重写了两个default方法(就是接口里面直接实现了的)chars(),codePoints().
String还重写了CharSequence接口中的toString()抽象方法,我们姑且将toString()方法算作是重写的这个接口里的而不是Object类中的.
public int length()
public int length() {
return value.length >> coder();
}
这个方法返回String对象储存字符的数组长度.
">>“这个东西表示将二进制数字右移,位数就是”>>"右边那个数字,右移一位相当于将数字除以二.
public char charAt(int index)
public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}
这个方法返回String对象储存字符的数组第index个字符.
依然是根据压缩情况进行对应的输出
public CharSequence subSequence(int start, int end)
public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}
虽然这个方法写着返回值是字符序列,但实际上String类中的实现返回的是String类型的数据,因为String实现了这个接口,所以说String类型是CharSequence类型的子类也行,自然也能返回String类型.
这个方法返回当前字符串从beginIndex开始到endIndex这个段字符组成的字符串.
别看这个方法没有抛异常,substring方法里有,所以请不要传错误数据.
public IntStream chars()
public IntStream chars() {
return StreamSupport.intStream(
isLatin1() ? new StringLatin1.CharsSpliterator(value, Spliterator.IMMUTABLE)
: new StringUTF16.CharsSpliterator(value, Spliterator.IMMUTABLE),
false);
}
返回对当前字符串的char值进行零扩展的int流
public IntStream codePoints()
public IntStream codePoints() {
return StreamSupport.intStream(
isLatin1() ? new StringLatin1.CharsSpliterator(value, Spliterator.IMMUTABLE)
: new StringUTF16.CodePointsSpliterator(value, Spliterator.IMMUTABLE),
false);
}
返回当前字符串的字符代码点值的int流
public String toString()
public String toString() {
return this;
}
这应该是是Java中最简单的实现了,toString()方法正如他的名字返回的就是String类的对象.
Object类方法
作为所有类的共同父类,String类自然也继承Object类,同时,String类也重写了几个相关方法.
String类重写了equals(Object),hashCode()两个方法,toString()方法算CharSequence接口的
public boolean equals(Object anObject)
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
这个方法很好理解,首先如果这俩比较的对象是同一个对象那他俩的内容肯定一致.
第二个if判断的是传入的Object类的子类是不是String类型的,instanceof是java的一个保留关键字,和"==",">","<"一样是个二元操作符,它的作用是测试它左边的对象是否是它右边的类的实例,返回是不是.
如果传进来的都不是String类型的自然返回false,如果是进行强转在判断编码格式是否一致,不一样的话内容自然也不一样,返回false.
如果上面几个都满足了再根据对应的压缩方式调用对应方法进行判断.
public int hashCode()
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}
这个方法是返回这个对象的哈希码的,在Java源码通俗理解之Object类中有介绍这个方法,想看的可以点击自行了解.
那个判断是为了不用反复计算,算过一次之后String类中的成员变量hash就会变成这个数,因为这个对象一旦创建就无法修改,所以这个类的实例一生只需计算一次哈希码就好.
私有方法
private static void checkBounds(byte[] bytes, int offset, int length)
private static Void rangeCheck(char[] value, int offset, int count) {
checkBoundsOffCount(offset, count, value.length);
return null;
}
这个方法就是上一篇中构造方法中反复出现的方法
他只有一个用处,那就是:
抛出异常
只要传入的参数有一点点不对,那么异常在等着你!!!
这个方法其实是判断bytes数组中是否有第offset个字符,从offset个字符开始是否后面还有大于等于length个字符.主要用途是判断用来生成字符串的字符数组和相关参数时候合法.
private boolean nonSyncContentEquals(AbstractStringBuilder sb)
private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
int len = length();
if (len != sb.length()) {
return false;
}
byte v1[] = value;
byte v2[] = sb.getValue();
if (coder() == sb.getCoder()) {
int n = v1.length;
for (int i = 0; i < n; i++) {
if (v1[i] != v2[i]) {
return false;
}
}
} else {
if (!isLatin1()) { // utf16 str and latin1 abs can never be "equal"
return false;
}
return StringUTF16.contentEquals(v1, v2, len);
}
return true;
}
这个方法会比较当前字符串和传入的AbstractStringBuilder储存内容进行比较,完全一样才返回true.
AbstractStringBuilder是StringBuilder和StringBuffer的父类,所以传啥都行.
这个方法据某位大佬所说是JDK为了速度有意的破坏封装弄出来的.,我把那段贴出来大家可以看看 :
JDK连注释都没写估计也是因为没有遵守规定而心虚吧,反正我也不知道,我也不敢问.权当扩展知识好了.
private int indexOfSupplementary(int ch, int fromIndex)
private int indexOfSupplementary(int ch, int fromIndex) {
if (Character.isValidCodePoint(ch)) {
final char[] value = this.value;
final char hi = Character.highSurrogate(ch);
final char lo = Character.lowSurrogate(ch);
final int max = value.length - 1;
for (int i = fromIndex; i < max; i++) {
if (value[i] == hi && value[i + 1] == lo) {
return i;
}
}
}
return -1;
}
这是JDK8里面的函数,现在已经没了,但这个东西的拓展也算一个知识,所以记录一下
简单解释一下中间参数:
hi是高代理,是将ch右移10位,加上D800这个字符减去65536右移16位之后所得数的差,然后强转为char类型(Character.highSurrogate(int codePoint))
lo是低代理,将ch与1023进行按位运算,就是将两个数转换成二进制,每一位进行比较,如果两个数的对应数位上有一个是0,那么结果对应数位就是0,最后得到的结果转换成十进制,然后再加上DC00这个字符,得到的结果进行强转为char类型(Character.lowSurrogate(int codePoint))
个人感觉,因为有些有些字符需要两个编号共同组成,但一个编号占用一个char的位置,这个方法就是判断ch这个字符在字符串储存字符的数组第formIndex字符之后有没有出现
网上也找到了一个相关解释:
private int indexOfNonWhitespace()
private int indexOfNonWhitespace() {
if (isLatin1()) {
return StringLatin1.indexOfNonWhitespace(value);
} else {
return StringUTF16.indexOfNonWhitespace(value);
}
}
判断字符串是否为空或者空格的isBlank()方法里用到了这个方法
这个方法里面调用的那两个方法是判断传入的字符数组第一个出现的空格或’\t’或空白符
下面那个代码段是StringLatin1里面的方法细节,StringUTF16里面基本一致.
public static int indexOfNonWhitespace(byte[] value) {
int length = value.length;
int left = 0;
while (left < length) {
char ch = (char)(value[left] & 0xff);
if (ch != ' ' && ch != '\t' && !Character.isWhitespace(ch)) {
break;
}
left++;
}
return left;
}
内部类
private static class CaseInsensitiveComparator implements Comparator, java.io.Serializable
这是个静态内部类,个人感觉这个类就是String类自己内部使用的比较器,因为String类支持序列化,所以这个类也实现了Serializable接口.Comparator接口是比较器接口,重写compare方法
private static final long serialVersionUID
private static final long serialVersionUID = 8575799808933029326L;
这个变量作用和String类里那个同名变量作用一模一样,都是与序列化相关的.详情请看上一篇文章
public int compare(String s1, String s2)
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
这是个挺有意思的方法,作用是不区分大小写的进行比较字符串大小.
之所以说这个方法有意思,是因为很多人看这个方法都很迷糊,不区分大小写干嘛要用三个if来比较呢.通过上面某方法我们可以知道JDK为了性能什么都做得出来,所以排除了为了保险规范的事,那又是为什么呢.
其实这跟我们的惯性思维有关,字符串里面存的可不一定是什么语言,中文没有大小写自不用说,英文都切换成大写或小写比一遍也OK,那如果是某些小众语言呢,比如格鲁吉亚字母,格鲁吉亚字母表转换大写是无效的,只能通过再次比较小写来判断.java作为一门世界通用编程语言,自然要考虑到所有我们考虑不到的东西了.
当然,这个是老版本里面的实现(新版本的底层逻辑和这个没什么区别),新版本外面套了个判断什么逻辑都看不见,原因自然还是压缩的问题,JDK11中的代码我会放在这两行字的下面,参考一下就好
public int compare(String s1, String s2) {
byte v1[] = s1.value;
byte v2[] = s2.value;
if (s1.coder() == s2.coder()) {
return s1.isLatin1() ? StringLatin1.compareToCI(v1, v2)
: StringUTF16.compareToCI(v1, v2);
}
return s1.isLatin1() ? StringLatin1.compareToCI_UTF16(v1, v2)
: StringUTF16.compareToCI_Latin1(v1, v2);
}
private Object readResolve()
private Object readResolve() {
return CASE_INSENSITIVE_ORDER;
}
这个方法也是跟序列化有关的,当反序列化进行时如果发现了这个方法,就会用这个方法返回的对象替代原对象提供给调用者.和序列化有关的就不多说了.
CASE_INSENSITIVE_ORDER这个对象就是下面那个东西
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
这个成员变量不是这个类里面的,他其实是String类的成员变量,只不过这个东西就是为了这个类而诞生的,所以干脆放一块好了.
new的那个CaseInsensitiveComparator对象就是这个类的实例.
总结
只是看了这么点就发现了自身的知识储备还是太少太少,很多东西只能强行理解,连自己都不是很赞同自己的理解,只是暂时找不到更好地理解只能这么理解了.String类作为一个和字符打交道的类,和编码格式,序列化,流等概念的交集会很多很多,如果在理解这几个之前先看的String类的话建议看完这两个后在回过头来看看这些内容,可能会有新的理解.
作为一个小白,也不知道新版本里出现的压缩这个东西到底是真好还是假好,虽然好像是省了内存,但逻辑上多出了好多步,现实情况只能说仁者见仁了.