一、继承体系
String是不可变类,所谓不可变类,就是创建该类的实例后,该实例的属性是不可改变的。同时String类被final修饰,不可被继承。
image.png
二、成员变量
// 存储字符串的字符数组,final关键字修饰了,这是String是不可变类的缘由所在
private final char value[];
// hash码
private int hash; // Default to 0
// 序列码
private static final long serialVersionUID = -6849794470754667710L;
三、常用方法
1、public boolean equals(Object anObject)方法
public boolean equals(Object anObject) {
//如果引用的是同一个对象,返回真
if (this == anObject) {
return true;
}
//如果不是String类型的数据,返回假
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
//如果char数组长度不相等,返回假
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;
}
equals方法经常用得到,它用来判断两个对象从实际意义上是否相等,String对象判断规则:
- 内存地址相同,则为真。
- 如果对象类型不是String类型,则为假。否则继续判断。
- 如果对象长度不相等,则为假。否则继续判断。
- 从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。
注:若被比较的入参对象为空,则会被报NEP,所以使用该方法需要让调用者和入参的String对象都不为null才可使用。
2、int compareTo(String anotherString)方法
public int compareTo(String anotherString) {
//自身对象字符串长度len1
int len1 = value.length;
//被比较对象字符串长度len2
int len2 = anotherString.value.length;
//取两个字符串长度的最小值lim
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
//从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符)
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
//如果前面都相等,则返回(自身长度-被比较对象长度)
return len1 - len2;
}
这个方法写的很巧妙,先从0开始判断字符大小。如果两个对象能比较字符的地方比较完了还相等,就直接返回自身长度减被比较对象长度,如果两个字符串长度相等,则返回的是0,巧妙地判断了三种情况。
ps:同样的入参和调用者String对象若有一个为空,则都会报NEP。
3、String concat(String str)方法
public String concat(String str) {
int otherLen = str.length();
//如果被添加的字符串为空,返回对象本身
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
// 由此可以看出String的字符串操作都会新生成一个对象返回
return new String(buf, true);
}
四、特殊说明
1、String是不可变类
2、String不可被继承
3、String维护了一个字符串常量池(字符串内部列表)
JAVA中所有的对象都存放在堆里面,包括String对象。字符串常量保存在JAVA的.class文件的常量池中,在编译期就确定好了。虚拟机为每个被装载的类型维护一个常量池。常量池就是该类型所用常量的一个有序集合,包括直接常量(string、integer和float point常量)和对其他类型、字段和方法的符号引用。
例如:
String s = new String( "myString" );
其中字符串常量是"myString",在编译时被存储在常量池的某个位置。在解析阶段,虚拟机发现字符串常量"myString",它会在一个内部字符串常量列表中查找,如果没有找到,那么会在堆里面创建一个包含字符序列[myString]的String对象s1,然后把这个字符序列和对应的String对象作为名值对( [myString], s1 )保存到内部字符串常量列表中。如下图所示:
image.png
如果虚拟机后面又发现了一个相同的字符串常量myString,它会在这个内部字符串常量列表内找到相同的字符序列,然后返回对应的String对象的引用。维护这个内部列表的关键是任何特定的字符序列在这个列表上只出现一次。
例如,String s2 = "myString",运行时s2会从内部字符串常量列表内得到s1的返回值,所以s2和s1都指向同一个String对象。但是String对象s在堆里的一个不同位置,所以和s1不相同。
JAVA中的字符串常量可以作为String对象使用,字符串常量的字符序列本身是存放在常量池中,在字符串内部列表中每个字符串常量的字符序列对应一个String对象,实际使用的就是这个对象。
字符串截留intern()方法
在某些上下文环境下,仅仅保留某个字符串的一份copy能够提高内存的使用和效率。String类的intern()方法可以截留字符串,如果String对象包含的字符序列不在字符串常量内部列表中,那么就把这个String对象包含的字符序列和String对象的引用作为名值对保存到内部列表中,最后intern()返回一个指向String对象本身的引用;如果String对象包含的字符序列在字符串常量内部列表中,那么就返回列表的名值对中的对应的字符串对象引用,而String对象本身的就会被丢弃。
例如,s.intern()就会返回和s2相同的引用,而以前的s对象就会被垃圾回收。
使用intern()要注意,被放到字符串内部列表中的字符串对象是不会被垃圾回收的,生命周期和整个程序相同,所以如果使用不当会造成内存泄露。
五、测试
1、代码
public class StringTest {
public static void main(String[] args) {
test1();
}
/**
* String的字符常量池及intern()方法
*/
private static void test1(){
String s1 = new String("ABC");
String s2 = new String("ABC");
System.out.println("s1 == s2 : " + (s1==s2));// false
String s3 = "ABC";
String s4 = "ABC";
String s5 = "AB" + "C";
System.out.println("s1 == s3 : " + (s1==s3));// false
System.out.println("after s1.intern(), s1 == s3 : " + (s1.intern()==s3));// true
System.out.println("s3 == s4 : " + (s3==s4));// true
System.out.println("s3 == s5 : " + (s3==s5));// true
String s6 = "ABC";
String s7 = "AB";
String s8 = s7 + "C";
System.out.println("s6 == s8 : " + (s6==s8));// false
}
}
2、打印结果
s1 == s2 : false
s1 == s3 : false
after s1.intern(), s1 == s3 : true
s3 == s4 : true
s3 == s5 : true
s6 == s8 : false
网友评论