常用的方法
- equals()
- length()
- append()
- endsWith()
- startsWith()
- charAt()
- isEmpty()
- split()
- substring(pos,N)
- indexOf(str)
- toLowerCase()
- toUpperCase()
创建字符串对象的不同方式
String s = new String("bbb");
创建了2或1个实例对象。
第一个是在类【解析】的时候,生成一个字符串字面量对象放到堆中,然后字符串池中存放该对象的引用。当然,如果字符串池中已经有这个字面量对象的引用的话,第一个字面量对象就不用创建了,相当于什么也没发生。 第二个是在类【初始化】的时候用new来动态创建的,生成一个对象放到堆中(与第一个不同),然后将这个对象的引用存到栈中,并返回给s。所以,s指向的是new出来的那个对象。
String s = "aaa";
创建一个实例对象,也就是在类【解析】的时候,生成一个字符串字面量对象放到堆中,然后字符串常量池池中存放该对象的引用,并返回给s。
不可改变、不可继承
String类是final类,也即意味着String类不能被继承,不可变,并且它的成员方法都默认为final方法。
String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象。比如sub、concat、replace操作、重新赋值操作、字符串编辑操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串字面量对象,都是在这个新对象副本上操作的。
比如:
String a = "aaa";
String b = a;
b = "bbb";
System.out.println(a); // aaa
System.out.println(b); // bbb
第二句没有问题,效果就是引用a和b同时指向字符串对象”aaa“,但是第三句生成了新的字符串对象”bbb“,被引用b指向;原来的”aaa“对象仍然存在,被引用a指向。
另外,想通过下标修改字符串中的某一位也是不可行的,会在编译器报错,因为字符串不支持下标操作。
尝试“修改”String的原理?
首先创建一个 StringBuffer,然后调用 StringBuffer 的 append() 方法,最后调用 StringBuffer 的 toString() 方法返回一个新的字面量String对象。
final 修饰 StringBuffer 后还可以 append 吗?
可以。final 修饰的 StringBuffer 只是一个引用变量,堆中这个对象内部的属性是可以变化的。
其实这个题和String无关,和final关键字有关。
为什么String是对象类型的?
因为String底层是数组实现的(一个final的char数组),而数组是对象的一种。
为什么要设计成不可变
-
便于实现字符串池,可复用。
-
线程安全:不可变的对象天生线程安全
真的不可变吗?
可以用反射来改变String中value的值,所以严格意义上说不一定不可变
“+” 操作
每遇到一个 “+” ,就会生成一个StringBuilder对象,通过append方法完成与后面内容的拼接,然后再自动调用一个toString()方法转化为String 字面量对象 放到堆,最后把堆中地址存到字符串常量池中,并返回给等号左边的引用变量一份。
String a = "hello";
String s1 =a + new String("world"); // 返回的是字面量对象,也就是第一个生成的那个对象
String s2 = s1.intern();
System.out.println(s2 == s1); //true
String s3 = "helloworld";
System.out.println(s3==s1); // true
另外,对于执行时机,也有区别
-
如果 “+” 两边都是纯字面量,包括以下两种情况,会在编译期直接拼接,只创建合并后的那个对象。
String str = "hello"+"world" String str = new String("hello") + new String("world"); -
如果 “+” 两边至少存在一个引用或者方法的调用,那这个 “+” 就只能等到执行期才能拼接。
特殊情况:用final修饰的字符串变量,等同于纯字面量。例如下面的
a + "world",其中的a不会被看作是引用。
final a = "hello"; String str = a + "world" // 等同于 "hello"+"world"
小例题
String怎样翻转
public static String reverseStringBuffer(String s){
StringBuffer sb = new StringBuffer(s);
String afterReverse = sb.reverse().toString();
return afterReverse;
}
以下的第三行代码创建出几个对象(第一和第二行不算入)
String var1 = "abc";
String var2 = new String("abc");
String str = var1 + var2;
答:两个对象,一个是String对象,另一个是StringBuilder对象,首先创建一个StringBuilder对象,调用append方法把var1和var2拼接起来,最后调用toString()方法返回一个String对象
以下三种创建String对象的方式有什么区别吗?
String str1 = new String();
String str2 = new String(""); // 空串在Java虚拟机启动的时候默认生成,因此不生成字面量对象
String str3 = ""; // 空串在Java虚拟机启动的时候默认生成,因此生成了0个对象
答:str1和str2都是在堆内存里创建了一个通过new产生的对象,但都没有生成字符串字面量对象,前者是因为null,而后者是利用了虚拟机启动时默认生成的空串。str3相比于前两种,什么都没有创建。
以下代码中,str2和str3哪种创建方式更好?
String str1 = "abc";
String str2 = new String("abc");
String str3 = new String(str1);
答:s3好。因为在编译的时候,如果直接用字符串常量的方式构造,编译器会去遍历常量区里的所有字符串,直到找到"abc"字符串,再去构造str2。而str3在构造的时候,由于str1指向的是一个引用地址,相当于直接告诉str3你要的字符串常量就在我保存的这个位置,这样就省去了遍历常量区的过程,速度会更快一点。
以下代码打印出来的结果是什么
char[] chars = {'a','b','c'};
String str = new String(chars);
chars[0] = 'x';
System.out.println(str);
答:是abc,使用带char数组参数的构造方法,底层是把char数组的内容复制一份给了字符串,所以chars数组和str底层的value数组不是同一个数组,即使改变了chars数组的内容,str还是不变。
为什么在循环的时候,String用+号进行字符串拼接效率低? 答:因为使用+进行拼接的话,编译器每一次的拼接都会创建一个StringBuilder对象将每个+号后面的字符串通过append方法拼接起来,但是每循环一次都会产生一个StringBuilder对象,当循环的次数很大的时候,效率自然就降低了
String中的 hashCode() 函数,计算方法:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
s[i] 的值是字符的ASCII值;n是字符串长度