Java 零基础教程

Java 字符串

字符串(Strings)用于表示和处理文本数据。在 Java 中,必须要牢记一点:字符串是对象(Objects),而不是基本数据类型(Primitive data types)

本章将全面探索如何在 Java 中处理字符串,涵盖它们的创建方式、常用操作以及一些非常关键的底层特征。

1. 创建字符串 (Creating Strings)

在 Java 中,字符串是 String 类的对象。我们有几种不同的方式来创建字符串对象:

1.1 字符串字面量 (String Literals)

最常见、最推荐的创建字符串的方式是使用“字符串字面量”,也就是将字符序列直接用双引号 "" 括起来。

String message = "Hello, World!"; // 创建一个字符串字面量
System.out.println(message);

底层揭秘:字符串常量池 (String Constant Pool)
当你使用字面量创建字符串时,Java 会去检查内存中的一个特殊区域——“字符串常量池”。如果池中已经存在内容完全相同的字符串,Java 会直接重用那个已经存在的对象,而不会占用新的内存。如果池中没有,它才会在池中创建一个新的字符串对象。

String str1 = "Java";
String str2 = "Java"; // str2 会直接指向 str1 所指向的同一个对象

// == 比较的是内存地址(引用)。因为它们指向常量池中的同一个对象,所以结果为 true
System.out.println(str1 == str2); // 输出: true

1.2 使用 new 关键字

你也可以像创建普通对象一样,使用 new 关键字来创建字符串。注意:这种方式每次都会在内存(堆)中强制创建一个全新的字符串对象,即使字符串常量池中已经有了内容相同的字符串。

String str3 = new String("Java");
String str4 = new String("Java"); // str4 将是一个全新的对象,与 str3 毫无关系

// 因为它们在内存中是两个截然不同的对象,所以地址不同,结果为 false
System.out.println(str3 == str4); // 输出: false

1.3 从字符数组创建 (Character Arrays)

你可以将一个字符数组 (char[]) 组装成一个字符串:

char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String messageFromArray = new String(charArray);
System.out.println(messageFromArray); // 输出: Hello

1.4 从字节数组创建 (Byte Arrays)

在处理文件或网络传输时,你经常需要将字节数组 (byte[]) 转换回字符串。在此过程中,你可以指定字符编码(Encoding):

byte[] byteArray = {65, 66, 67}; // A, B, C 对应的 ASCII 码值
String messageFromBytes = new String(byteArray); // 使用系统默认的字符编码
System.out.println(messageFromBytes); // 输出: ABC

// 最佳实践:显式指定 UTF-8 编码,防止乱码
String messageFromBytesUTF8 = new String(byteArray, java.nio.charset.StandardCharsets.UTF_8); 
System.out.println(messageFromBytesUTF8); // 输出: ABC

2. 字符串的不可变性 (String Immutability)

Java 中的字符串是不可变的 (Immutable)。这意味着一旦一个字符串对象被创建,它的内容就永远无法被改变。任何看起来像是在修改字符串的操作,实际上都在底层创建了一个全新的字符串对象

String text = "Hello";
text = text + " World"; // 这里创建了一个全新的字符串对象 "Hello World"
System.out.println(text); // 输出: Hello World

在这个例子中,最初的 "Hello" 字符串本身并没有被改变。+ 拼接操作在内存中生成了一个新的 "Hello World" 字符串,然后将 text 这个变量重新指向了这个新对象。如果那个旧的 "Hello" 没有任何变量再去引用它,它最终会被 Java 的垃圾回收器 (Garbage Collector) 清理掉。

2.1 不可变性的深远影响

  • 线程安全 (Thread Safety): 因为内容不可变,多个线程可以同时安全地读取同一个字符串,完全不用担心数据被意外篡改。
  • 节省内存 (String Pool Efficiency): 正是因为不可变,Java 才能放心地引入“字符串常量池”机制,让多个变量共享同一个字符串对象,极大地节省了内存。
  • 行为可预测 (Predictability): 不可变性使得字符串的行为非常稳定,你不需要担心传递给某个方法的字符串会被该方法暗中修改。

3. 常用字符串操作 (String Operations)

String 类内置了大量极其有用的方法,用于处理和操作文本。以下是日常开发中最常用的几个:

3.1 获取长度 (Length)

length() 方法返回字符串中字符的个数。

String myString = "Java is fun";
int length = myString.length();
System.out.println("字符串长度: " + length); // 输出: 字符串长度: 11 (注意:空格也算一个字符)

3.2 拼接字符串 (Concatenation)

你可以使用 + 运算符或者 concat() 方法将多个字符串连接在一起。

String firstName = "John";
String lastName = "Doe";

String fullName = firstName + " " + lastName; // 使用 + 运算符 (最常用)
System.out.println(fullName); // 输出: John Doe

String fullNameConcat = firstName.concat(" ").concat(lastName); // 使用 concat() 方法
System.out.println(fullNameConcat); // 输出: John Doe

3.3 截取子串 (Substrings)

substring() 方法用于从原字符串中提取一部分。它有两种常用形式:

  • substring(int beginIndex):从 beginIndex(包含该索引)一直截取到字符串末尾。
  • substring(int beginIndex, int endIndex):beginIndex(包含)截取到 endIndex不包含)。注意:Java 的索引是从 0 开始的。
String message = "Hello, World!";

String sub1 = message.substring(7); // 从索引 7 截取到末尾
System.out.println(sub1); // 输出: World!

String sub2 = message.substring(0, 5); // 从索引 0 截取到索引 4 (不包含 5)
System.out.println(sub2); // 输出: Hello

3.4 大小写转换 (Case Conversion)

toLowerCase() 转换为全小写,toUpperCase() 转换为全大写。

String text = "Java Programming";
System.out.println("转小写: " + text.toLowerCase()); // 输出: 转小写: java programming
System.out.println("转大写: " + text.toUpperCase()); // 输出: 转大写: JAVA PROGRAMMING

3.5 去除首尾空白 (Trimming Whitespace)

trim() 方法会自动清理掉字符串开头和结尾的空格、换行符等不可见字符(中间的空格不会动)。这在处理用户输入时非常有用。

String stringWithWhitespace = "   Hello, World!   ";
String trimmedString = stringWithWhitespace.trim();
System.out.println("清理空格后: [" + trimmedString + "]"); // 输出: 清理空格后: [Hello, World!]

3.6 替换字符或子串 (Replacing)

replace() 方法可以将字符串中指定的字符或文本片段替换成新的内容。

String message = "Hello, World!";
String replacedMessage = message.replace("World", "Java");
System.out.println(replacedMessage); // 输出: Hello, Java!

String replacedChar = message.replace('!', '?');
System.out.println(replacedChar); // 输出: Hello, World?

3.7 分割字符串 (Splitting)

split() 方法可以根据指定的分隔符(Delimiter),把一个长字符串切分成一个字符串数组 (String[])。

String csvData = "John,Doe,30,New York";
String[] values = csvData.split(","); // 按照逗号切分

for (String value : values) {
    System.out.println(value);
}
/* 输出:
John
Doe
30
New York
*/

4. 字符串比较 (Comparing Strings):极其重要!

比较字符串时,你必须使用 equals()equalsIgnoreCase() 方法。

  • equals():精确比较,区分大小写。
  • equalsIgnoreCase():忽略大小写进行比较。

新手避坑指南: 绝对不要使用 == 来比较字符串的内容!== 比较的是两个变量在内存中的物理地址(它们是不是同一个对象),而 .equals() 比较的才是字面文本内容是否相同。

String str1 = "Java";
String str2 = "java";

System.out.println(str1.equals(str2)); // 输出: false (因为大小写不同)
System.out.println(str1.equalsIgnoreCase(str2)); // 输出: true (忽略了大小写)

String str3 = new String("Java");
String str4 = "Java";

System.out.println(str3.equals(str4)); // 输出: true (它们的内容确实都是 "Java")
System.out.println(str3 == str4);      // 输出: false (警告!因为一个是 new 出来的堆对象,一个是常量池对象,地址不同)

5. 检查空字符串或 null

在处理字符串时,经常需要检查它是不是空的(长度为 0),或者是不是 null(变量尚未指向任何对象)。

String emptyString = "";
String nullString = null;

System.out.println("emptyString 是空的吗? " + emptyString.isEmpty()); // 输出: true

if (nullString == null) {
    System.out.println("nullString 的值是 null"); // 输出: nullString 的值是 null
}

// 组合防御:防止空指针异常 (NullPointerException) 的标准写法
if (emptyString != null && !emptyString.isEmpty()) {
    System.out.println("字符串有实际内容");
} else {
    System.out.println("字符串要么是 null,要么是空串"); // 输出: 字符串要么是 null,要么是空串
}

6. 查找字符或子串 (Finding)

indexOf()lastIndexOf() 用于定位某个子串在原字符串中的具体索引位置。如果找不到,它们会返回 -1

  • indexOf():从左往右找,返回第一次出现的位置。
  • lastIndexOf():从右往左找,返回最后一次出现的位置。
String text = "This is a test string";

int firstIndex = text.indexOf("is");
int lastIndex = text.lastIndexOf("is");

System.out.println("'is' 第一次出现的索引: " + firstIndex); // 输出: 'is' 第一次出现的索引: 2 (Th'is' 里面的 is)
System.out.println("'is' 最后一次出现的索引: " + lastIndex);   // 输出: 'is' 最后一次出现的索引: 5 (单独的那个 'is')

int notFoundIndex = text.indexOf("xyz");
System.out.println("'xyz' 的索引: " + notFoundIndex); // 输出: 'xyz' 的索引: -1 (没找到)

7. 字符串格式化 (String Formatting)

Java 提供了 String.format() 方法,允许你通过“占位符 (Format specifiers)”来优雅地拼接和格式化字符串,这比满屏幕的 + 号要清晰得多。

String name = "Alice";
int age = 30;
double salary = 50000.0;

// 使用占位符将变量嵌入文本中
String formattedString = String.format("姓名: %s, 年龄: %d, 薪水: %.2f", name, age, salary);
System.out.println(formattedString); // 输出: 姓名: Alice, 年龄: 30, 薪水: 50000.00

常见占位符速查表:

  • %s : 插入 String (字符串)
  • %d : 插入 Integer (整数)
  • %f : 插入 Floating-point (浮点数)
  • %.<数字>f : 保留指定小数位数的浮点数 (如 %.2f 保留两位小数)
  • %c : 插入 Character (单个字符)
  • %b : 插入 Boolean (布尔值)