原码
首先有个String类,看看原码(看源码的方式,我用eclipse为例。鼠标放到String上,点击Alt键,然后变成一个手的样子,再点一下,就到了String.class里面可以看原码了)
特点
- String类是一个最终类。
- 属性有一个private final 的char型数组,这个数组是存我们写的字符串的值
String 是引用类型
String s1 = "123";//给一个字符串赋值
s1 = "abc" ; //修改字符串中的值
System.out.println(s1); //输出看看,字符串的值改变了。
前面图片说了字符串类里面不能修改字符串的值,但是为什么这里可以修改字符串的值。
一开始我很不理解,因为字符串是引用类型,应该来说例子中的s1保存的是"123"这个值的地址,不应该被改变,但是为什么这里可以给对象s1修改值呢,原因是当你修改地址中的值(例如上面的"abc")的时候,那么也即是在内存中再开辟一个空间保存"abc",那么对象s1中保存的地址也发生了变化,变成了"abc"这个值的地址。
上结论
String类型创建对象后,里面的value数组值不能改变,但是String字符串的指向是可以改变的
过程就是我上面的,不知道是我学懵了,还是砸了。
//这个代码看起来就比较清晰,和上面的简化版是一样的,也就是解释这个过程
String s1 = new String("123");
s1 = new String("abc") ;
System.out.println(s1);
字符串,如果直接赋值,相当于在常量池中创建一个对象,下次再赋值时,如果是同一个值,直接引用,是同一个地址
直接赋值:代码如下
String s1 = "123";//给一个字符串赋值
常量池,前面我有写过final修饰的类,方法,属性呀,修饰属性那么这个属性就是常量,定义时就要赋初值,不能再赋值,即为常量。常量池那么也就是很多很多的常量放在一起构成一个池,池的作用就是不用创建对象,减少对内存的占用。
如果此时代码,变成这样
String s1 = "123";
String s2 = "123" ;
//String是一个引用类型,应该来说,值相等,不一定地址相等,但是因为是直接赋值,那么就是常量池里创建对象,值相同,就是同一个对象。
System.out.println(s1 == s2);
结果为:
通过new赋值,相当于创建的一个对象,多次使用,就创建了多个对象
也就是说 ,new创建对象,就不是在常量池里创建对象,地址也就不同。
String s1 = new String("123");
String s2 = new String("123") ;
System.out.println(s1 == s2); //比较地址
System.out.println(s1.equals(s2)); //比较值
再来看看直接赋值的
String s3 = "12";
String s4 = "12" ;
System.out.println(s3 == s4);
System.out.println(s3.equals(s4));
说明字符串对象通过new创建的,如果他们的值相等,使用 ==判断和equals得到的结果是不一样的。
字符串直接赋值的,= =和equals结果是一样的。这里面的原理,我一会还说不清楚。所以为了避免出错,字符串比较值是否相等,就用equals()进行判断
字符串难以理解的一个点
字符串作为一个引用类型,也就是字符串对象保存的应该是一个地址,但是下面这个例子却打破了这个常识。
public class Test {
public static void main(String []args) {
String s1 = "123456789";
String s2 = "123456789";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
func(s1); //这行报错
System.out.println(s1 );
}
public void func( String s) {
s = "1" ;
}
}
看看结果
这个就要说到保存string的char型数组是private final修饰的,private修饰说明不能在外部修改,final表示常量,也是不能修改的意思。这个说明在字符串赋值过后就不能对字符串进行修改,但是可以改变字符串的地址从而改变字符串的值。还是验证了下面这句话。
String类型创建对象后,里面的value数组值不能改变,但是String字符串的指向是可以改变的
字符串和数组还是很不一样
是否在常量池中创建,就看 =右边是不是只是常量,不能改变。
String s1 = "1"; //常量池中创建
String s2 = s1 + "2" ; //只要右边是常量那么就不会改变,s2会自动变成"12"
String s3 = s1 + "2" ; //由于s1这个值可能发生改变,也就是保存的地址可能改变,所以s3也就不会进入内存池里,
看看下面的
final String s4 = "1" ; //说明s4已经是常量了,final修饰嘛,常量池中创建
String s5 = s4 + "2" ; //右边是常量,所以s5在常量池中识别为"12"
再看看下面的
先看看String类里面有个字符串拼接的方法concat。上源码
public String concat(String str) {//传进来的参数str是拼接上的字符串,举例说明的话,就是String s2 = s1 + "2" ;str就相当于“2”
int otherLen = str.length();
if (otherLen == 0) {
return this; //如果拼接的字符串长度为0,空串,那么直接返回当前对象。
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true); //长度不会0,返回一个新的对象,有new嘛
}
String s6 = "1".concat("2"); //这个是new一个对象的,因为拼接“2”呀,有长度,不在常量池里
String s6 = "12".concat(""); //这个就直接返回了,因为拼接空串嘛,还是在常量池里
charAt(index)
**作用就是传进去一个下标,返回查询字符串中下标的某个字符**
public char charAt(int index) {//index是下标
if ((index < 0) || (index >= value.length)) {//越界就抛出异常
throw new StringIndexOutOfBoundsException(index);
}
return value[index]; //没有越界,就返回下标
}
String s7 = "123456789";
char c = s7.charAt(8);
System.out.println(c );
说明下标8的值为9
contains(“string”)
boolean res = s7.contains("123"); //判断某个字符串包含某个字符串,返回一个boolean值。这个就是s7这个串是否包含"123"这个串。
System.out.println(res );
trim()
去掉字符串两边的空格,但是不能去掉字符串中间任何位子的空格
String s7 = " 123456 789 ";
String s8 = s7.trim();
System.out.println(s8 );
toCharArray()
变成字符型数组,这样就有下标了
String s7 = " 123456 789 ";
char [] c1 = s7.toCharArray();
for(int i = 0 ; i < c1.length ; i++ ) {
System.out.println(c1[i]);
}
replace(“旧串” , “新串”)
将字符串中某个串换成新串
String s7 = " 123456 789 ";
String str1 = s7.replace("123", "abc"); //将“123”替换“abc”
System.out.println(str1);
substring(startIndex , endIndex)
切割字符串,可以没有endIndex结尾下标,startIndex开始下标一定要有。
String s7 = "123456789";
String res2 = s7.substring(5);
System.out.println(res2);
String res3 = s7.substring(3 , 7);
System.out.println(res3);
spilt(“串”)
按照串分割,得到字符串数组。切了那么那一部分就没有了
String s7 = "123456789";
String [] res4 = s7.split("45" );
for(int i = 0 ; i < res4.length ; i++) {
System.out.println(res4[i]);
}