
- JDK中String类的声明
- String类源码
- 创建字符串的四种方式
- 字符串的字面量
- 字符串比较相等
- 拓展equalsIgnoreCase方法
- 重点:
- 关于字符串的常量池问题
- 当使用直接赋值法产生字符串对象时
- 当使用new产生字符串对象时
- 手工入池
- 1.当前常量池中已经存在了该对象
- 2.当前常量池中不存在该对象
- 字符串的不可变性
- 如何修改字符串的内容
- 关于StringBuilder类的具体使用
- StringBuilder类和String类的相互转换
- StringBuilder类的其他方法
- 总结
为什么String类被final修饰?
创建字符串的四种方式因为别final修饰的类无法被继承,String类不存在子类,这样就保证所有使用JDK的人,大家用到的String类仅此一个,大家都相同!!!
1.直接赋值
String str = “hello world”;
2.通过构造方法产生对象
String str = new String(“hello world”);
3.通过字符数组产生对象
char[] data = new char[]{“a”, “b”, “c”};
String str = new String(data);
4.通过String的静态方法valueOf(任意数据类型),转为字符串
String str = String.valueOf(hello world);
直接写出来的数值称之为字面量
10 -> int字面量
10.1 -> double字面量
true -> boolean字面量
"abc" -> String字面量——就是一个字符串的对象
String str = “hello world”;是字符串字面量,也是字符串的对象。
下列代码,在内存中的变化
String str1 = "hello";
String str2 = str;
str2 = "world";
System.out.println(str2);
//输出结果:world
分析:
当执行第一行和第二行代码时,首先会在堆中产生一个“hello”对象,而且str1和str2指向同一个对象,但是执行第三行代码时,会在堆中再产生一个world对象,此时str2指向新的对象也就是world对象。
world也是字符串字面量,是一个新的字符串对象,str2实际上指向了新的对象world,str1仍然指向原对象world。
图示:
字符串比较相等所有引用数据类型在比较是否相等时,使用equals方法比较,JDK常用类,都已经腹泻了equals方法。
引用数据类型使用==比较的仍然是数值,也就是比较的是地址是否相等
代码实例:
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);
//输出结果:true
此时的str1和str2指向的是同一个对象(至于为什么是同一个对象,接下来我会在字符串常量池中介绍到),既然指向的是同一个对象,而且此时比较的是地址,那就是地址相同。所以返回true。
String str1 = new String ("hello");
String str2 = new String ("hello");
System.out.println(str1 == str2);
//输出结果:false
拓展equalsIgnoreCase方法此时的这段代码,是产生了两个不相同的对象,虽然其中的值相等,但是地址却完全不同。所以返回false。
使用equalsIgnoreCase();方法不区分大小写比较字符串
代码实例:
//使用equals方法比较字符串,区分大小写
String str1 = new String ("Hello");
String str2 = new String ("hello");
System.out.println(str1.equals(str2));
//输出结果:false
//使用equalsIgnoreCase方法比较字符串,不区分大小写
String str1 = new String ("Hello");
String str2 = new String ("hello");
System.out.println(str1.equalsIgnoreCase(str2));
//输出结果:true
问题:
在实际开发中,让用户从浏览器输入一个String变量userName,和你定义的String变量比较是否相等。
String userName = null;
System,out,println(userName.equals("彭于晏")); //方法一
System,out,println("彭于晏".equals(userName)); //方法二
问:上面方法一和方法二哪个更好?还是都可以无所谓?
重点: 关于字符串的常量池问题 当使用直接赋值法产生字符串对象时答案:使用方法二更好,原因是,当userName为空时,就会出现空引用,会报错空指针异常!!!
因为我们要比较的特定内容本身就是字符串的字面量,一定不是空对象,把要比较的内容放在equals前面就可以避免userName为空的问题。
利用==比较这几个引用的地址是否相同,相同则返回true,不同返回false。
下面看两段代码,来直观了解字符串常量池。
代码段1:
String str1 = "hello";
String str2 = "hello";
String str3 = "hello";
System.out.println(str1 == str2);
System.out.println(str2 == str3);
//输出结果:
//true
//true
此时这三个引用指向了相同的内存。
代码段2:
String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("hello");
System.out.println(str1 == str2);
System.out.println(str2 == str3);
//输出结果:
//false
//false
因为要new一个对象的时候就要在堆中开辟一块新空间,所以这三个引用指向三个不同的字符串对象,也就指向了三块不同的内存空间。
结论
当使用直接赋值法产生一个字符串对象时候,JVM会维护一个字符串常量池,若该对象在堆中还不存在,则产生一个新的字符串对象加入字符串的常量池中。
当继续使用直接赋值法产生字符串对象时,JVM发现该引用指向的内容在常量池中已经存在了,则此时不再新建字符串对象,而是复用已有对象。
图解内存空间变化
当使用new产生字符串对象时同样是, 若是在堆中不存在,第一次产生的字符串对象,就会加入到常量池中。但是同时也会在堆中产生对象。
下面是使用三个引用产生三个字符串对象。
String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("hello");
那么这三个字符串在内存中是如何产生的呢?让我们用图示来直观的看下内存变化。
图解内存空间变化
手工入池所谓的“池”,都是类似的思想,
共享设计模式 - 节省空间(因为内存是第一个非常珍贵的资源),字符串产生之后大部分情况都是要用来进行输出的,打印打的内容,所以为何不整一个放入池中就行了~~~
类似的数据库的连接池,线程池都是类似的思想~~~
使用String提供的intern();方法
1.当前常量池中已经存在了该对象调用intern方法会将当前字符串引用指向的对象保存到字符串常量池初中。
a.若当前常量池中已经存在了该对象,则不再产生新的对象,返回常量池中的String对象。
代码实例1:
String str1 = new String("hello");
str1.intern();
String str2 = "hello";
System.out.println(str1 == str2);
//输出结果:false
图解分析:
因为str1使用new去产生一个对象,此时str1会先在堆中开辟一块空间,产生一个对象,此时hello这个字符串对象是第一次产生,所以会将hello这个对象放入到常量池中。
然后执行intern方法,会返回常量池中的String对象,但是此时没有变量去接收这个返回的String对象。随后str2通过赋值法产生字符串对象,但是此时在常量池中已经存在hello对象,所以str2直接指向常量池中的对象。
代码示例2:
String str1 = new String("hello");
str1 = str1.intern();
String str2 = "hello";
System.out.println(str1 == str2);
//输出结果:true
图解分析:
2.当前常量池中不存在该对象此时当执行到第二行代码时,会先将str1产生的对象放入常量池中,因为inter方法会返回常量池中的对应的对象,所以此时str1引用会指向常量池中的对象。
b.若当前常量池中不存在该对象,则将该对象入池,返回入池后的地址。
代码示例:
char[] data = new char[]{'a','b','c'};
String str1 = new String(data);
str1.intern();
String str2 = "abc";
System.out.println(str1 == str2);
//输出结果:true
图解分析:
此时现在堆中产生一个字符数组data的对象,第二行代码中String接收的不是字符串对象,而是字符数组,此时还没有对象!!!所以只会在堆中产生一个普通的对象,而不会在常量池中加入此对象!!
字符串的不可变性这时再执行str1的intern方法,但是此时在常量池中并没有str1的对象,所以会将str1的对象入池,也就是移动到常量池中,所以此时的str1指向的是常量池中的对象。
str2通过赋值法产生字符串对象,但是此时已经在常量池中有了abc这个字符串对象,所以str2指向常量池中的对象~~~
所谓的字符串不可变值得是内容不能变,而不是引用不能变。
这里的不可变指的是字符串对象hello,world,!!!这三个对象不能变!!!一旦声明后无法修改其内容!!!
String str = "hello";
str += "world";
str += "!!!"
System.out.ptintln(str);
图解分析:
因为都是通过赋值法产生字符串对象,所以第一次产生的对象都会放入常量池中,后边不再赘述。
首先会在常量池中产生一个
hello对象,str指向该对象。然后会在常量池中产生一个
world对象,随后还会产生一个helloworld对象,str此时指向该对象。同理,还会再产生一个
!!!对象,和helloworld!!!对象,此时str指向该对象。但是在内存中,这些队产生后就不可修改!!!
问题:为何字符串的对象无法修改内容,但是其他类型的对象能修改内容?
如何修改字符串的内容字符串其实就是一个字符数组
char[],字符串的保存实际上在数组中保存。而且是一个被private修饰的字符数组,所以外部根不能无法拿到这个变量,更何况修改这个变量?
a.在运行时通过反射破坏value数组的封装(不推荐,所以在这里不展开介绍,后边再讲到反射时候会展开介绍~~~)
b.更换使用StringBuilder类或者StringBuffer类,此时已经不是String类型了
这两个都是类!!!调用类中的方法。
应用场景:
若需要频繁进行字符串的拼接,使用StringBuilder类的append();方法。(只会产生一个对象,对内容进行修改)
StringBuffer使用方法和StringBuilder用法一样,只是有线程安全,但是性能较差。
代码示例:
StringBuilder str = new StringBuilder();
str.append("hello");
str.append("world");
str.append("!!!");
System.out.println(str);
//输出结果:helloworld!!!
关于StringBuilder类的具体使用
StringBuilder类和String类是两个独立的类, StringBuilder就是为了解决字符串拼接问题产生的。
因为String类的对象无法修改内容,为了方便字符串的拼接 *** 作,产生了StringBuilder类,StringBuilder类的对象是可以修改内容的。
StringBuilder类和String类的相互转换1.StringBuilder类变成String类
调用toString方法
String str1 = str.toString();
2.String类变成StringBuilder类
使用StringBuilder的构造方法,
StringBuilder sb = new StringBuilder(“hello”);
使用append方法
sb.append("123");
StringBuilder类的其他方法
a.字符串反转 *** 作reverse()方法
不需要传入变量
代码示例:
StringBuilder str = new StringBuilder();
str.append("hello");
str.append("world");
str.append("!!!");
str.reverse();
System.out.println(str);
//输出结果:!!!dlrowolleh
b.删除指定范围的数据delete()方法
delete(int left,int right),传入所以范围,左闭右开区间
StringBuilder str = new StringBuilder();
str.append("hello");
str.append("world");
str.append("!!!");
str.delete(5,10);
System.out.println(str);
//输出结果:hello!!!
c.插入 *** 作insert()方法
insert(int index , 任意类型数据),从索引为index的位置插入任意数据类型的数据
代码示例:
StringBuilder str = new StringBuilder();
str.append("hello");
str.append("world");
str.append("!!!");
str.insert(3,"3");
System.out.println(str);
//输出结果:hel3loworld!!!
面试题
总结请解释String,StringBuilder,StringBuffer的区别:
1.String的对象无法修改,俩sb的对象内容可以修改。
2.StringBuffer是线程安全的 *** 作,性能较差;StringBuilder是线程不安全,性能较高。
1.关于字符串常量池以及intern方法的理解问题
2.理解啥是字符串不可变性
3.StringBuilder的使用
4.要使用String类,就采用直接赋值的方式,要比较引用类型是否相等就使用equals方法。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)