1. 类说明
1.1. 首先是final修饰
类不能被继承,所以String无子类
1.2. 类实现了Serializable,Comparable,CharSequence接口
- Serializable:用于序列化
- Comparable:用于比较
- CharSequence:用于操作String的value对象,获取长度,指定字符等
1.3. 内部主要成员
- value:用于存储字符串内容
- hash:用于存储hash值,默认是0,在0的情况下且value长度大于0,执行hashCode()方法时,会计算hash值
1.4. 构造函数
- String():空实例化,使用的是空字符串进行实例化
- String(String original):待参实例化,此时,字符串对象不一致,但是值是一致的
1.5. 主要方法
- intern():如果当前字符串不在常量池中,则将当前的字符串移动至常量池并返回;如果常量池中存在则直接返回常量池中的字符串(所以,intern的返回值和原始值,有可能是同一个对象,有可能不是同一个对象)
- charAt(int index):获取String内部value下标为index的char
- toUpperCase toLowerCase: 转大小写
- equalsIgnoreCase compareTo: 字符串比较
- getBytes:转字节数组
- split substring:字符串分割截取
2. 案例介绍
2.1. 字符串实例化
public class TestString {
public static void main(String[] args) throws Exception {
String a = "11";
String a1 = new String("11");
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] value = (char[])valueField.get(a);
value[0] = '2';
System.out.println("待参构造,String对象不一致,但是值一致");
System.out.println("a == a1:" + (a == a1));
System.out.println("由于通过反射将a变量第一个字符改成了2,所以a的值从11变成了21");
System.out.println("a = " + a);
System.out.println("a1 = " + a1);
}
}
运行结果:
待参构造,String对象不一致,但是值一致
a == a1:false
由于通过反射将a变量第一个字符改成了2,所以a的值从11变成了21
a = 21
a1 = 21
所以,String的对象不一样,但是值是同一个对象
2.2. intern方法
public class TestString {
public static void main(String[] args) throws Exception {
// 初始化常量会直接进入常量池
String a = "11";
String a1 = new String("11");
// intern会返回常量池中的字符串
String intern = a1.intern();
System.out.println("a == a1: " + (a == a1));
System.out.println("a == intern: " + (a == intern));
System.out.println("a1和intern不是同一个对象: " + (a1 == intern));
}
}
运行结果:
a == a1: false
a == intern: true
a1和intern不是同一个对象: false
public class TestString {
public static void main(String[] args) throws Exception {
String a = "1";
String b = "2";
String a1 = a + b;
String intern = a1.intern();
System.out.println("a1和intern是同一个对象: " + (a1 == intern));
}
}
a1和intern是同一个对象: true
由于常量池中没有"12"所以将a1移动到常量池
2.3. 加法操作
public class TestString {
public static void main(String[] args) throws Exception {
String a = "1";
String b = "2";
String a1 = a + b;
String a2 = "1" + "2";
String a3 = "12";
System.out.println("变量相加生成新对象: " + (a1 == a2));
System.out.println("常量相加不生成新对象: " + (a2 == a3));
}
}
变量相加生成新对象: false
常量相加不生成新对象: true
3. 常见问答
结合上面的描述,再来看看面试的时候会问什么问题呢
3.1. 解释String为什么不可变?String a = "1"; a = "2";
答:因为用final修饰,所以String对象创建后不能再进行修改了;因此当前场景下其实生成了两个字符串对象,而不是把原本的字符串对象改成了"2",而是a指向了新的实例
注:非要抬杠的话,可以说用反射可以做到去修改字符串,然后没有生成新的String但是值变了(大家高兴就好)
3.2. 下面代码会生成几个字符串对象?
String a = "1";String b = "1";
答:1个
String a = "1";String b = a + "";
答:2个
String a = "1";String b = a + "";String c = b.intern();
答:2个
注:这个题目随便都能出,关键要理解
如果自己不确定,可以用idea等工具去看
3.3. 问两个String对象通过方法交换值(不使用反射)
答:做不到,用反射可以
大家需要注意这个地方,可能下意识会说用中间变量中转,那就尴尬了
看一下反例
public class StringSwapFail {
public static void main(String[] args) {
String a = "1";
String b = "2";
swap(a, b);
System.out.println("外部:a=" + a + ";b=" + b);
}
private static void swap(String a, String b) {
String swapVal = a;
a = b;
b = swapVal;
System.out.println("内部:a=" + a + ";b=" + b);
}
}
执行结果:
内部:a=2;b=1
外部:a=1;b=2
原因呢,如图
可以的反射例子
public class StringSwap {
public static void main(String[] args) {
String a = "a";
String b = "b";
swap(a, b);
System.out.println("a=" + a + ";b=" + b);
}
private static void swap(String a, String b) {
try {
// 获取字段
Field fieldValue = String.class.getDeclaredField("value");
Field fieldHash = String.class.getDeclaredField("hash");
// 设置字段可访问
fieldValue.setAccessible(true);
fieldHash.setAccessible(true);
// 交换值
Object swapVal = fieldValue.get(a);
fieldValue.set(a, fieldValue.get(b));
fieldValue.set(b, swapVal);
// 交换hash值
swapVal = fieldHash.get(a);
fieldHash.set(a, fieldHash.get(b));
fieldHash.set(b, swapVal);
// 也可以设置0,String内部对于0会去生成hash值
// fieldHash.setInt(a, 0);
// fieldHash.setInt(b, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
-- 有缘登山,寒山不寒