第九章、String 类(不可变)
一、概述
1、在Java中,
java.lang.String类代表字符串。它是不可变(immutable)的,即一旦创建,就不能修改其内容。2、在程序中出现双引号包括的字符串,不一定就会在方法区常量池中创建一个String对象,要看它是否进行拼接操作,查看源码编译后的class字节码文件的结果。
1、String表示字符串类型,属于引用数据类型,不属于基本数据类型
2、在java中随意使用双引号括起来的都是String对象。例如:“abc”,“def”,都是String对象
3、java中规定,双引号括起来的字符串,是不可变的,也就是说“abc”自出生到死亡,都是不可变的。
1、字符串必须使用双引号
在 Java 中,字符串的表示必须使用双引号 " " 来包围,而不支持使用单引号 ' ' 来表示字符串。这是 Java 语言的规定,单引号在 Java 中用于表示字符(char)类型,而不是字符串(String)类型。
以下是一个示例,说明在 Java 中字符串必须使用双引号表示:
public class StringExample {
public static void main(String[] args) {
String myString = "This is a string.";
System.out.println(myString);
}
}
在上面的示例中,myString 是一个字符串变量,它被双引号包围。
如果你想在 Java 字符串中包含双引号,可以使用转义字符 \" 来表示:
String stringWithQuotes = "He said, \"Hello!\"";
System.out.println(stringWithQuotes);
但是请注意,Java 不支持使用单引号来表示字符串。单引号在 Java 中用于表示字符常量,例如 'a' 表示字符 'a'。
所以,在 Java 中,字符串必须使用双引号来表示,而单引号用于字符常量。
2、字符串不可变解释
//字符串不可变是说“”双引号里面的字符串对象一旦创建不可变
//而不是会所引用s中保存的内存地址不可变,除非s是被final修饰的
String s = "abc"; //“abc”放到了方法区字符串常量池中,“abc”不可变
//s是可以指向其他字符串对象的
s = "xyz"; //“xyz”放到了方法区字符串常量池中,“xyz”不可变
在 Java 中,字符串(String)是不可变的数据类型,这意味着一旦创建了字符串对象,就不能更改它的内容。当你对字符串进行操作(例如连接、截取、替换等),实际上是创建了一个新的字符串对象,而不是在原始的字符串对象上进行修改。
这种字符串不可变性的特性具有一些重要的影响和优势:
-
保证数据的安全性: 不可变性确保了字符串内容在创建后不能被意外修改,从而避免了在程序运行时发生意外的数据更改。
-
优化内存使用: 由于字符串不可变,Java 可以在适当的情况下共享字符串的内存,从而减少内存的使用,提高性能。
-
字符串池: 由于字符串是不可变的,Java 使用了字符串池(String Pool)来共享相同值的字符串对象,从而节省内存和提高性能。
-
多线程安全: 字符串的不可变性使得多线程操作字符串时不会出现竞争条件,从而减少了线程安全问题。
下面是一些示例来说明字符串的不可变性:
String originalString = "Hello";
String modifiedString = originalString + ", World!"; // 创建新的字符串对象
System.out.println(originalString); // 输出仍然是 "Hello"
System.out.println(modifiedString); // 输出 "Hello, World!"
在上面的示例中,我们对 originalString 进行了连接操作,得到了一个新的字符串 modifiedString。原始的字符串 originalString 的内容没有被修改,仍然保持不变。
总之,Java 中的字符串不可变性确保了字符串对象一旦创建就不会被修改,这种特性在编程中有很多优势,包括数据的安全性、内存优化、字符串池和线程安全。
3、字符串对象内存优化:字符串常量池
在java中双引号括起来的字符串,例如:“abc”都是直接存储在JVM的方法区的字符串常量池中。因为字符串在实际的开发中使用太频繁,为了执行效率,所以吧字符串放到了方法区的常量池中。如果在创建相同值的字符串对象时可以共享

4、字符串对象是可索引,可迭代的
在 Java 中,字符串是可索引和可迭代的。这意味着你可以通过索引访问字符串中的单个字符,并且你可以使用循环来迭代字符串中的每个字符。
在 Java 中,字符串的索引从 0 开始,即第一个字符的索引是 0,第二个字符的索引是 1,依此类推。你可以使用 charAt() 方法来访问特定索引位置的字符。
String str = "Hello, World!";
char firstChar = str.charAt(0); // 获取第一个字符 'H'
char secondChar = str.charAt(1); // 获取第二个字符 'e'
此外,你还可以使用增强型的 for-each 循环来迭代字符串中的每个字符:
String str = "Hello";
for (char c : str.toCharArray()) {
System.out.print(c + " "); // 输出每个字符,依次为 'H', 'e', 'l', 'l', 'o'
}
System.out.println();
for (int i = 0; i < str.length(); i++) {
System.out.print(str.charAt(i) + " "); // 输出每个字符,依次为 'H', 'e', 'l', 'l', 'o'
}
二、创建字符串对象
1、使用字面值创建 String 引用名 = “字面值”;
public class StringTest01 {
public static void main(String[] args) {
//这两行代码表示底层创建了2个字符串对象,都在字符串常量池中。
String s1 = "abcdef"; //一个字符串对象 "abcdef"
String s2 = "abcdef" + "xy"; //另一个字符串对象 "abcdefxy"
}
}
2、使用构造方法创建 String 引用名 = new String(“字面值”);
package com.javase.String;
public class StringTest01 {
public static void main(String[] args) {
//使用构造方法创建String对象
//凡是双引号括起来的都是在字符串常量池中有一份。
//new 对象的时候一定在堆内存当中开辟内存空间
String s3 = new String("xy");
//i变量中保存的是100这个值。
int i = 100;
//s变量中保存的是字符串对象的内存地址
//s引用中保存的不是“abc“,而是 0x1111
//而0x1111是“abc”字符串对象在“字符串常量池”当中的内存地址、
String s = "abc";
}
}
3、两种方式内存图分析

package com.javase.String;
public class StringTest02 {
public static void main(String[] args) {
//“hello”是存储在方法区的字符串常量池中
//所以这个“hello”不会新建,因为这个对象已经存在了
String s1 = "hello";
String s2 = "hello";
//== 双引号比较的变量中保存的值(该处为内存地址)
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true
String x = new String("xyz");
String y = new String("xyz");
//== 双引号比较的变量中保存的值(该处为内存地址)
System.out.println(x == y); //false
//通过这个案例的学习,我们知道了,字符串对象之间的比较不能使用 ==
//应使用 equals 方法
//String类已经重写了equals方法,以下的equals方法调用的是String重写之后的equals方法
System.out.println(x.equals(y)); //true
String k = new String("testString");
//"testString"这个字符串可以后面加“.”尼?
//因为“testString”是一个String对象,只要是对象都可以调用方法
//使用这种方式,因为这个可以避免空指针异常。
System.out.println("testString".equals(k)); //true
}
}

4、String类型的引用中同样也是保存String类型的内存地址
类中成员变量(实例或静态变量)为String类型,则也是保存的是字符串对象的内存地址,而不是字符串对象的值,也不是字符串对象本身
User.java
package com.javase.String;
public class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
UserTest.java
package com.javase.String;
public class UserTest {
public static void main(String[] args) {
User user = new User(110,"张三");
}
}

5、面试题分析
package com.javase.String;
public class StringTest03 {
public static void main(String[] args) {
/**
* 一共创建了3个对象
* 方法区字符串常量池中有1个:“hello”
* 堆内存中有两个String对象
* 一共3个
*/
String s1 = new String("hello");
String s2 = new String("hello");
}
}
三、String类的构造方法 (数组元素拼接成字符串)
1、public String(byte[] bytes)
String类的构造方法public String(byte[] bytes)允许使用指定的字节数组创建一个新的字符串对象。
该构造方法的作用是将字节数组中的内容解码为字符串,使用默认的字符集来进行解码。在解码过程中,每个字节都被解释为字符的 ASCII 值。
源码实现:
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
其实就是调用了该public String(byte[] bytes, int offset, int length)构造方法
参数说明:
bytes:要用于构造字符串的字节数组。
构造方法的行为如下:
- 字节数组中的每个字节将被解释为相应的字符,使用默认字符集进行解码。
- 解码后的字符将按照顺序组合成一个新的字符串对象。
以下是一个示例使用该构造方法的代码:
byte[] bytes = {97, 98, 99}; // 字节数组 [97, 98, 99] 对应 ASCII 字符编码中的 'a', 'b', 'c'
String str = new String(bytes);
System.out.println(str); // 输出: "abc"
在上述示例中,bytes 字节数组被解码为字符串对象 str,其值为 "abc"。
需要注意的是,使用该构造方法时,默认使用的字符集是平台默认字符集。如果字节数组是使用不同的字符集编码的,可以使用其他构造方法 public String(byte[] bytes, Charset charset) 来指定特定的字符集进行解码。
2、public String(byte[] bytes, int offset, int length)
String类的构造方法public String(byte[] bytes, int offset, int length)允许使用指定的字节数组的子数组创建一个新的字符串对象。
该构造方法的作用是将字节数组中指定范围的内容解码为字符串,使用默认的字符集来进行解码。在解码过程中,每个字节都被解释为字符的 ASCII 值。
参数说明:
bytes:要用于构造字符串的字节数组。offset:子数组的起始偏移量,表示从字节数组的哪个位置开始解码。length:子数组的长度,表示要解码的字节个数。
构造方法的行为如下:
- 从字节数组中的指定偏移量开始,(包括该偏移值对象的byte类型元素)取连续的
length个字节。 - 解码这些字节为相应的字符,使用默认字符集进行解码。
- 解码后的字符按照顺序组合成一个新的字符串对象。
以下是一个示例使用该构造方法的代码:
byte[] bytes = {97, 98, 99, 100, 101}; // 字节数组 [97, 98, 99, 100, 101] 对应 ASCII 字符编码中的 'a', 'b', 'c', 'd', 'e'
String str = new String(bytes, 1, 3);
System.out.println(str); // 输出: "bcd"
在上述示例中,bytes 字节数组的子数组从索引 1 开始,长度为 3,即取字节 [98, 99, 100]。这些字节被解码为字符串对象 str,其值为 "bcd"。
需要注意的是,使用该构造方法时,默认使用的字符集是平台默认字符集。如果字节数组是使用不同的字符集编码的,可以使用其他构造方法 public String(byte[] bytes, int offset, int length, Charset charset) 来指定特定的字符集进行解码。
3、public String(char[] value)
String类的构造方法public String(char[] value)允许使用指定的字符数组创建一个新的字符串对象。
该构造方法的作用是将字符数组中的内容组合成一个字符串。
参数说明:
value:要用于构造字符串的字符数组。
构造方法的行为如下:
- 将字符数组中的每个字符按照顺序组合成一个新的字符串对象。
以下是一个示例使用该构造方法的代码:
char[] chars = {'a', 'b', 'c'};
String str = new String(chars);
System.out.println(str); // 输出: "abc"
在上述示例中,字符数组 chars 中的字符被组合为字符串对象 str,其值为 "abc"。
需要注意的是,字符数组中的每个字符都会被按照顺序连接起来,形成一个字符串。如果字符数组中包含空字符 ('\0') 或者在构造方法之后还有其他字符,它们将会包含在字符串中。例如,char[] chars = {'a', 'b', 'c', '\0', 'd'} 构造出的字符串将包含 "abc\0d"。
此构造方法还有其他变体,可以通过提供字符数组的起始偏移量和长度来创建子串。例如,public String(char[] value, int offset, int count) 构造方法允许从字符数组的指定位置开始,取连续的指定长度的字符,将其组合成一个新的字符串。
4、public String(char[] value, int offset, int count)
String类的构造方法public String(char[] value, int offset, int count)允许使用指定的字符数组的子数组创建一个新的字符串对象。
该构造方法的作用是将字符数组中指定范围的内容组合成一个字符串。
参数说明:
value:要用于构造字符串的字符数组。offset:子数组的起始偏移量,表示从字符数组的哪个位置开始构造字符串。count:子数组的长度,表示要构造的字符个数。
构造方法的行为如下:
- 从字符数组中的指定偏移量开始,取连续的
count个字符。 - 将这些字符按照顺序组合成一个新的字符串对象。
以下是一个示例使用该构造方法的代码:
char[] chars = {'a', 'b', 'c', 'd', 'e'};
String str = new String(chars, 1, 3);
System.out.println(str); // 输出: "bcd"
在上述示例中,字符数组 chars 的子数组从索引 1 开始,长度为 3,即取字符 ['b', 'c', 'd']。这些字符被组合为字符串对象 str,其值为 "bcd"。
需要注意的是,如果提供的偏移量 offset 超出了字符数组的有效范围,或者指定的长度 count 超过了字符数组的剩余长度,将会引发 IndexOutOfBoundsException 异常。
此构造方法还有其他变体,可以通过提供字符数组的起始偏移量和长度来创建子串。例如,public String(char[] value, int offset, int count) 构造方法允许从字符数组的指定位置开始,取连续的指定长度的字符,将其组合成一个新的字符串。
四、String类常用方法
1、int length():返回字符串的长度,即字符串中字符的个数
判断数组长度和判断字符串长度不一样:
判断数组长度是length属性,判断字符串长度是length() 方法
String str = "Hello";
int length = str.length(); // 返回 5
2、char charAt(int index):返回指定索引位置处的字符(相当于字符串取索引)
String str = "Hello";
char ch = str.charAt(1); // 返回 'e'
Python 中的字符串允许取索引和切片,而 Java 中的字符串不允许取索引和切片,这是因为两种编程语言对字符串的设计和实现方式不同。以下是解释这种差异的一些原因:
-
不可变性和安全性:
- Python 的字符串是不可变的,意味着一旦创建后不能修改。因此,可以安全地允许访问和切片字符串中的字符,因为操作不会影响原始字符串。
- Java 的字符串也是不可变的,但 Java 语言设计者可能认为允许直接访问和修改字符串中的字符可能会导致不必要的风险,因此没有提供索引和切片的功能。
-
语言设计理念:
- Python 的设计理念之一是“简洁性胜过复杂性”,鼓励直接访问和操作数据,以方便编程。因此,Python 允许对字符串进行索引和切片操作。
- Java 的设计理念之一是“显式胜过隐式”,强调代码的安全性和稳定性。Java 可能更倾向于限制对不可变对象的直接访问,以避免潜在的问题。
-
数据类型的不同:
- 在 Python 中,字符串是一种内置的数据类型,由解释器直接支持。这使得 Python 可以提供方便的字符串操作。
- 在 Java 中,字符串是一个类(String 类),由 Java 标准库提供支持。因此,Java 可能更加注重在类的设计和用法上进行控制。
需要注意的是,虽然 Java 中的字符串不允许直接取索引和切片,但 Java 提供了一些方法来进行类似的操作,如 charAt() 方法用于获取指定位置的字符,以及 substring() 方法用于获取子字符串。
总之,Python 和 Java 之所以在字符串索引和切片方面存在差异,是因为它们的设计和实现理念不同,以及对编程体验和数据安全性的不同权衡。
2.1、java.lang.StringIndexOutOfBoundsException
字符串索引越界异常,当传入的索引大于字符串的最大索引时会引发该异常
3、String substring(int beginIndex) 和 String substring(int beginIndex, int endIndex):字符串切片
截取子字符串,索引左闭右开(开始索引能取到,结束索引取不到)
返回从指定索引开始到字符串末尾(或指定索引前)的子字符串。
String str = "Hello World";
String sub1 = str.substring(6); // 返回 "World"
String sub2 = str.substring(0, 5); // 返回 "Hello"
3.1、java.lang.StringIndexOutOfBoundsException
字符串索引越界异常,当传入的索引大于字符串的最大索引时会引发该异常
4、String concat(String str):将指定的字符串连接到原始字符串的末尾
String s1 = "hello";
String s2 = " world";
String newStr = s1.concat(s2);
System.out.println(newStr); //hello world
5、String toUpperCase() 和String toLowerCase():将字符串转换为大写或小写
String str = "Hello";
String upperCase = str.toUpperCase(); // 返回 "HELLO"
String lowerCase = str.toLowerCase(); // 返回 "hello"
6、String trim():去除字符串两端的空格
String str = " Hello ";
S

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



