🎯 今日学习目标

  1. 深入理解 String类,明白它为什么是“不可变”的。
  2. 掌握创建 String对象的多种方式。
  3. 熟练使用字符串的常用方法:获取长度、比较、查找、截取、替换、大小写转换等。
  4. 理解字符串拼接的不同方式及其效率差异。
  5. 能够使用字符串解决实际问题,如文本分析、数据格式化。

一、初识字符串:不只是“一段文字”

在之前的课程中,我们已经多次使用过字符串:

String name = "大雄";
System.out.println("Hello, World!");

String在Java中远不止是“用双引号包起来的文字”。它是一个,是Java标准库中用来表示和操作字符序列的核心类。

1. 字符串无处不在

想想你使用的软件:

  • 用户名、密码是 String
  • 聊天消息、文章内容是 String
  • 文件路径、网址是 String
  • 甚至程序读取的配置文件、网络传输的数据,也常常是 String格式。

可以说,String是编程中处理“文本”信息的基石。​ 掌握好它,你才能让程序“读懂”和“说出”人话。


二、String 的创建:多种方式,殊途同归

创建 String对象(也就是得到一个字符串)主要有以下几种方式:

1. 直接赋值(最常用、最推荐)

String str1 = "Hello, Java!";
String str2 = "你好,Java!";
String emptyStr = ""; // 空字符串,长度为0

这种方式最为简洁直观。Java会使用一种叫做“字符串常量池”的机制来管理这些字符串,可以提高效率。你现在可以简单理解为:用这种方式创建,Java会尽量复用已有的相同字符串。

image.png

2. 通过构造方法 new String()

String是一个类,自然可以用 new关键字来创建它的对象。

// 方式1:根据字符数组创建
char[] charArray = {'J', 'a', 'v', 'a'};
String str3 = new String(charArray); // str3 的内容是 "Java"

// 方式2:根据字节数组创建(常用于网络传输、文件读写)
byte[] byteArray = {65, 66, 67}; // ASCII码,A=65, B=66, C=67
String str4 = new String(byteArray); // str4 的内容是 "ABC"

// 方式3:直接 new
String str5 = new String("Hello"); // 创建一个内容为"Hello"的新String对象

注意new String(“Hello”)String str = “Hello”;这两种写法在内存处理上有细微差别。对于初学者,记住绝大多数情况下,使用第一种“直接赋值”的方式就够了,它更简洁高效。new String()通常在有特定需求(如从字节数组转换)时使用。

3. 一个重要的概念:字符串不可变性

这是理解 String所有行为的关键!String对象一旦被创建,其内容(字符序列)就永远不能被改变。

String s = "Hello";
String sCopy = s; // 将s赋值给sCopy
s = s + " World"; // 这看起来像是修改了s
System.out.println(s); // 输出 "Hello World"
System.out.println(sCopy); // 输出的还是 "Hello"

❌ 误解:上面代码好像把 "Hello"改成了 "Hello World"

✅ 真相:实际上,s + “ World”这个操作创建了一个全新的字符串对象“Hello World”,然后让变量 s指向这个新对象。原来的 “Hello”这个字符串对象依然存在于内存中,并且内容 “Hello”没有任何变化。

形象的比喻

String对象想象成一个已经写上字并且用玻璃封装好的相框

  • 你无法擦掉玻璃相框里已经写好的字(不可修改内容)。
  • 但你可以换一个新的、写了不同字的相框,然后把标签(变量名 s)贴到这个新相框上(让变量引用新对象)。

为什么这样设计?

主要是为了安全效率。因为不可变,所以多个变量可以安全地共享同一个字符串对象,而不必担心其中一个修改会影响其他。这也是“字符串常量池”能够存在的基础。


三、字符串的常用方法(上):信息获取与比较

String类提供了几十个方法供我们操作字符串。我们按功能分类学习。今天先学习最常用的一部分。

如何调用方法:字符串是对象,所以调用其方法的语法是:字符串变量名.方法名(参数)

1. 获取字符串信息

String message = "Hello, Java Programmers!";

// 1. length(): 返回字符串的长度(字符个数)
int len = message.length();
System.out.println("字符串长度: " + len); // 输出: 24 (注意空格和标点都算一个字符)

// 2. charAt(int index): 返回指定索引处的字符
char firstChar = message.charAt(0);
char lastChar = message.charAt(message.length() - 1);
System.out.println("第一个字符: " + firstChar); // H
System.out.println("最后一个字符: " + lastChar); // !

// 3. isEmpty(): 判断字符串是否为空(长度为0)
String emptyStr = "";
System.out.println("emptyStr是否为空? " + emptyStr.isEmpty()); // true
System.out.println("message是否为空? " + message.isEmpty());   // false
// 注意:null 和 空字符串 "" 是两回事。一个没有引用的变量(null)调用方法会报错。

2. 字符串的比较

⚠️ 超级重点:比较字符串内容是否相等,永远不要用 ==

String s1 = "Java";
String s2 = "Java";
String s3 = new String("Java");
String s4 = "java";

// ❌ 错误的方式:使用 ==
// == 比较的是两个变量是否指向内存中的同一个对象(地址),而不是内容是否相同。
System.out.println("s1 == s2: " + (s1 == s2)); // 可能为 true (因为常量池复用)
System.out.println("s1 == s3: " + (s1 == s3)); // false! 因为s3是new出来的新对象

// ✅ 正确的方式:使用 equals() 方法比较内容
System.out.println("s1.equals(s2): " + s1.equals(s2)); // true
System.out.println("s1.equals(s3): " + s1.equals(s3)); // true!内容相同
System.out.println("s1.equals(s4): " + s1.equals(s4)); // false,大小写不同

// ✅ 忽略大小写比较:equalsIgnoreCase()
System.out.println("s1.equalsIgnoreCase(s4): " + s1.equalsIgnoreCase(s4)); // true

// 比较字符串在字典中的顺序:compareTo()
// 返回0表示相等,负数表示调用者在前,正数表示参数在前(按Unicode值比较)
System.out.println("\"apple\".compareTo(\"banana\"): " + "apple".compareTo("banana")); // 负数
System.out.println("\"cat\".compareTo(\"cat\"): " + "cat".compareTo("cat")); // 0
System.out.println("\"dog\".compareTo(\"cat\"): " + "dog".compareTo("cat")); // 正数

为什么 ==有时也对?

因为Java的“字符串常量池”优化,String s1 = “Java”;String s2 = “Java”;可能让 s1s2指向内存中同一个 “Java”对象,所以 ==比较为真。但这是一种不稳定的优化,绝非比较内容的正确方法。牢记:比较字符串内容,请无条件使用 equals()方法。


四、字符串的常用方法(中):查找与子串

1. 查找字符或子串

String text = "I love Java programming. Java is fun!";

// 1. indexOf(...): 返回指定字符或子串第一次出现的索引,找不到返回-1
int indexOfLove = text.indexOf("love");
int indexOfJava = text.indexOf("Java");
int indexOfPython = text.indexOf("Python");
System.out.println("'love'首次出现在: " + indexOfLove); // 2
System.out.println("'Java'首次出现在: " + indexOfJava); // 7
System.out.println("'Python'首次出现: " + indexOfPython); // -1 (没找到)

// 可以从指定位置开始查找
int secondJavaIndex = text.indexOf("Java", indexOfJava + 1); // 从第一个Java后面开始找
System.out.println("第二个'Java'在: " + secondJavaIndex); // 25

// 2. lastIndexOf(...): 返回指定字符或子串最后一次出现的索引
int lastIndexOfJava = text.lastIndexOf("Java");
System.out.println("'Java'最后一次出现在: " + lastIndexOfJava); // 25

// 3. contains(CharSequence s): 判断是否包含某个子串 (返回boolean)
System.out.println("是否包含'fun'? " + text.contains("fun")); // true
System.out.println("是否包含'Python'? " + text.contains("Python")); // false

2. 截取子串

String url = "https://www.example.com/page/index.html";

// substring(int beginIndex): 从beginIndex开始截取到字符串末尾
String domain = url.substring(8); // 从索引8('w')开始截取
System.out.println("域名及之后: " + domain); // www.example.com/page/index.html

// substring(int beginIndex, int endIndex):
// 截取 [beginIndex, endIndex) 范围的子串 (包含头,不包含尾)
String protocol = url.substring(0, 5); // 索引0到4
String host = url.substring(8, 22); // 索引8到21
System.out.println("协议: " + protocol); // https
System.out.println("主机: " + host); // www.example.com

五、字符串的常用方法(下):修改与转换

再次强调:由于字符串不可变,所有这些“修改”方法都不会改变原字符串,而是返回一个全新的字符串对象

1. 替换

String original = "Hello World, World is big.";

// replace(char oldChar, char newChar): 替换所有指定字符
String replaced1 = original.replace('o', '0');
System.out.println(replaced1); // Hell0 W0rld, W0rld is big.

// replace(CharSequence target, CharSequence replacement): 替换所有子串
String replaced2 = original.replace("World", "Java");
System.out.println(replaced2); // Hello Java, Java is big.

// 注意:原字符串不变!
System.out.println("原字符串: " + original); // 仍然是 "Hello World, World is big."

2. 去除首尾空白

String userInput = "    Hello, how are you?   \n";
String trimmed = userInput.trim(); // 去除首尾的空白字符(空格、制表符、换行符)
System.out.println("修剪前: '" + userInput + "'");
System.out.println("修剪后: '" + trimmed + "'");
// 中间的空格不会被去除

3. 大小写转换

String mixedCase = "Hello Java";

String upper = mixedCase.toUpperCase();
String lower = mixedCase.toLowerCase();

System.out.println("原串: " + mixedCase);
System.out.println("大写: " + upper); // HELLO JAVA
System.out.println("小写: " + lower); // hello java

六、字符串拼接

将多个字符串连接成一个,是最常见的操作之一。

1. 使用 +运算符

最简单直观的方式。

String firstName = "张";
String lastName = "三";
int age = 25;

String fullName = firstName + lastName; // "张三"
String info = "姓名: " + fullName + ", 年龄: " + age; // 字符串与数字拼接,数字会自动转为字符串
System.out.println(info);

2. 使用 concat()方法

功能和 +类似。

String s1 = "Hello";
String s2 = "World";
String s3 = s1.concat(" ").concat(s2); // 链式调用
System.out.println(s3); // Hello World

3. 性能考虑与 StringBuilder

+concat()在循环中大量拼接字符串时效率很低,因为每次都会创建新的String对象。

// ❌ 低效的写法(在循环内用 + 拼接)
String result = "";
for (int i = 0; i < 1000; i++) {
    result += i; // 每次循环都产生一个新字符串,旧对象成为垃圾
}

高效的做法:使用 StringBuilder

StringBuilder是一个可变的字符序列,专门用于高效拼接字符串。

// ✅ 高效的写法
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i); // 在同一个StringBuilder对象上修改,不创建新对象
}
String finalResult = sb.toString(); // 最后一次性转换为String
System.out.println(finalResult);

简单规则:在循环内拼接字符串,或者需要频繁修改字符串内容时,使用 StringBuilder。简单的单次拼接用 +即可。


七、综合实战:文本分析工具

让我们运用所学的字符串方法,制作一个简单的文本分析工具。

import java.util.Scanner;

public class TextAnalyzer {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        System.out.println("=== 简易文本分析工具 ===");
        System.out.println("请输入一段英文文本(按回车结束):");
        String inputText = scanner.nextLine(); // 读取一整行文本
        
        if (inputText == null || inputText.trim().isEmpty()) {
            System.out.println("输入不能为空!");
            scanner.close();
            return;
        }
        
        // 1. 基本统计
        int totalChars = inputText.length();
        String trimmedText = inputText.trim();
        int nonSpaceChars = trimmedText.replace(" ", "").length(); // 去除空格后的长度
        int wordCount = countWords(trimmedText);
        
        // 2. 查找关键词
        System.out.print("请输入一个你想在文中查找的单词: ");
        String keyword = scanner.next().toLowerCase(); // 读取一个单词,并转为小写
        int keywordOccurrences = countOccurrences(trimmedText.toLowerCase(), keyword);
        
        // 3. 输出报告
        printAnalysisReport(trimmedText, totalChars, nonSpaceChars, wordCount, keyword, keywordOccurrences);
        
        scanner.close();
    }
    
    /**
     * 统计单词数(简易版,按空格分割)
     */
    public static int countWords(String text) {
        if (text.isEmpty()) {
            return 0;
        }
        // 使用 split 方法按空白字符分割字符串,返回单词数组
        String[] words = text.split("\\s+"); // 正则表达式 \s+ 表示一个或多个空白字符
        return words.length;
    }
    
    /**
     * 统计某个关键词出现的次数
     */
    public static int countOccurrences(String text, String keyword) {
        int count = 0;
        int index = 0;
        // 循环查找,每次从上一次找到的位置之后开始
        while ((index = text.indexOf(keyword, index)) != -1) {
            count++;
            index += keyword.length(); // 跳过已找到的关键词,继续向后查找
        }
        return count;
    }
    
    /**
     * 打印分析报告
     */
    public static void printAnalysisReport(String text, int totalChars, int nonSpaceChars,
                                           int wordCount, String keyword, int occurrences) {
        System.out.println("\n========== 文本分析报告 ==========");
        System.out.println("原文摘要: \"" + getTextPreview(text, 50) + "\"");
        System.out.println("----------------------------------");
        System.out.println("1. 字符统计:");
        System.out.println("   总字符数 (含空格): " + totalChars);
        System.out.println("   非空格字符数: " + nonSpaceChars);
        System.out.println("   单词数 (近似): " + wordCount);
        System.out.println("\n2. 关键词查找:");
        System.out.printf("   关键词 \"%s\" 出现了 %d 次。\n", keyword, occurrences);
        System.out.println("\n3. 其他信息:");
        System.out.println("   全部大写: " + text.toUpperCase());
        System.out.println("   全部小写: " + text.toLowerCase());
        System.out.println("==================================");
    }
    
    /**
     * 获取文本预览(前n个字符,超出部分用...表示)
     */
    public static String getTextPreview(String text, int maxLength) {
        if (text.length() <= maxLength) {
            return text;
        } else {
            return text.substring(0, maxLength) + "...";
        }
    }
}

📚 今日巩固练习

练习1:验证回文串

回文串是指正着读和反着读都一样的字符串,忽略空格、标点、大小写。例如:“A man, a plan, a canal: Panama”。

  1. 编写一个方法 boolean isPalindrome(String s),判断一个字符串是否是回文串。
  2. main方法中测试。

提示:可以先使用 toLowerCase()replaceAll(“[^a-z0-9]”, “”)(正则表达式,去掉非字母数字字符)处理字符串,再用双指针法或反转字符串比较。

练习2:密码强度检查器

编写一个程序,检查用户输入的密码强度。规则如下(可自定义):

  • 长度 >= 8
  • 包含至少一个大写字母
  • 包含至少一个小写字母
  • 包含至少一个数字
  • 包含至少一个特殊字符(如!@#$%) 根据满足的条件数量,输出密码强度:弱、中、强。

提示:使用循环和 charAt()遍历密码字符,或用 contains()配合条件判断。

练习3:字符串翻转

编写一个方法 String reverseString(String s),实现字符串的翻转。例如,输入 “hello”,返回 “olleh”

  1. 使用循环和 charAt()实现。
  2. 尝试使用 StringBuilderreverse()方法实现。

挑战题:简单的命令行解析

模拟解析一个命令行指令,如 “copy file.txt /home/user backup_”

  1. 将指令按空格分割成数组(split(” “))。
  2. 第一个元素是命令(copy)。
  3. 后续的是参数。尝试识别参数中的选项(以 -开头的)和值。

💡 常见问题解答 (Q&A)

Q1:String是基本数据类型吗?

A:不是。​ String是Java标准库提供的一个java.lang.String)。它是引用类型。只是因为太常用了,Java允许用双引号 ""这种字面量形式创建其对象,让人感觉像基本类型。

Q2:==equals()的区别,再说一次?

A:这是必考重点!

  • ==:比较两个变量存储的地址值是否相同,即是否指向内存中同一个对象
  • equals():比较两个对象的内容是否相同(对于 String就是字符序列是否相同)。 黄金法则:只要你想比较两个字符串的内容是否一样,永远、无条件、必须使用 equals()方法。

Q3:null和空字符串 ""一样吗?

A:完全不一样!

  • null:表示一个引用(变量)没有指向任何对象。在 null上调用任何方法(如 length())都会导致 NullPointerException
  • "":这是一个有效的 String对象,内容是空的,长度为0。你可以在它上面安全地调用所有 String方法。
String s1 = null;
String s2 = "";
System.out.println(s1 == null); // true
System.out.println(s2 == null); // false
System.out.println(s2.isEmpty()); // true
// System.out.println(s1.isEmpty()); // 运行时错误: NullPointerException

Q4:字符串的“不可变性”到底有什么用?

A:主要好处:

  1. 安全:作为参数传递时,不用担心方法内部会修改它。例如,数据库用户名、密码作为字符串传递是安全的。
  2. 可以缓存哈希值StringhashCode()经常被使用(如在 HashMap中),因为内容不变,哈希值只需计算一次,提高了效率。
  3. 实现字符串常量池,共享相同内容的字符串,节省内存。

Q5:什么时候用 StringBuilder

A:当你在循环体内进行字符串拼接,或者需要频繁、多次修改一个字符串时,使用 StringBuilder。对于简单的、次数固定的拼接(如 String s = a + b + c;),用 +即可,代码更清晰。


🎓 今日核心总结

今天,你深入学习了Java中处理文本的核心——String类。

  1. 理解本质:明白了 String是类而非基本类型,掌握了其不可变性这一核心特性。
  2. 创建对象:学会了用直接赋值和构造方法创建字符串。
  3. 熟练使用API:掌握了获取信息(length, charAt)、比较(equals, compareTo)、查找(indexOf, contains)、截取(substring)、修改(replace, trim, toUpperCase)等常用方法。牢记比较内容用 equals()
  4. 字符串拼接:了解了 +concat()StringBuilder的区别与适用场景。
  5. 实战应用:通过构建文本分析工具,综合运用了字符串的各种操作来解决实际问题。

🔧 能力提升:你现在可以让程序自如地处理用户输入、分析文件内容、格式化输出信息了。字符串操作是日常编程的“基本功”,今天学的方法将伴随你的整个编程生涯。


📮 作业提交与明日预告

你的作业

  1. 必做:完成“综合实战”中的 《文本分析工具》,并尝试添加一个新功能(如统计某个字母的出现频率)。

  2. 选做:从“巩固练习”中至少完成2个。

  3. 代码位置:请将你的作业代码文件,创建在以下目录中:

    module01_basic_syntax/src/day05_strings/

提交要求

  • 📝 每日学习打卡 话题下,搜索《Day 05 作业》完成每日打卡吧。

明日预告

Day 06:第一阶段复习与综合练习

明天是我们第一阶段“基础语法”的复习日。我们将:

  • 系统回顾前5天(变量、流程控制、数组、方法、字符串)的核心知识点。
  • 通过一系列精心设计的综合练习题,巩固你的学习成果。
  • 讲解常见的错误模式与调试技巧。
  • 为Day 07的迷你项目“控制台计算器”做准备。

✨ 提前准备:建议你花点时间复习一下前5天的笔记和代码,尝试自己总结每个知识点的关键。明天是检验和提升的好机会!


⚠️ 学习提醒

  1. 代码位置:请务必把今天的练习代码保存到正确的包路径下:module01_basic_syntax/src/day05_strings/

  2. 多敲多试:字符串的方法很多,最好的记忆方式是在IDEA里多敲代码,利用代码补全(输入”abc”.然后按Ctrl+Space)查看所有可用方法,并尝试它们。

  3. 善用Git:记得提交你的代码。

    git add .
    git commit -m "feat: Day 05: 掌握String类的核心方法及不可变性"
    
  4. 理解本质:“不可变性”和“equals()vs ==”是初学者的两大拦路虎。如果不甚明白,把今天的例子多运行几次,在 ❓ 问答与答疑​ 区提出你的困惑。

恭喜你完成了字符串的学习!你手中的工具又多了强大的一件。明天我们将停下脚步,回顾来时路,让知识沉淀得更牢固。复习是进步的阶梯,我们明天见!📖

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐