String类
String类的理解
- String 对象用于保存字符串,也就是一组支付串序列
注:如果一个类可以串行化,说明该对象可以在网络传输。
- 字符串常量对象是用双引号括起来的字符串序列,例如"你好"、“12.97”、"boy"等
- 字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节
- String类有很多构造器(其它看Java图标)。构造器的重载
eg:
String s1 = new String();
String s2 = new String(String original);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startindex,int count)
- String 是final类,不能被其它的类继承
- String 类中有属性 private final char value[]; 用于存放字符串内容,一定要注意:value是一个final类型的,不可修改。(不可修改的是里面的地址:即value不能指向新的地址,但是单个字符内容是可以变化的)
String类的创建对象
方式一:直接赋值 String s = “hello”;
先从常量池查看是否有"hello"数据空间,如果有,直接指向;如果没有则重新创建,然后指向。s最终指向的是常量池的空间地址
方式二:调用构造器 String s2 = new String(“hello”);
先在堆中创建空间,里面维护了value属性,指向常量池的helli空间。如果常量池没有"hello",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。
练习1
String a = "joker";//a 指向常量池的 "joker"
String b = new String("joker");//b 指向堆中的对象
System.out.println(a.equals(b));//输出true
System.out.println(a==b);//输出false
System.out.println(a==b.intern);//输出true
System.out.println(b==b.intern);//输出false
当调用intern方法时,如果池已经包含一个等于次String对象的字符串(用equals(Object)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。
b.intern()方法最终返回的是常量池的地址
练习2 !!!
Person p1 = new Person();
p1.name = "joker";
Person p2 = new Person();
p2.name = "joker";
System.out.println(p1.name.equals(p2.name));//输出true
System.out.println(p1.name == p2.name);//输出true,此处p1和p2都在堆中创建了一个对象,对象地址不一样,但是对象中的name都指向常量池中的"joker"的地址,总的来说都指向同一个池中的同一个地址,因此输出true。
System.out.println(p1.name == "joker");//输出 true,因为"joker"是字符串常量是放在常量池中的,会返回一个常量池中的地址
String s1 = new String("bcde");
String s2 = new String("bcde");
System.out.println(s1 == s2);//输出false
字符串的特性
- String 是一个final类,代表不可变的字符序列
- 字符串是不可变的。一个字符串对象一旦被分配,其内容是不可变的
eg:练习1
String s1 = "joker";
s1 = "hello";
//创建了两个对象,首先s1在常量池中没有找到"joker",创建了"joker",之后s1被赋值"hello"后只会再次重新创建一个"hello",然后把s1指向"hello"的地址。
eg:练习2
String a = "hello" + "abc";
//问:创建了几个对象
//系统会优化这句代码,优化后等价 String a = "helloabc",所以只有一个对象
eg:练习3
String a = "hello";
String b = "abc";
String c = a + b;
//问:创建了几个对象(关键就是要分析String c = a + b; 到底如何执行的)
1.在底层先创建一个StringBuilder sb = StringBuilder()
2.执行 sb.append("hello");
3.执行 sb.append("abc");
4.调用 sb.toString(),把两个字符串连接输出返回,String c = sb.toString;
5.最后其实是 c 指向堆中的对象(String类型) value[]-> 常量池中的"helloabc";
6.a指向的是常量池中的"hello" b指向的是常量池中的"abc" c先指向堆中的对象,对象里面的value指向常量池中的"helloabc"。c 并不是直接指向常量池中的"helloabc",而是通过底层new,toString输出的,最终c是指向堆的。
解:3个对象
重要规则
String c1 = “ab” + “cd”;(常量相加,看的是常量池)
String c1 = a + b;(变量相加,是在堆中)
String c = a + b;
String d = "helloabc";
System.out.println(c == d);//真还是假? 是false
String e = "hello" + "abc";//直接看池, e指向常量池
System.out.println(d == e);//真还是假? 是true
eg:练习4
class Test1 {
String str = new String("joker");
final char[] ch = {'j', 'a', 'v', 'a'};
public void change(String str, char ch[]) {
str = "java";//会在常量池中新开一个,这时str是调用change方法时是栈里面开辟的新方法空间里面的str,不是堆里面的str,这时堆里面的str是指向joker,而方法栈里面str是指向java,两个不一样
ch[0] = 'h';//数组存储放在堆中
}
public static void main(String[] args) {
Test1 ex = new Test1();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");//这时是调用主栈里面的str因此是joker
System.out.println(ex.ch);
}
}
输出的是:jokerandjava
分析
调用方法时ex.change(ex.str, ex.ch)传入的str会在方法栈里面,然后在常量池中新开一个"java"指向,而不是ex对象里面的str,对象里面的str还是指向"joker",因此调用主栈里面的ex.str是原来"joker",并不是"java"