Java零基础入门:可变字符串与包装类:StringBuilder、StringBuffer 与 通讯录管理系统实战
一、为什么需要 StringBuilder?------ String 的"不可变性"
1. String 是不可变的
在 Java 中,String 对象一旦创建,内容就不能改变。
String s = "Hello";
s = s + " World"; // 看起来是"修改"了,其实不是
真相是:
第1步:创建 "Hello" → 存在内存里
第2步:创建 " World" → 存在内存里
第3步:拼接产生 "Hello World" → 又是一个全新的字符串对象
第4步:让变量 s 指向这个新对象
原来的 "Hello" 并没有被修改,它还在内存里等着被垃圾回收。
2. 频繁拼接的灾难
// 这样写,循环10000次要创建10000多个临时对象!
String result = "";
for (int i = 0; i < 10000; i++) {
result = result + i; // 每次循环都产生新对象
}
后果:内存占用暴增 + 速度极慢(因为每次都要复制之前的字符)。
3. 解决方案:StringBuilder
StringBuilder 就像一块可擦写的白板 ,你可以在上面不断写字、擦字、插字,不会每次都换一块新白板。
二、StringBuilder 详解
1. 创建 StringBuilder
// 方式一:空的可变字符串
StringBuilder sb1 = new StringBuilder();
// 方式二:带初始内容的
StringBuilder sb2 = new StringBuilder("Hello");
// 方式三:指定初始容量(知道大概要多长时用)
StringBuilder sb3 = new StringBuilder(100);
2. 常用方法速查表
| 方法 | 作用 | 示例 |
|---|---|---|
| append(任意类型) | 在末尾追加 | sb.append("World") |
| insert(位置, 内容) | 在指定位置插入 | sb.insert(5, " Java") |
| delete(开始, 结束) | 删除指定范围 | sb.delete(5, 10) |
| deleteCharAt(索引) | 删除指定位置的字符 | sb.deleteCharAt(0) |
| replace(开始, 结束, 新串) | 替换 | sb.replace(0, 5, "Hi") |
| reverse() | 反转字符串 | sb.reverse() |
| toString() | 转成 String | String s = sb.toString() |
| length() | 获取长度 | sb.length() |
| capacity() | 获取当前容量 | sb.capacity() |
| setLength(新长度) | 设置长度 | sb.setLength(5) |
3. 基本使用示例
public class StringBuilderDemo {
public static void main(String\[\] args) {
StringBuilder sb = new StringBuilder("Hello");
// 1. 追加
sb.append(" ");
sb.append("World");
sb.append("!");
System.out.println(sb); // Hello World!
// 2. 插入
sb.insert(6, "Java "); // 在索引6处插入
System.out.println(sb); // Hello Java World!
// 3. 删除
sb.delete(6, 11); // 删除索引6到10("Java ")
System.out.println(sb); // Hello World!
// 4. 替换
sb.replace(6, 11, "Java");
System.out.println(sb); // Hello Java!
// 5. 反转
sb.reverse();
System.out.println(sb); // !avaJ olleH
// 6. 再反转回来
sb.reverse();
System.out.println(sb); // Hello Java!
// 7. 转成 String
String result = sb.toString();
System.out.println("转成String: " + result);
}
}
4. 链式调用(超好用)
StringBuilder 的大多数方法返回自身(return this),所以可以连着写:
StringBuilder sb = new StringBuilder();
sb.append("姓名:").append("张三").append(",年龄:").append(20).append("岁");
System.out.println(sb.toString());
// 输出:姓名:张三,年龄:20岁
一行搞定,不需要写一堆 + 号拼接。
三、StringBuffer:线程安全的版本
StringBuffer 和 StringBuilder 的用法完全一样,唯一区别是:
| 对比项 | StringBuilder | StringBuffer |
|---|---|---|
| 线程安全 | ❌ 不安全(单线程用) | ✅ 安全(多线程用) |
| 速度 | 更快 | 稍慢(因为加了同步锁) |
| 使用场景 | 单线程(99%的情况) | 多线程共享同一个字符串缓冲区 |
// StringBuffer 用法跟 StringBuilder 一模一样
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World");
System.out.println(sbf.toString());
结论 :你写单线程程序(比如学生管理系统、通讯录),永远用 StringBuilder 就够了。
四、性能对比实验:String vs StringBuilder vs StringBuffer
完整测试代码
public class StringPerformanceTest {
public static void main(String\[\] args) {
int count = 50000; // 拼接次数
// ===== 测试 String =====
long start1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < count; i++) {
s = s + i;
}
long end1 = System.currentTimeMillis();
System.out.println("String 拼接 " + count + " 次耗时:" + (end1 - start1) + " ms");
// ===== 测试 StringBuilder =====
long start2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
sb.append(i);
}
long end2 = System.currentTimeMillis();
System.out.println("StringBuilder 拼接 " + count + " 次耗时:" + (end2 - start2) + " ms");
// ===== 测试 StringBuffer =====
long start3 = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < count; i++) {
sbf.append(i);
}
long end3 = System.currentTimeMillis();
System.out.println("StringBuffer 拼接 " + count + " 次耗时:" + (end3 - start3) + " ms");
}
}
典型运行结果(不同电脑有差异)
String 拼接 50000 次耗时:4821 ms
StringBuilder 拼接 50000 次耗时:3 ms
StringBuffer 拼接 50000 次耗时:5 ms
差距是几百倍到上千倍!
为什么 String 这么慢?
每次 s = s + i 都做了这些事:
- 创建新的 StringBuilder(隐式)
- 把 s 的内容复制进去
- 把 i 追加进去
- 调用 toString() 生成新 String
- 原来的 String 变成垃圾等回收
循环 50000 次 = 创建了 50000 个临时对象 + 复制了 50000 次数组。
而 StringBuilder 只在同一个字符数组上操作,满了才扩容,效率天差地别。
五、包装类:基本类型的"马甲"
1. 为什么需要包装类?
Java 有 8 种基本类型(int、double、boolean 等),但它们不是对象,不能:
- 放进集合里(ArrayList<int> ❌ 不允许)
- 调用方法
- 参与面向对象的多态
所以 Java 给每种基本类型都配了一个对应的类 ,叫包装类(Wrapper Class)。
2. 八种基本类型与包装类对照表
| 基本类型 | 包装类 | 备注 |
|---|---|---|
| byte | Byte | |
| short | Short | |
| int | Integer | 最常用 |
| long | Long | |
| float | Float | |
| double | Double | |
| char | Character | 唯一不是以基本类型名+s结尾的 |
| boolean | Boolean |
3. 自动装箱与自动拆箱
手动装箱/拆箱(Java 5 之前必须这样写)
// 手动装箱:基本类型 → 包装类
Integer num1 = Integer.valueOf(100);
// 手动拆箱:包装类 → 基本类型
int num2 = num1.intValue();
自动装箱/拆箱(Java 5 之后,编译器帮你做)
// 自动装箱:编译器自动把 int 100 变成 Integer.valueOf(100)
Integer num1 = 100;
// 自动拆箱:编译器自动调用 num1.intValue()
int num2 = num1;
// 甚至可以直接参与运算
Integer a = 10;
Integer b = 20;
int sum = a + b; // a 和 b 自动拆箱成 int,相加后再赋值给 sum
底层原理:你写 Integer num = 100;,编译器编译后变成 Integer num = Integer.valueOf(100);。你写 int x = num;,编译后变成 int x = num.intValue();。
4. 常见坑:Integer 缓存
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true(缓存范围内)
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false(超出缓存范围)
System.out.println(c.equals(d)); // true(用 equals 比较值才正确)
原因 :Integer.valueOf() 对 -128 到 127 之间的数会缓存复用,超出范围才 new 新对象。
记住 :比较包装类的值,永远用 equals(),不要用 ==。
六、字符串与基本类型互转
1. 字符串 → 基本类型
// 方式一:用包装类的 parseXxx() 方法(最常用)
int age = Integer.parseInt("20");
double price = Double.parseDouble("19.99");
boolean flag = Boolean.parseBoolean("true");
// 方式二:用 valueOf()(返回的是包装类对象)
Integer ageObj = Integer.valueOf("20");
int age2 = ageObj; // 自动拆箱
注意:字符串格式必须正确,否则抛异常:
int x = Integer.parseInt("abc"); // ❌ NumberFormatException
2. 基本类型 → 字符串
// 方式一:String.valueOf()(最推荐,不会抛异常)
String s1 = String.valueOf(123); // "123"
String s2 = String.valueOf(3.14); // "3.14"
String s3 = String.valueOf(true); // "true"
// 方式二:拼接空字符串
String s4 = 123 + "";
// 方式三:包装类的 toString()
String s5 = Integer.toString(123);
3. 综合练习:用户输入数字并计算
import java.util.Scanner;
public class StringParseDemo {
public static void main(String\[\] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入第一个数字:");
String input1 = sc.nextLine();
int num1 = Integer.parseInt(input1); // 字符串 → int
System.out.print("请输入第二个数字:");
String input2 = sc.nextLine();
int num2 = Integer.parseInt(input2);
int sum = num1 + num2;
// int → String 输出
System.out.println("结果:" + String.valueOf(sum));
// 或者直接用 + 拼接
System.out.println("结果:" + sum);
sc.close();
}
}
七、综合小项目:通讯录管理系统
1. 需求分析
| 功能 | 描述 |
|---|---|
| 添加联系人 | 输入姓名、电话、邮箱、分组 |
| 删除联系人 | 按姓名或电话删除 |
| 修改联系人 | 按姓名查找,修改电话/邮箱 |
| 查询联系人 | 按姓名模糊搜索 |
| 显示所有 | 列出所有联系人 |
| 退出 | 保存并退出 |
2. 完整代码实现
Contact.java(联系人实体类)
public class Contact {
private String name;
private String phone;
private String email;
private String group; // 分组:家人、朋友、同事等
public Contact(String name, String phone, String email, String group) {
this.name = name;
this.phone = phone;
this.email = email;
this.group = group;
}
// Getter 和 Setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getGroup() { return group; }
public void setGroup(String group) { this.group = group; }
// 重写 toString,用 StringBuilder 拼接(性能更好)
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("【").append(group).append("】")
.append(name).append(" - ")
.append(phone).append(" - ")
.append(email);
return sb.toString();
}
}
ContactManager.java(管理类)
import java.util.ArrayList;
public class ContactManager {
private ArrayList<Contact> contacts = new ArrayList<>();
// ===== 添加联系人 =====
public boolean addContact(Contact contact) {
// 检查电话是否已存在
for (Contact c : contacts) {
if (c.getPhone().equals(contact.getPhone())) {
System.out.println("❌ 该电话号码已存在!");
return false;
}
}
contacts.add(contact);
System.out.println("✅ 添加成功!");
return true;
}
// ===== 删除联系人(按电话)=====
public boolean removeByPhone(String phone) {
for (int i = 0; i < contacts.size(); i++) {
if (contacts.get(i).getPhone().equals(phone)) {
Contact removed = contacts.remove(i);
System.out.println("✅ 已删除:" + removed.getName());
return true;
}
}
System.out.println("❌ 未找到该电话号码对应的联系人");
return false;
}
// ===== 修改联系人 =====
public boolean updateContact(String phone, String newName, String newEmail, String newGroup) {
for (Contact c : contacts) {
if (c.getPhone().equals(phone)) {
if (newName != null && !newName.trim().isEmpty()) {
c.setName(newName);
}
if (newEmail != null && !newEmail.trim().isEmpty()) {
c.setEmail(newEmail);
}
if (newGroup != null && !newGroup.trim().isEmpty()) {
c.setGroup(newGroup);
}
System.out.println("✅ 修改成功!");
return true;
}
}
System.out.println("❌ 未找到该电话号码对应的联系人");
return false;
}
// ===== 按姓名模糊搜索 =====
public void searchByName(String keyword) {
boolean found = false;
System.out.println("\n====== 搜索结果 ======");
for (Contact c : contacts) {
if (c.getName().toLowerCase().contains(keyword.toLowerCase())) {
System.out.println(c);
found = true;
}
}
if (!found) {
System.out.println("未找到包含 \"" + keyword + "\" 的联系人");
}
System.out.println("======================\n");
}
// ===== 显示所有联系人 =====
public void showAll() {
if (contacts.isEmpty()) {
System.out.println("📭 通讯录为空");
return;
}
System.out.println("\n====== 通讯录(共 " + contacts.size() + " 人)======");
for (int i = 0; i < contacts.size(); i++) {
System.out.println((i + 1) + ". " + contacts.get(i));
}
System.out.println("================================\n");
}
// ===== 按分组显示 =====
public void showByGroup(String group) {
boolean found = false;
System.out.println("\n====== " + group + " 分组 ======");
for (Contact c : contacts) {
if (c.getGroup().equals(group)) {
System.out.println(c);
found = true;
}
}
if (!found) {
System.out.println("该分组下暂无联系人");
}
System.out.println("==========================\n");
}
}
ContactSystem.java(主程序/菜单交互)
import java.util.Scanner;
public class ContactSystem {
public static void main(String\[\] args) {
Scanner sc = new Scanner(System.in);
ContactManager manager = new ContactManager();
// 预置几个联系人
manager.addContact(new Contact("张三", "13800138001", "zhangsan@email.com", "朋友"));
manager.addContact(new Contact("李四", "13800138002", "lisi@email.com", "同事"));
manager.addContact(new Contact("王五", "13800138003", "wangwu@email.com", "家人"));
while (true) {
System.out.println("\n====== 通讯录管理系统 ======");
System.out.println("1. 添加联系人");
System.out.println("2. 删除联系人");
System.out.println("3. 修改联系人");
System.out.println("4. 搜索联系人(按姓名)");
System.out.println("5. 显示所有联系人");
System.out.println("6. 按分组查看");
System.out.println("7. 退出");
System.out.print("请选择:");
String choice = sc.nextLine();
int choiceNum = Integer.parseInt(choice); // 字符串 → int
switch (choiceNum) {
case 1: {
// 添加
System.out.print("姓名:");
String name = sc.nextLine();
System.out.print("电话:");
String phone = sc.nextLine();
System.out.print("邮箱:");
String email = sc.nextLine();
System.out.print("分组(朋友/同事/家人):");
String group = sc.nextLine();
Contact c = new Contact(name, phone, email, group);
manager.addContact(c);
break;
}
case 2: {
// 删除
System.out.print("输入要删除的联系人电话:");
String phone = sc.nextLine();
manager.removeByPhone(phone);
break;
}
case 3: {
// 修改
System.out.print("输入要修改的联系人电话:");
String phone = sc.nextLine();
System.out.print("新姓名(不改直接回车):");
String newName = sc.nextLine();
System.out.print("新邮箱(不改直接回车):");
String newEmail = sc.nextLine();
System.out.print("新分组(不改直接回车):");
String newGroup = sc.nextLine();
manager.updateContact(phone, newName, newEmail, newGroup);
break;
}
case 4: {
// 搜索
System.out.print("输入搜索关键词(姓名):");
String keyword = sc.nextLine();
manager.searchByName(keyword);
break;
}
case 5: {
// 显示所有
manager.showAll();
break;
}
case 6: {
// 按分组查看
System.out.print("输入分组名称:");
String group = sc.nextLine();
manager.showByGroup(group);
break;
}
case 7: {
// 退出
System.out.println("👋 再见!");
sc.close();
System.exit(0);
break;
}
default:
System.out.println("❌ 无效选择,请重新输入");
}
}
}
}
3. 代码亮点总结
| 知识点 | 在代码中的体现 |
|---|---|
| ArrayList | 存储所有联系人 |
| StringBuilder | Contact.toString() 中拼接输出信息 |
| 包装类 | Integer.parseInt(choice) 字符串转 int |
| 字符串处理 | trim()、isEmpty()、toLowerCase()、contains() |
| CRUD 完整实现 | 增删改查全覆盖 |
| 菜单循环 | while(true) + switch |
| 空指针防护 | 修改时判断 null 和空字符串 |
八、每日一练
练习1(基础):StringBuilder 操作
- 创建 StringBuilder,追加你的姓名、年龄、爱好
- 在姓名和年龄之间插入"今年"
- 删除爱好部分
- 最后反转输出
练习2(进阶):包装类转换
- 从键盘输入5个字符串形式的数字,转成 int 后存入 ArrayList<Integer>
- 计算它们的平均值并输出
练习3(挑战):给通讯录加新功能
- 添加"导出为文本"功能:把所有联系人信息用 StringBuilder 拼接后写入 .txt 文件
- 添加"排序"功能:按姓名拼音排序(提示:用 Collections.sort() + Comparator)