《JAVA SE》认识String类

2022-12-02 16:14:30 浏览数 (1)

前言

Sting类是JAVA中十分重要的一种引用数据类型,本章将深入String类内部,了解其基本用法以及常见操作,认识字符串常量池以及StringBuffer 和 StringBuilder。


一、JDK中String类的声明

为何Sring类被final修饰?

被final修饰的类无法被继承,String类不存在子类。

这样的话就可以保证所有使用JDK的人,大家用的String类都仅此一次,大家都相同。

继承的方法覆写在带来灵活性的同时,也会带来很多子类行为不一致导致的问题。

二、创建字符串

常见创建字符串的四种方式:

  1. 方式一: 直接赋值(常用) String str = “Hello World”;
  2. 方式二:通过构造方法产生对象 String str2 = new String(“Hello World”);
  3. 方式三:通过字符数组产生对象 char[] data = new char[]{‘a’, ‘b’, ‘c’}; String str = new string(data);
  4. 方式四:通过String的静态方法valueOf(任意数据类型) => 转化为字符串(常用) String str = String.valueOf(10);

三、字符串比较相等

所有引用数据类型比较是否相等时,使用equals方法比较,JDK常用类,都已经覆写了equals方法,直接使用即可。(如String 、 Integer)

引用数据类型使用 “==” 比较的仍然是数值(地址是否相等)

equals 使用注意事项:

现在需要比较 str 和 “Hello” 两个字符串是否相等, 我们该如何来写呢?

代码语言:javascript复制
String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));

在上面的代码中, 哪种方式更好呢? 我们更推荐使用 “方式二”. 一旦 str 是 null, 方式一的代码会抛出异常, 而方式二不会.

代码语言:javascript复制
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) 直接赋值

代码语言:javascript复制
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底层实际上会自动维护一个对象池(字符串常量池)

  1. 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.
  2. 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用。
  3. 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。

理解 “池” (pool) “池” 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 “内存池”, “线程池”, “数据库连接池” … 然而池这样的概念不是计算机独有, 也是来自于生活中. 举个栗子: 现实生活中有一种女神, 称为 “绿茶”, 在和高富帅谈着对象的同时, 还可能和别的屌丝搞暧昧. 这时候这个屌丝被称为 “备胎”. 那么为啥要有备胎? 因为一旦和高富帅分手了, 就可以立刻找备胎接盘, 这样 效率比较高. 如果这个女神, 同时在和很多个屌丝搞暧昧, 那么这些备胎就称为 备胎池.

b) 采用构造方法 类对象使用构造方法实例化是标准做法。分析如下程序:

代码语言:javascript复制
String str = new String("hello");

这样的做法有两个缺点:

  1. 如果使用String构造方法就会开辟两块堆内存空间,若常量池不存在该对象,则入池,否则将销毁。
  2. 字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间.

我们可以使用 String 的 intern 方法(见下注解)来手动把 String 对象加入到字符串常量池中:

手工入池:String类提供的intern方法

调用intern()方法会将当前字符串引用的对象保存到字符串常量池中。

a. 若当前常量池中已经存在了该对象,则不再产生新的对象,返回常量池中的String对象

b.若当前常量池中不存在该对象,则将对象入池,返回入池后的地址

面试题:请解释String类中两种对象实例化的区别

  1. 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
  2. 构造方法:会开辟两块堆内存空间,一个会自动保存在对象池中,也可以使用intern()方法手工入池。

综上, 我们一般采取直接赋值的方式创建 String 对象.

五、字符串的不可变性

字符串是一种不可变对象. 它的内容不可改变.

String 类的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组。

因此字符串对象的内容无法改变 = 》 String类的外部无法获取这个value数组。

代码语言:javascript复制
String str = "hello" ; 
str = str   " world" ; 
str  = "!!!" ; 
System.out.println(str); 
// 执行结果
hello world!!!

六、如何修改字符串内容

那么如果实在需要修改字符串, 例如, 现有字符串 str = “Hello” , 想改成 str = “hello” , 该怎么办?

a) 常见办法: 借助原字符串, 创建新的字符串

代码语言:javascript复制
String str = "Hello";
str = "h"   str.substring(1);
System.out.println(str);
// 执行结果
hello

b) 特殊办法: 使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员.

代码语言:javascript复制
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 要不可变?(不可变对象的好处是什么?)

  1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
  2. 不可变对象是线程安全的.
  3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中。

注意事项: 如下代码不应该在你的开发中出, 会产生大量的临时对象, 效率比较低

代码语言:javascript复制
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的区别:

  1. String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
  2. StringBuffer与StringBuilder大部分功能是相似的
  3. StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作。

a. 字符串反转操作,sb提供的reverse();

b.删除指定范围的数据,删除索引从start开始到end之前的内容,[start,end)

c. 插入操作,将新元素插入到sb对象中,插入后新数值的起始索引为offset

总结

指的注意的点:

  1. 了解字符串常量池, 体会 “池” 的思想.
  2. 理解字符串不可变
  3. StringBuffer 和 StringBuilder 的功能

关于字符串常用的一些操作方法,后面博主会更新的~感谢大家支持O(∩_∩)O ❤❤❤

0 人点赞