前言
Sting类是JAVA中十分重要的一种引用数据类型,本章将深入String类内部,了解其基本用法以及常见操作,认识字符串常量池以及StringBuffer 和 StringBuilder。
一、JDK中String类的声明
==为何Sring类被final修饰?==
被final修饰的类无法被继承,String类不存在子类。
这样的话就可以保证所有使用JDK的人,大家用的String类都仅此一次,大家都相同。
继承的方法覆写在带来灵活性的同时,也会带来很多子类行为不一致导致的问题。
二、创建字符串
常见创建字符串的四种方式:
- 方式一: 直接赋值(常用)
String str = "Hello World";
- 方式二:通过构造方法产生对象
String str2 = new String(“Hello World”);
- 方式三:通过字符数组产生对象
char[] data = new char[]{'a', 'b', 'c'};
String str = new string(data);
- 方式四:通过String的静态方法valueOf(任意数据类型) => 转化为字符串(常用)
String str = String.valueOf(10);
三、字符串比较相等
所有引用数据类型比较是否相等时,使用equals方法比较,JDK常用类,都已经覆写了equals方法,直接使用即可。(如String 、 Integer)
引用数据类型使用 “==” 比较的仍然是数值(地址是否相等)
equals 使用注意事项:
现在需要比较 str 和 "Hello" 两个字符串是否相等, 我们该如何来写呢?
String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));
在上面的代码中, 哪种方式更好呢?
我们更推荐使用 "方式二". 一旦 str 是 null, 方式一的代码会抛出异常, 而方式二不会.
String str = null;
// 方式一
System.out.println(str.equals("Hello")); // 执行结果 抛出 java.lang.NullPointerException 异
常
// 方式二
System.out.println("Hello".equals(str)); // 执行结果 false
注意事项: "Hello" 这样的字面值常量, 本质上也是一个 String 对象, 完全可以使用 equals 等 String 对象的方法。
四、字符串常量池
在上面的例子中, String类的两种实例化操作, 直接赋值和 new 一个新的 String.
a) 直接赋值
String str1 = "hello" ;
String str2 = "hello" ;
String str3 = "hello" ;
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // true
这三个引用指向了相同的内存。
为什么现在并没有开辟新的堆内存空间呢?
String类的设计使用了共享设计模式。
在JVM底层实际上会自动维护一个对象池(字符串常量池)
- 如果现在采用了==直接赋值==的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将==自动保存==到这个对象池之中.
- 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用。
- 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。
理解 "池" (pool)
"池" 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 "内存池", "线程池", "数据库连接池" ....
然而池这样的概念不是计算机独有, 也是来自于生活中. 举个栗子:
现实生活中有一种女神, 称为 "绿茶", 在和高富帅谈着对象的同时, 还可能和别的屌丝搞暧昧. 这时候这个屌丝被称为 "备胎". 那么为啥要有备胎? 因为一旦和高富帅分手了, 就可以立刻找备胎接盘, 这样 效率比较高.
如果这个女神, 同时在和很多个屌丝搞暧昧, 那么这些备胎就称为 备胎池.
b) 采用构造方法
类对象使用构造方法实例化是标准做法。分析如下程序:
String str = new String("hello");
这样的做法有两个缺点:
- 如果使用String构造方法就会开辟两块堆内存空间,若常量池不存在该对象,则入池,否则将销毁。
- 字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间.
我们可以使用 String 的 intern 方法(见下注解)来手动把 String 对象加入到字符串常量池中:
==手工入池:String类提供的intern方法==
调用intern()方法会将当前字符串引用的对象保存到字符串常量池中。
==a. 若当前常量池中已经存在了该对象,则不再产生新的对象,返回常量池中的String对象。==
==b.若当前常量池中不存在该对象,则将对象入池,返回入池后的地址。==
面试题:请解释String类中两种对象实例化的区别
- 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
- 构造方法:会开辟两块堆内存空间,一个会自动保存在对象池中,也可以使用intern()方法手工入池。
综上, 我们一般采取直接赋值的方式创建 String 对象.
五、字符串的不可变性
字符串是一种不可变对象. 它的内容不可改变.
String 类的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组。
因此字符串对象的内容无法改变 = 》 String类的外部无法获取这个value数组。
String str = "hello" ;
str = str + " world" ;
str += "!!!" ;
System.out.println(str);
// 执行结果
hello world!!!
六、如何修改字符串内容
那么如果实在需要修改字符串, 例如, 现有字符串 str = "Hello" , 想改成 str = "hello" , 该怎么办?
==a) 常见办法: 借助原字符串, 创建新的字符串==
String str = "Hello";
str = "h" + str.substring(1);
System.out.println(str);
// 执行结果
hello
==b) 特殊办法: 使用 "反射" 这样的操作可以破坏封装, 访问一个类内部的 private 成员.==
String str = "Hello";
// 获取 String 类中的 value 字段. 这个 value 和 String 源码中的 value 是匹配的.
Field valueField = String.class.getDeclaredField("value");
// 将这个字段的访问属性设为 true
valueField.setAccessible(true);
// 把 str 中的 value 属性获取到.
char[] value = (char[]) valueField.get(str);
// 修改 value 的值
value[0] = 'h';
System.out.println(str);
// 执行结果
hello
关于反射
反射是面向对象编程的一种重要特性, 有些编程语言也称为 "自省".
指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 "认清自己" .
为什么 String 要不可变?(不可变对象的好处是什么?)
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中。
注意事项: 如下代码不应该在你的开发中出, 会产生大量的临时对象, 效率比较低
String str = "hello" ;
for(int x = 0; x < 1000; x++) {
str += x ;
}
System.out.println(str);
==c) 特殊办法: 更换使用StringBuilder类或者StringBuffer类- 已经和String类不是一个数据类型了.==
由于String的不可更改特性,为了方便字符串的修改,JDK提供StringBuffer和StringBuilder类:
面试题:请解释String、StringBuffer、StringBuilder的区别:
- String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
- StringBuffer与StringBuilder大部分功能是相似的
- StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作。
a. 字符串反转操作,sb提供的reverse();
b.删除指定范围的数据,删除索引从start开始到end之前的内容,[start,end)
c. 插入操作,将新元素插入到sb对象中,插入后新数值的起始索引为offset
总结
指的注意的点:
- 了解字符串常量池, 体会 "池" 的思想.
- 理解字符串不可变
- StringBuffer 和 StringBuilder 的功能
关于字符串常用的一些操作方法,后面博主会更新的~感谢大家支持O(∩_∩)O ❤❤❤